Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

search for references from IClassFile without source #1632

Merged
merged 1 commit into from
Feb 17, 2021

Conversation

snjeza
Copy link
Contributor

@snjeza snjeza commented Dec 15, 2020

@testforstephen
Copy link
Contributor

@snjeza Would you mind sharing the specific root cause to help us understand this fix?

@snjeza
Copy link
Contributor Author

snjeza commented Dec 16, 2020

@snjeza Would you mind sharing the specific root cause to help us understand this fix?

There are several issues:

  1. JDTUtils.findElementAtSelection(...) can't find an element in a class file
  2. ReferencesHandler.findReferences(...) doesn't define the location of an element in a class file.
    A similar issue also exists in the NavigateToDefinitionHandler class. If you call the "Go To Definition" action for an element without a source, Java LS will always open a class file at the (0,0). location.
    I will create a separate issue for this.
  3. Sometimes, Eclipse doesn't find all references in binary files without a source, but that is an upstream issue.

The PR improves both JDTUtils.findElementAtSelection() and ReferencesHandler.findReferences

@rgrunber
Copy link
Contributor

rgrunber commented Jan 4, 2021

Not the first time we've had issues with finding references (eg. https://github.com/eclipse/eclipse.jdt.ls/pull/1561/files#r501326693)

@rgrunber
Copy link
Contributor

rgrunber commented Jan 5, 2021

Some initial thoughts.

The code in ReferencesHandler seems to resolve the issue with finding references, so I'll take a closer look there. Calling AST parsing on the classfile contents of every match could be intensive but I don't see a way around it. Would this still work with setResolveBindings/setBindingsRecovery set to false ?

The additions to JDTUtils#findElementsAtSelection(..) seem to produce better resolutions (eg. hovers) for various tokens, which aren't that informative (in the example i'm using), but better than nothing. Instead of the search engine just looking for type declarations in classfiles, it can now find other types of elements as well.

Copy link
Contributor

@rgrunber rgrunber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, looks good to me. Just a small refactoring of the visit(..) boolean check, and maybe avoiding binding resolution if possible.

@snjeza snjeza force-pushed the issue-1665 branch 2 times, most recently from 3558bc6 to 9ce4d13 Compare January 14, 2021 00:00
@snjeza
Copy link
Contributor Author

snjeza commented Jan 14, 2021

test this please

@snjeza
Copy link
Contributor Author

snjeza commented Jan 14, 2021

@rgrunber I have updated the PR.

@rgrunber
Copy link
Contributor

Great! @testforstephen , let me know if there's anything else you'd like to address here. I'll likely be merging this before the week is up.

@testforstephen
Copy link
Contributor

Performance is my concern. Based on the results of profiling the code action handler I did before, findElementsAtSelection is not a cheap operation. Since code action handler is a high frequency operation, I don't want the new modifications to cause it to slow down.

Let me take a look at the performance side.

Copy link
Contributor

@testforstephen testforstephen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in ReferencesHandler seems to resolve the issue with finding references, so I'll take a closer look there. Calling AST parsing on the classfile contents of every match could be intensive but I don't see a way around it. Would this still work with setResolveBindings/setBindingsRecovery set to false ?

Yes, AST parser is expensive. An idea is to add a preference to control whether to include the decompiled sources when finding references.

The additions to JDTUtils#findElementsAtSelection(..) seem to produce better resolutions (eg. hovers) for various tokens, which aren't that informative (in the example i'm using), but better than nothing. Instead of the search engine just looking for type declarations in classfiles, it can now find other types of elements as well.

The search engine will look up for more pattern, it will take more time to return. But i don't see it will affect the code action performance yet. Because SourceAssistProcessor.java will preferentially use the unit.codeSelect branch of findElementsAtSelection method to find the currently selected element.

However, this pull request didn't fix the issue you mentioned at Issue with 'Go To Definition' #1634. 'Go to definition' still stops at 0,0 of the disassembled source contents of the classfile. We need use the same logic to convert position to the location of it's disassembled source. I'm ok to fix go to definition by using a new PR.

go-to-definition.mov

@testforstephen testforstephen changed the title search for enum reference without source search for references from IClassFile without source Jan 15, 2021
@snjeza
Copy link
Contributor Author

snjeza commented Feb 1, 2021

@testforstephen @rgrunber I have updated the PR.

return location;
}

public static ICompilationUnit getWorkingCopy(IClassFile classFile, String contents, IProgressMonitor monitor) throws JavaModelException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not to use ITypeRoot.getWorkingCopy() method directly? What's the difference between this one and ITypeRoot.getWorkingCopy()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We associate a decompiled source with a class file and configure a ICompilationUnit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in your new findElementAtSelection() method, you're using ClassFile.getWorkingCopy() to attach a decompile source, it looks more concise to me.

ICompilationUnit workingCopy = classFile.getWorkingCopy(new WorkingCopyOwner() { }, monitor);
workingCopy.getBuffer().setContents(contents);
workingCopy.becomeWorkingCopy(monitor);
workingCopy.makeConsistent(monitor);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

try {
if (monitor.isCanceled()) {
return cancelled(res);
}
IJavaElement[] elements = JDTUtils.findElementsAtSelection(unit, line, column, this.preferenceManager, monitor);
ITypeRoot typeRoot = unit;
if (unit instanceof IClassFile && preferenceManager.getPreferences().isIncludeDecompiledSources()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would add check to bypass those class file with source jar attached to avoid creating workingCopy for them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment on lines 943 to 950
String name = contents.substring(region.getOffset(), region.getOffset() + region.getLength());
List<IJavaElement> elements = search(unit, name, element, preferenceManager, monitor);
if (monitor != null && monitor.isCanceled()) {
return null;
}
if (elements.isEmpty()) {
elements = search(unit.getJavaProject(), name, element, preferenceManager, monitor);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The codeSelect above already returns a JavaElement, it it possible to make other places to use this result directly? I didn't get the point to use search engine to search the element again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The codeselect finds a java element in a ICompilationUnit we have created using JDTUtils.getWorkingCopy(). We can't return a java element from ICompilationUnit which is created temporarily and destroyed. We have to search a class file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't return a java element from ICompilationUnit which is created temporarily and destroyed.

If this is the only concern, then you can have the outermost caller to create the workingcopy, and destroy the workingcopy until the LSP response returns.

We need to search three times to find the currently selected element, which seems a bit too complicated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the PR. Could, you, please, review it.

@snjeza snjeza force-pushed the issue-1665 branch 3 times, most recently from 34aa411 to a7ba70d Compare February 6, 2021 18:34
@snjeza snjeza removed the in progress label Feb 6, 2021
@snjeza
Copy link
Contributor Author

snjeza commented Feb 6, 2021

@rgrunber @testforstephen I have updated the PR. It also fixes #1634.

Copy link
Contributor

@testforstephen testforstephen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried the latest commit, it works better than before. But i find it's not pretty stable with go-to-definition. For example, in the decompiled StringUtils library, sometimes go to definition can jump to the right position of the decompiled source, sometimes not.

gtd.mov

BTW, i will be off for two weeks. @rgrunber, you can merge it if you test it to be OK.

*
* @param uri
* @param returnCompilationUnit
* @param monitor2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

monitor2 -> monitor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}
return resolveCompilationUnit(uri);
}

public static void discardWorkingCopy(ITypeRoot unit) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about naming it as discardClassFileWorkingCopy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -937,6 +1100,20 @@ public static IJavaSearchScope createSearchScope(IJavaProject project, Preferenc
return SearchEngine.createJavaSearchScope(elements, scope);
}

public static IJavaSearchScope createSearchScope(IJavaElement element, PreferenceManager preferenceManager) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just searched the code and this new helper method is unused. You can remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed it.

Comment on lines 1595 to 1596
ICompilationUnit workingCopy = null;
workingCopy = getWorkingCopy(classFile, contents, monitor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge them to one line.

ICompilationUnit workingCopy = getWorkingCopy(classFile, contents, monitor);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@snjeza
Copy link
Contributor Author

snjeza commented Feb 7, 2021

I tried the latest commit, it works better than before. But i find it's not pretty stable with go-to-definition. For example, in the decompiled StringUtils library, sometimes go to definition can jump to the right position of the decompiled source, sometimes not.

I have updated the PR.
Sometimes a decompiler throws an exception like the following:

java.lang.NullPointerException
	at org.jetbrains.java.decompiler.util.VBStyleCollection.removeWithKey(VBStyleCollection.java:84)
	at org.jetbrains.java.decompiler.modules.decompiler.stats.Statement.collapseNodesToStatement(Statement.java:209)
	at org.jetbrains.java.decompiler.modules.decompiler.DomHelper.findSimpleStatements(DomHelper.java:600)
	at org.jetbrains.java.decompiler.modules.decompiler.DomHelper.processStatement(DomHelper.java:356)
	at org.jetbrains.java.decompiler.modules.decompiler.DomHelper.processStatement(DomHelper.java:305)
	at org.jetbrains.java.decompiler.modules.decompiler.DomHelper.parseGraph(DomHelper.java:196)
	at org.jetbrains.java.decompiler.main.rels.MethodProcessorRunnable.codeToJava(MethodProcessorRunnable.java:101)
	at org.jetbrains.java.decompiler.main.rels.ClassWrapper.init(ClassWrapper.java:62)
	at org.jetbrains.java.decompiler.main.ClassesProcessor.initWrappers(ClassesProcessor.java:280)
	at org.jetbrains.java.decompiler.main.ClassesProcessor.writeClass(ClassesProcessor.java:227)
	at org.jetbrains.java.decompiler.main.Fernflower.getClassContent(Fernflower.java:90)
	at org.jetbrains.java.decompiler.struct.ContextUnit.save(ContextUnit.java:97)
	at org.jetbrains.java.decompiler.struct.StructContext.saveContext(StructContext.java:58)
	at org.jetbrains.java.decompiler.main.Fernflower.decompileContext(Fernflower.java:47)
	at org.jetbrains.java.decompiler.main.decompiler.BaseDecompiler.decompileContext(BaseDecompiler.java:26)
	at dg.jdt.ls.decompiler.fernflower.FernflowerDecompiler.getContent(FernflowerDecompiler.java:82)
	at dg.jdt.ls.decompiler.fernflower.FernflowerDecompiler.decompileContent(FernflowerDecompiler.java:73)
	at dg.jdt.ls.decompiler.common.CachingDecompiler.getSource(CachingDecompiler.java:47)
	at org.eclipse.jdt.ls.core.internal.managers.ContentProviderManager.getContent(ContentProviderManager.java:112)
	at org.eclipse.jdt.ls.core.internal.managers.ContentProviderManager.getSource(ContentProviderManager.java:65)
	at org.eclipse.jdt.ls.core.internal.JDTUtils.searchDecompiledSources(JDTUtils.java:1577)
	at org.eclipse.jdt.ls.core.internal.handlers.NavigateToDefinitionHandler.computeDefinitionNavigation(NavigateToDefinitionHandler.java:187)
	at org.eclipse.jdt.ls.core.internal.handlers.NavigateToDefinitionHandler.computeDefinitionNavigation(NavigateToDefinitionHandler.java:90)
	at org.eclipse.jdt.ls.core.internal.handlers.NavigateToDefinitionHandler.definition(NavigateToDefinitionHandler.java:73)
	at org.eclipse.jdt.ls.core.internal.handlers.JDTLanguageServer.lambda$8(JDTLanguageServer.java:574)
	at org.eclipse.jdt.ls.core.internal.BaseJDTLanguageServer.lambda$0(BaseJDTLanguageServer.java:75)
	at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:642)
	at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:479)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)

Could you try again?

@rgrunber
Copy link
Contributor

rgrunber commented Feb 9, 2021

I think this is working pretty well. The case I've been testing from before seem to be working. Occasionally I got the following stacktrace when selecting the StringWriter type, from line 291 in Gson.class, from the eclipse-sample project :

null
org.eclipse.jface.text.BadLocationException
    at org.eclipse.jface.text.ListLineTracker.getLineOffset(ListLineTracker.java:197)
    at org.eclipse.jface.text.AbstractLineTracker.getLineOffset(AbstractLineTracker.java:161)
    at org.eclipse.jface.text.AbstractDocument.getLineOffset(AbstractDocument.java:877)
    at org.eclipse.jdt.ls.core.internal.handlers.JsonRpcHelpers.toOffset(JsonRpcHelpers.java:85)
    at org.eclipse.jdt.ls.core.internal.handlers.JsonRpcHelpers.toOffset(JsonRpcHelpers.java:69)
    at org.eclipse.jdt.ls.core.internal.handlers.NavigateToDefinitionHandler.computeBreakContinue(NavigateToDefinitionHandler.java:99)
    at org.eclipse.jdt.ls.core.internal.handlers.NavigateToDefinitionHandler.computeDefinitionNavigation(NavigateToDefinitionHandler.java:88)
    at org.eclipse.jdt.ls.core.internal.handlers.NavigateToDefinitionHandler.definition(NavigateToDefinitionHandler.java:73)
    at org.eclipse.jdt.ls.core.internal.handlers.JDTLanguageServer.lambda$8(JDTLanguageServer.java:574)
    at org.eclipse.jdt.ls.core.internal.BaseJDTLanguageServer.lambda$0(BaseJDTLanguageServer.java:75)
    at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:642)
    at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:479)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1016)
    at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1665)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1598)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)

It seemed to only happen the first few attempts.

Otherwise, I'll probably have a look at the new additions, but I think this is in better shape.

@snjeza
Copy link
Contributor Author

snjeza commented Feb 9, 2021

I think this is working pretty well. The case I've been testing from before seem to be working. Occasionally I got the following stacktrace when selecting the StringWriter type, from line 291 in Gson.class, from the eclipse-sample project :

@rgrunber It is an issue related to the fernflower decompiler. Could you, please, test the latest PR? - aec27b7

Copy link
Contributor

@rgrunber rgrunber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this change works. I think it's in better shape than before. I still had some cases where go-to-definition would fail the first or 2nd time (eg. From within Gson.class on StringWriter with the java sources installed, "No definition found") but maybe we can address these issues separately as this patch is getting large.

}
return resolveCompilationUnit(uri);
}

public static void discardClassFileWorkingCopy(ITypeRoot unit) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think unit is ever a ClassFileWorkingCopy for me. Is there a specific case where this should happen ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right. Fixed.

Copy link
Contributor

@rgrunber rgrunber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should prepare to merge this after the 0.75.0 release just so that we at least have weeks leading up to 0.76.0 to fix up any regressions that may come up.

I'll still try a few of the use-cases and maybe look at the code more thoroughly, and make suggestions. However, to me the functionality seems pretty solid now, and I think merging + fixing any subsequent issues is the best option.

@snjeza
Copy link
Contributor Author

snjeza commented Feb 14, 2021

I still had some cases where go-to-definition would fail the first or 2nd time (eg. From within Gson.class on StringWriter with the java sources installed, "No definition found") but maybe we can address these issues separately as this patch is getting large.

@rgrunber The issue you have faced is an upstream fernflower issue.
I have updated fernflower.jar to the latest version. You can check it at https://github.com/snjeza/vscode-test/raw/master/java-decompiler-0.0.3.vsix.

@rgrunber
Copy link
Contributor

I still had some cases where go-to-definition would fail the first or 2nd time (eg. From within Gson.class on StringWriter with the java sources installed, "No definition found") but maybe we can address these issues separately as this patch is getting large.

@rgrunber The issue you have faced is an upstream fernflower issue.
I have updated fernflower.jar to the latest version. You can check it at https://github.com/snjeza/vscode-test/raw/master/java-decompiler-0.0.3.vsix.

If that's the case, then let's merge this along with its dependent change and we can address any additional issues that might arise in separate changes.

@snjeza snjeza merged commit e350670 into eclipse-jdtls:master Feb 17, 2021
@testforstephen
Copy link
Contributor

The issue you have faced is an upstream fernflower issue.
I have updated fernflower.jar to the latest version. You can check it at https://github.com/snjeza/vscode-test/raw/master/java-decompiler-0.0.3.vsix.

@snjeza since you have figured out the cause of this failure, how about sending a PR to Java Decompiler extension?https://github.com/dgileadi/dg.jdt.ls.decompiler/tree/master/dg.jdt.ls.decompiler.fernflower/lib

@snjeza
Copy link
Contributor Author

snjeza commented Feb 22, 2021

since you have figured out the cause of this failure, how about sending a PR to Java Decompiler extension?https://github.com/dgileadi/dg.jdt.ls.decompiler/tree/master/dg.jdt.ls.decompiler.fernflower/lib

I will try.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Issue with 'Go To Definition' search for enum reference without source
4 participants