Project

General

Profile

Bug #11867

Covnersion fails if the abl folder is symlinked

Added by Alexandru Lungu about 1 month ago. Updated 5 days ago.

Status:
Review
Priority:
Normal
Target version:
-
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
version_resolved:
production:
No
env_name:
topics:

Related issues

Related to Conversion Tools - Bug #11156: Conversion fails if source folder is a symlink Review

History

#1 Updated by Alexandru Lungu about 1 month ago

  • Assignee set to Dănuț Filimon

ConversionDriver crashes with a NullPointerException during incremental conversion whenever the file-set's <directory> reference (basepath, e.g. "./abl/") is a symlink pointing to a directory whose own final path component has a different name than the symlink itself.

Steps to reproduce (using hotel_gui):
  • In the hotel_gui project, move the real source content aside and replace it with a symlink under a different target name, e.g.:
    • mv abl abl_src
    • ln -s abl_src abl (abl_src can be any name that differs from "abl" - that mismatch is what triggers the bug.)
  • Confirm cfg/p2j.cfg.xml's profile has a file-set directory reference for this path, e.g.:
    • <file-set>
    • <directory recursive="true" path="./abl/" spec="(.[pPwWtT]|.cls)" />
  • Run the conversion driver in incremental mode against that profile.

Expected is that the files under abl/ (via the symlink) are found and conversion proceeds normally, same as when abl is a real directory. But it fails with:

java.lang.NullPointerException: Cannot invoke
    "com.goldencode.artifacts.ArtifactCollection.getArtifacts()" because
    "col" is null
        at com.goldencode.artifacts.ArtifactCollection.<init>(ArtifactCollection.java:112)
        at com.goldencode.p2j.convert.TransformDriver.executeJob(TransformDriver.java:1149)
        at com.goldencode.p2j.convert.ConversionDriver.main(ConversionDriver.java:1458)

Root cause:

FileListFactory.resolvePaths() (FileListFactory.java:429-447) reconstructs each matched file's relative path from a real (symlink-resolved) absolute path using segment-count arithmetic:

    Path real = path.toRealPath();
    Path base = real.subpath(0, real.getNameCount() - path.getNameCount());
    Path resolved = real.subpath(base.getNameCount(), real.getNameCount());

This is only correct when the symlink's target ends in a path component named identically to the symlink itself. Whenever it differs (the normal case for any real symlink), the computed "resolved" path is wrong and does not exist relative to the project root. ExplicitFileList.listImpl() then silently drops every such entry because it only keeps files that actually exist on disk, so the whole file-set resolves to zero files.

That empty result reaches ArtifactManager.getArtifactCol(), whose "if (col.isEmpty()) return null;" turns the empty file-set into a null collection instead of an empty one; ArtifactManager.createProjectArtifacts() (PROFILE case) then wraps that null in a "success" ArtifactConversionResult (code=1), so job.artifacts ends up null. In incremental mode, TransformDriver.executeJob()'s "new ArtifactCollection(job.artifacts)" then throws the NPE above.

Suggested fix:

FileListFactory.resolvePaths() should not reconstruct the relative path from segment counts at all - it already has the original, normalized relative path and only needs toRealPath() to confirm the file exists; it should return the original path string rather than a path derived from the real (symlink-resolved) segments.

#2 Updated by Dănuț Filimon about 1 month ago

  • Related to Bug #11156: Conversion fails if source folder is a symlink added

#3 Updated by Dănuț Filimon about 1 month ago

  • Status changed from New to WIP

Looks like the same problem from #11156, I will rebase the branch and retest.

#4 Updated by Dănuț Filimon 30 days ago

  • Status changed from WIP to Review
  • % Done changed from 0 to 100

I committed 11156a/16754, the symbolic link will not be followed in resolvePaths().

Alexandru, please review.

#5 Updated by Dănuț Filimon 30 days ago

  • reviewer Alexandru Lungu added

#6 Updated by Alexandru Lungu 5 days ago

Danut, I looked at the changes in 11156a. They look right to me. But I wonder one thing: doesn't it affect the portability? In other words, I see many absolutePath being used, but if these paths reach some artifacts (including H2 incremental conversion database), won't this mean that the project should be converted always in the same place.

  • As a regression test, can you convert a project at a patch, move the project somewhere else and run incremental conversion?
  • Also think of OO and other more peculiar cases where conversion paths may end up being saved in artifacts / H2 database.
  • Also consider Windows system for regression testing these changes.
  • Nevertheless, mind that conversion is also being used at run-time, so double-check that there is no security backdoor into these changes done now (like converting a query that does some .., /etc logic).

AI review:

  • [MAJOR] functional SymbolResolver.exists: The exact-case branch now returns file.getAbsolutePath() (symlink preserved), but the case-insensitive fallback still calls FileSystemDaemon.getCaseInsensitiveMatch(file, true), which canonicalizes both input and result and therefore resolves symlinks. pkgExists always calls exists(file, false) for PROPATH entries (4GL class matching is case-insensitive), so on Linux any case-mismatched package reference reaches the fallback regardless of the case-sensitive setting. With a symlinked package directory under basepath (e.g. abl/lib -> /elsewhere/shared), USING lib.util.* normalizes to ./lib/util, while USING Lib.Util.* yields the canonical /elsewhere/shared/util. normalizeFilename cannot relativize that path, even with its canonical fallback, so the USING handling writes normalized-path=.elsewhere.shared.util. oo_references.rules then drops the Java import or builds a wrong package name. Trunk was uniformly canonical, so this branch leaves the result dependent on the letter case of the reference, which defeats its symlink-preserving goal. The case-insensitive path needs the same non-canonical treatment: match against the absolute/normalized path and return getAbsolutePath().
  • [MAJOR] functional FileScope.resolveFileName: On a Windows conversion host, getCanonicalPath() used to fold a path to its on-disk case. getAbsolutePath() / Path.normalize() now keep the case as referenced. Because NTFS is case-insensitive, two references differing only in case (e.g. {Inc/X.i} and {inc/x.i}) both pass the first canRead(), and findCaseInsensitively is never reached. open then sets fileName = normalizeFilename(fullname), producing ./Inc/X.i and ./inc/x.i. Since ArtifactKey compares names with plain String.equals / hashCode, one file becomes two artifacts. This splits pphints, include dependency tracking and AST registration. The same applies to class/package paths from SymbolResolver.exists, to the AST artifact names in AstGenerator, and to the case of the &{FILE_NAME} output. Linux hosts are unaffected. Windows conversion is explicitly supported (CallGraphHelper.isWindowsOs).
  • [MINOR] functional ArtifactManager.normalizeFilename: .. segments are now collapsed lexically by Path.normalize(), while the file itself is still opened through the raw path, where the OS resolves .. physically. Example: propath entry ./abl/app, where abl/app is a symlink to /x/app, and an include {../common/x.i}. FileScope.resolveFileName builds /proj/./abl/app/../common/x.i, and open reads /x/common/x.i. The registered key, however, becomes ./abl/common/x.i, which lies under home, so the canonical fallback never runs. That key feeds the ClearStream IncludeHint artifact, &{FILE_NAME}, the CallGraphWorker findUniqueNode lookup (which creates a phantom node for a non-existent path) and loadIncludeHints, all of which point at a missing or different file. Trunk's realpath kept the key consistent with the file actually read.

Style review is omitted for brevity. Regenerate the review yourself to capture the styling issues after we finish the functional bullets.

Also available in: Atom PDF