Some classes decompiled (via custom `ClassFileDecompilers.Light`) produce `InvalidMirrorException: stub:[PsiMethod:Checker...`
Answered
Hello, I am developing jd-intellij, which is the IDEA integration of JD-Core, which is a java decompiler (you probably heard about JD-GUI).
I identified a case, likely not the only one where IntelliJ IDEA reports an error in the mirror, this does not happen on all classes though.
Here's the reported stacktrace with the `javax.annotations.RegEx` (findbugs jsr305 1.3.9 annotations)
jar:///Users/brice/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/1.3.9/40719ea6961c0cb6afaeb6a921eaa1f6afd4cfdf/jsr305-1.3.9.jar!/javax/annotation/RegEx.class
com.intellij.diagnostic.PluginException: stub:[PsiMethod:Checker, PsiMethod:forConstantValue]; mirror:[PsiMethod:forConstantValue] [Plugin: jd-intellij]
at com.intellij.psi.impl.compiled.ClsFileImpl.wrapException(ClsFileImpl.java:387)
at com.intellij.psi.impl.compiled.ClsFileImpl.getMirror(ClsFileImpl.java:355)
at com.intellij.psi.impl.compiled.ClsFileImpl.getDecompiledPsiFile(ClsFileImpl.java:396)
at com.intellij.codeInsight.folding.impl.FoldingUpdate.getFoldingsFor(FoldingUpdate.java:230)
at com.intellij.codeInsight.folding.impl.CodeFoldingManagerImpl.buildInitialFoldings(CodeFoldingManagerImpl.java:135)
at com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl.loadEditorInBackground(PsiAwareTextEditorImpl.java:48)
at com.intellij.openapi.fileEditor.impl.text.AsyncEditorLoader.lambda$scheduleLoading$0(AsyncEditorLoader.java:97)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:808)
at com.intellij.openapi.fileEditor.impl.text.AsyncEditorLoader.lambda$scheduleLoading$1(AsyncEditorLoader.java:95)
at com.intellij.openapi.application.impl.NonBlockingReadActionImpl$Submission.insideReadAction(NonBlockingReadActionImpl.java:521)
at com.intellij.openapi.application.impl.NonBlockingReadActionImpl$Submission.lambda$attemptComputation$3(NonBlockingReadActionImpl.java:468)
at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1096)
at com.intellij.openapi.progress.util.ProgressIndicatorUtils.lambda$runInReadActionWithWriteActionPriority$0(ProgressIndicatorUtils.java:79)
at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runActionAndCancelBeforeWrite(ProgressIndicatorUtils.java:157)
at com.intellij.openapi.progress.util.ProgressIndicatorUtils.lambda$runWithWriteActionPriority$1(ProgressIndicatorUtils.java:119)
at com.intellij.openapi.progress.ProgressManager.lambda$runProcess$0(ProgressManager.java:57)
at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$runProcess$2(CoreProgressManager.java:178)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:688)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:634)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:64)
at com.intellij.openapi.progress.impl.CoreProgressManager.runProcess(CoreProgressManager.java:165)
at com.intellij.openapi.progress.ProgressManager.runProcess(ProgressManager.java:57)
at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runWithWriteActionPriority(ProgressIndicatorUtils.java:116)
at com.intellij.openapi.progress.util.ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(ProgressIndicatorUtils.java:79)
at com.intellij.openapi.application.impl.NonBlockingReadActionImpl$Submission.attemptComputation(NonBlockingReadActionImpl.java:486)
at com.intellij.openapi.application.impl.NonBlockingReadActionImpl$Submission.lambda$transferToBgThread$1(NonBlockingReadActionImpl.java:408)
at com.intellij.util.concurrency.BoundedTaskExecutor.doRun(BoundedTaskExecutor.java:216)
at com.intellij.util.concurrency.BoundedTaskExecutor.access$200(BoundedTaskExecutor.java:27)
at com.intellij.util.concurrency.BoundedTaskExecutor$1.execute(BoundedTaskExecutor.java:195)
at com.intellij.util.ConcurrencyUtil.runUnderThreadName(ConcurrencyUtil.java:213)
at com.intellij.util.concurrency.BoundedTaskExecutor$1.run(BoundedTaskExecutor.java:184)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:668)
at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:665)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1.run(Executors.java:665)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: com.intellij.psi.impl.compiled.ClsElementImpl$InvalidMirrorException: stub:[PsiMethod:Checker, PsiMethod:forConstantValue]; mirror:[PsiMethod:forConstantValue]
at com.intellij.psi.impl.compiled.ClsElementImpl.setMirrors(ClsElementImpl.java:274)
at com.intellij.psi.impl.compiled.ClsClassImpl.setMirror(ClsClassImpl.java:451)
at com.intellij.psi.impl.compiled.ClsElementImpl.setMirror(ClsElementImpl.java:256)
at com.intellij.psi.impl.compiled.ClsElementImpl.setMirrors(ClsElementImpl.java:277)
at com.intellij.psi.impl.compiled.ClsClassImpl.setMirror(ClsClassImpl.java:452)
at com.intellij.psi.impl.compiled.ClsElementImpl.setMirror(ClsElementImpl.java:256)
at com.intellij.psi.impl.compiled.ClsElementImpl.setMirrors(ClsElementImpl.java:277)
at com.intellij.psi.impl.compiled.ClsElementImpl.setMirrors(ClsElementImpl.java:269)
at com.intellij.psi.impl.compiled.ClsFileImpl.setFileMirror(ClsFileImpl.java:300)
at com.intellij.psi.impl.compiled.ClsFileImpl.lambda$getMirror$1(ClsFileImpl.java:350)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:688)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:634)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:64)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeNonCancelableSection(CoreProgressManager.java:218)
at com.intellij.psi.impl.compiled.ClsFileImpl.getMirror(ClsFileImpl.java:349)
... 36 more
The code produced by the decompiled looks correct, and similar to the original. The stack traces seems to indicate an issue with the `Checker.forConstantValue` method.
/* Location: jar:///Users/brice/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/1.3.9/40719ea6961c0cb6afaeb6a921eaa1f6afd4cfdf/jsr305-1.3.9.jar!/javax/annotation/RegEx.class
* Java language version: 5
* Class File: 49.0
* JD-Core Version: 1.1.3
*/
package javax.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.annotation.meta.TypeQualifierNickname;
import javax.annotation.meta.TypeQualifierValidator;
import javax.annotation.meta.When;
@Documented
@Syntax("RegEx")
@Retention(RetentionPolicy.RUNTIME)
@TypeQualifierNickname
public @interface RegEx
{
When when() default When.ALWAYS;
public static class Checker
implements TypeQualifierValidator<RegEx>
{
public When forConstantValue(RegEx annotation, Object value) {
if (!(value instanceof String)) {
return When.NEVER;
}
try {
Pattern.compile((String)value);
} catch (PatternSyntaxException e) {
return When.NEVER;
}
return When.ALWAYS;
}
}
}
I have seen this invalid mirror error in many occasions. Is there something I can do ?
The source code is here : https://github.com/bric3/jd-intellij
Please sign in to leave a comment.
I have another similar issue when inspecting some local jar files, for example I was able to reproduce the following stack trace, by downloading the spring-jdbc jar and adding it as a local jar dependency, when inspecting
NamedParameterJdbcOperations
this raises a similar error about invalid mirror.implementation(files("lib/spring-jdbc-4.1.6.RELEASE.jar"))
The class produced by the decompiler doesn't have exactly the same set of members as the stub for the same file; one may compare a decompiled class with ClsFileImpl#decompile output to find out the difference.
Yann Cebron Thanks for the feedback, I'll take a look.
I wonder if this might be caused by synthetic members like default constructor, for the RegEx case
But for the classes in spring-jdbc, I can't guess at this time.
Anyway I'll test and report back.
Yann Cebron Indeed I can confirm you that in the case of the RegEx class it's the default constructor.
My decompiler don't output the default (and empty) constructor
ClsFileImpl.decompile however emit this constructor
Actual source code
I don't think I can do something about it, except making the decompiler less smart. Maybe I can open an issue on Youtrack ?
Hi Brice,
the current implementation requires the decompiler to provide the same class outline as the stub builder, and the latter does include default constructors - because to tell a synthesized default constructor from a "real" one, one has to analyze the bytecode, and that's not what the stub builder should do, for the sake of speed of indexing. Oh, and for "real" empty constructors, there's no way at all.
For FernFlower, we had to add an option to it to always generate a default constructor - maybe that's possible for JD?
Hi Roman Shevchenko
Ah thanks for the feedback.
It was possible to remove default constructors in an earlier version, but now it has been removed after a huge refactoring, and it bring the decompiled source closer to the original source code. For now I'll have to live with that, unless there are other implications besides these error reports. I'll see how/if this can be re-added in jd-core.
Isn't synthetic "merely" a modifier. That would solve some of these issues I think.
`ACC_SYNTHETIC` is a modifier, but Javac doesn't set it for default constructors, unfortunately.
Ah yes of course, I forgot this quirk.
But surely there's a way, default constructor's bytecodes are usually very simple. They are no-arg (except `this`) and they "only" invoke the superclass constructor (`<init>`). The stub decompiler could only verify the bytecodes if the number of args is `1` (this) and if the stack size is size is `1`, if not then it's unlikely to be default constructor.
Skipping some javap output (the constant pool in particular, as javap resolves reference in comments).
It is definitely possible, but would significantly complicate the code (*) and slow indexing down to a certain degree - all without providing clear benefits. Even then, it won't be 100% correct - the bytecode of a generated constructor and a user-written empty one is identical.
(*) by the time `MethodVisitor::visitCode` is called, the decision on whether to skip the method has long been made
OK I cannot guess such a change implies on the code base and the performance impact. I understand the constraint and your point of view on the matter.
About the benefit, from my point of view it would allow to have a decompiled source closer to the original (default constructor are almost never written explicitly). But what I actually try to solve are the repetitive InvalidMirrorException errors. So that's the benif I'm really looking for.
Since they are reported, I don't quite measure the impact within the IDE of this invalid mirror. Maybe it's just reporting differences with the stub decompiler, maybe it has other implications.
Imagining things, could it make sense that on InvalidMirrorException the IDE calls back the decompiler plugin so that it has a chance to act on the invalid mirror like back feeding the missing constructor programmatically ? While maintaining the decompiled text in the editor.
By the way thank you Yann Cebron and Roman Shevchenko for spending time on this exchange ! It's really appreciated !
I understand your position, but being on both sides (I occasionally maintain the bundled decompiler), this problem is much easier to address on the decompiler side. Routing InvalidMirrorException back into the decompiler is probably possible, but it would make both the API and implementations too cumbersome.
The only real opportunity on the IDE side I can think of is making the stub/decompiled text pairing tolerant to missing default constructor.
Roman Shevchenko
Hi, sorry for the late reply, OK I understand your position as well, indeed thinking again on this certainly makes the API convoluted.
That would be awesome.
Thank you again for taking the time to exchange on this topic it is very appreciated !