eirikbakke opened a new pull request, #9327:
URL: https://github.com/apache/netbeans/pull/9327

   For the past years, the "Go to Symbol" feature has appeared broken for me; 
searching for the name of a class that exists would frequently turn up no 
results. Today I managed to reproduce the problem. I put Claude Opus 4.6 on the 
task of investigating the issue, and had it produce both a fix and a regression 
test.
   
   The problem was that ".sig" files were written to the cache using a .class 
format with various NetBeans enhancements, but then loaded again in a way that 
failed when these enhancements were encountered. Only classes with certain 
contents would be affected, though here is a rather trivial example class that 
couldn't be found with "Go to Symbol" prior to the fix in this commit:
   
   ```
   public class DifficultClass19 {
     private static final String SOMECONST_STR = "hello";
     public void someMethod() { }
   }
   
   ```
   (If you try searching for this class in Go to Symbol, you may still get one 
hit in lowercase "difficultclass19". That's from the text-based Lucene index, 
which is used as a fallback when no Java symbols appear.)
   
   The fix is in AsyncJavaSymbolDescriptor. I have confirmed manually that the 
new JavaSymbolProviderTest fails without the fix in AsyncJavaSymbolDescriptor, 
and passes after the fix has been applied.
   
   An unrelated potential race condition in Go to Symbol's ContentProviderImpl 
is also fixed.
   
   **The commit/PR description above was written by me, Eirik. The code in this 
commit is by Claude Opus 4.6, written under my supervision. I am including 
Claude's summary of the investigation and fix below, as it is quite 
informative.**
   
   I'm putting a "do not merge" label on this until I've had more human 
feedback on this and tested the patch in my working IDE for a while.
   
   ### Claude's Summary (AI-generated, though useful)
   
   Fix Go to Symbol silently dropping Java results.
   
   AsyncJavaSymbolDescriptor.resolve() is meant to enrich Lucene-indexed Java 
symbol hits with real javac TypeElements. Since 2015 it has silently been 
returning an empty collection for every Java type whose per-root .sig file used 
any NetBeans-specific class-file encoding, which made Go to Symbol effectively 
broken for Java in many real projects: Lucene-backed entries flashed briefly in 
the list (lowercased, no icon) and were then deleted by 
Models.MutableListModelImpl#descriptorChanged, which treats an empty resolve() 
result as "delete this row". Clicking a surviving entry also did nothing, 
because the old open() went through the same empty resolve().
   
   Root cause: resolve() built its JavacTask by calling 
JavacTool.create().getTask(...) directly, which does not pre-register any of 
NetBeans' javac context enhancers. NetBeans .sig files (the per-root cached 
form of indexed Java classes, written by NBClassWriter) use a relaxed/extended 
class-file format that only NBClassReader knows how to parse correctly -- stock 
javac's ClassReader rejects them with BadClassFile, which propagates as 
CompletionFailure. ElementUtils.getTypeElementByBinaryName silently catches the 
CompletionFailure and returns null, so resolve() falls through to its 
empty-list return path. The exact rejection message varies by which NB-specific 
feature the .sig used; for one observed real-world case the .sig contained a 
ConstantValue attribute on a non-static-final String field, and stock javac 
rejected it with "variable of type 'java.lang.String' cannot have a constant 
value, but has one specified". The boot classpath, java.lang.Object 
loadability, and supertype wal
 king are not involved -- the old code can perfectly well find Object via the 
running JDK's auto-discovered system modules; it just can't read the per-root 
.sig file.
   
   History (verified against the pre-donation NetBeans repo):
   
     - 2015-05-18 (pre-Apache commit 
https://github.com/jtulach/netbeans-releases/commit/0f2c3e3d) 
AsyncJavaSymbolDescriptor.java introduced. resolve() goes through 
JavaSource.create(cpInfo).runUserActionTask(...), which routes via JavacParser 
and pre-registers the full NB javac context (NBClassReader, NBAttr, NBEnter, 
NBMemberEnter, NBResolve, NBClassFinder, NBJavaCompiler, NBClassWriter, etc.). 
Correct.
   
     - 2015-05-19 (pre-Apache commit 
https://github.com/jtulach/netbeans-releases/commit/445add56) "Speed up of 
javac resolution". Replaces the JavaSource/CompilationController call with a 
hand-rolled JavacTool.create().getTask(..., new RootChange(), ...) to skip the 
JavaSource setup overhead. NB context enhancer pre-registration is lost in the 
process; the resulting javac task uses stock ClassReader. This is the 
root-cause regression.
   
     - 2015-07-28 (pre-Apache commit 
https://github.com/jtulach/netbeans-releases/commit/6b7a248a) 
"#253081:NullPointerException at AsyncJavaSymbolDescriptor.resolve". An NPE in 
resolve() is reported in the wild only ~10 weeks after the 2015-05-19 
regression -- consistent with the new code path being immediately broken on at 
least some configurations, although the exact trigger is not derivable from the 
diff alone. The fix wraps the (already-broken) reflective 
ClassReader.packages/classes cleanup hack so it returns Collections.emptyMap() 
instead of null, hiding the NPE without addressing why the reflective cleanup 
failed in the first place.
   
     - 2016-12-12 (pre-Apache commit 
https://github.com/jtulach/netbeans-releases/commit/499db956) "Fixed Go To 
Symbol does not work with new javac". The commit message itself confirms that 
by late 2016 Go to Symbol was known to be broken with the JDK 9 javac. The 
attempted fix only switches the (still-broken) reflective hack from the 
now-non-existent ClassReader.packages/classes fields to a Symtab parameter, but 
still does ClassReader.class.getDeclaredField(...), so on JDK 9+ it remains a 
silent no-op. The underlying defect -- using stock javac with no NB context 
enhancers -- is not addressed, and Go to Symbol stays broken. This is the 
strongest direct evidence in the history that the bug has been live and 
acknowledged since at least 2016.
   
   The defect has been in place for just under eleven years.
   
   The fix replaces the whole hand-rolled construction -- including the 
long-broken reflective ClassReader.packages/classes cleanup hack (a silent 
no-op since those fields moved to Symtab on JDK 9, and the origin of the 
one-time WARNING "packages"/"classes" logged from AsyncJavaSymbolDescriptor at 
every JDK 9+ startup) -- with a single 
JavacParser.createJavacTask(ClasspathInfo.create(root), ...) call, matching the 
idiom JavaBinaryIndexer and ModuleNames.parseModuleName already use. 
JavacParser.createJavacTask routes through the NB context enhancer 
pre-registration path, so the resulting javac task uses NBClassReader and 
friends and can read the per-root .sig files. About 150 lines of dead code and 
the startup WARNINGs go away. See the comments in the file for the 
defense-in-depth fallbacks retained in resolve() and open().
   
   A new regression test, 
JavaSymbolProviderTest.testAsyncDescriptorResolvesForIndexedType, indexes a 
single-class source root whose source is intentionally rich (a lambda, 
generics, a static-final String constant, and a separate annotation type) so 
that the .sig file the indexer writes is empirically rejected by stock javac's 
ClassReader -- a trivial empty class produces a .sig vanilla javac reads 
without trouble, so the richer source is required. The test then asks 
JavaSymbolProvider to look up the class, triggers AsyncJavaSymbolDescriptor's 
async resolve() by calling getSymbolName() on the returned descriptor, and 
asserts that the resulting descriptor-change event carries a non-empty, 
enriched replacement that is not the original AsyncJavaSymbolDescriptor 
instance. It passes against the fix and fails against the pre-fix code path 
(verified end-to-end by reverting AsyncJavaSymbolDescriptor.java only and 
re-running the test). The exact attribute in the test class's .sig that trips s
 tock ClassReader has not been pinned down -- it is enough to know that the 
differential exists and is reliably reproduced.
   
   Also fixes a small, unrelated race in ContentProviderImpl.Worker: 
model.remove and model.add, previously issued as two separate background-thread 
invokeInEDT calls, are now performed inside the existing invokeLater runnable 
and guarded by !isCanceled, so they happen atomically within one EDT turn.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to