Status Update
Comments
gh...@google.com <gh...@google.com> #2
Is there another exception logged earlier on? This looks like an exception was thrown from a class initializer at some point, but we need to original exception to debug it.
gh...@google.com <gh...@google.com> #4
Is it possible to run the build such that stderr
from each task is logged? Or is it doing that already?
au...@google.com <au...@google.com> #5
au...@google.com <au...@google.com> #6
Did we land anything big in Lint between these two versions?
js...@google.com <js...@google.com> #7
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.jetbrains.uast.UastFacade
at org.jetbrains.uast.UastContext.<init>(UastContext.kt:30)
at com.android.tools.lint.Fe10UastEnvironmentKt.configureFe10ProjectEnvironment(Fe10UastEnvironment.kt:248)
at com.android.tools.lint.Fe10UastEnvironmentKt.createKotlinCompilerEnv(Fe10UastEnvironment.kt:237)
at com.android.tools.lint.Fe10UastEnvironmentKt.access$createKotlinCompilerEnv(Fe10UastEnvironment.kt:1)
at com.android.tools.lint.Fe10UastEnvironment$Companion.create(Fe10UastEnvironment.kt:199)
at com.android.tools.lint.UastEnvironment$Companion.create(UastEnvironment.kt:132)
at com.android.tools.lint.LintCliClient.initializeProjects(LintCliClient.kt:1499)
at com.android.tools.lint.client.api.LintClient.performInitializeProjects$lint_api(LintClient.kt:1039)
at com.android.tools.lint.client.api.LintDriver.initializeProjectRoots(LintDriver.kt:589)
at com.android.tools.lint.client.api.LintDriver.doAnalyze(LintDriver.kt:502)
at com.android.tools.lint.client.api.LintDriver.analyzeOnly(LintDriver.kt:453)
at com.android.tools.lint.LintCliClient$analyzeOnly$1.invoke(LintCliClient.kt:250)
at com.android.tools.lint.LintCliClient$analyzeOnly$1.invoke(LintCliClient.kt:250)
at com.android.tools.lint.LintCliClient.run(LintCliClient.kt:292)
at com.android.tools.lint.LintCliClient.run$default(LintCliClient.kt:275)
at com.android.tools.lint.LintCliClient.analyzeOnly(LintCliClient.kt:250)
at com.android.tools.lint.Main.run(Main.java:1673)
at com.android.tools.lint.Main.run(Main.java:279)
... 34 more
Note that it starts from analyzeOnly
(if it matters).
As per DriverMode.MERGE
I guess, while the error starts from analyzeOnly
. :confused:
gh...@google.com <gh...@google.com>
au...@google.com <au...@google.com> #9
This is going to block androidx from upgrading AGP, it would be awesome if someone investigated this. It is definitely something that changed between the two versions.
gh...@google.com <gh...@google.com> #10
But, it's actually filtering out for DriverMode.MERGE I guess, while the error starts from analyzeOnly. :confused:
IIUC the NoClassDefFoundError
means that a previous attempt at class initialization failed. So it is possible that the exception originally occurred during mergeOnly
, not analyzeOnly
. (And, somehow we are missing the original exception in the logs, which makes this hard to debug.)
I only have one theory so far. I noticed that UastFacade
has a static class initializer that does this:
init {
UastLanguagePlugin.extensionPointName.addChangeListener(...);
}
In particular, addChangeListener
queries Extensions.getRootArea()
and assumes the result is non-null. However, the result could be null if the Application environment is not initialized. In that case, an NPE would be thrown from UastFacade.<clinit>
and thus any subsequent accesses to UastFacade
would throw NoClassDefFoundError
.
If the theory is correct, then it implies that at some point during LintDriver.mergeOnly()
we accidentally trigger class initialization for UastFacade
.
This is only a theory for now. I'll continue digging.
gh...@google.com <gh...@google.com> #11
Hm, I wonder if the original exception is being swallowed in LintDriver.doAnalyze
here:
try {
analysis(roots)
} catch (throwable: Throwable) {
handleDetectorError(null, this, throwable)
}
(this catch-block reports most exceptions as a special Lint incident in the Lint report—which is unhelpful when the final Lint report task is broken).
@aurimas can you set either the Java system property lint.print-stacktrace=true
and the environment variable LINT_PRINT_STACKTRACE=true
? That should force the original exception to be logged to stderr.
au...@google.com <au...@google.com> #12
On Wed, Aug 10, 2022, 7:34 PM gharrma <buganizer-system+gharrma@google.com>
wrote:
gh...@google.com <gh...@google.com> #13
We already set this env variable.
I was afraid you'd say that (but thanks for confirming).
I'll keep digging tomorrow.
gh...@google.com <gh...@google.com> #14
OK, the root cause turned out to be quite interesting.
Root cause:
-
During a
lintReport
task, Lint still loads custom Lint checks and runsLintJarVerifier
on them to verify binary compatibility with the Lint API. To verify binary compatibility,LintJarVerifier
scans Lint-check bytecode and makes sure that all class/method/field references would resolve correctly in the current classloader. E.g., to verify a class reference, Lint looks up the class viaClass.forName(className)
. -
Unfortunately,
Class.forName(className)
also initializes the given class by default. So, if a custom Lint check referencesUastFacade
, then the static initializer forUastFacade
will run at this point. -
As predicted in comment
, if the static initializer forcomment#10 UastFacade
runs when the UAST environment is not initialized, then an NPE is thrown. This scenario is possible after the recent changehttp://ag/19152690 which disables UAST initialization forlintReport
tasks. -
The NPE causes an
ExceptionInInitializerError
, and thus all subsequent accesses toUastFacade
in the same Gradle daemon throwNoClassDefFoundError
. Also, the originalExceptionInInitializerError
is never logged to stderr is because it is swallowed byLintJarVerifier
in one of its catch-Throwable blocks. -
The reason the issue is flaky is because it requires that some
lintReport
task executes before any otherlintAnalyze
task in a given Gradle daemon.
Repro steps:
-
Make sure the project is using a custom Lint check which references the
UastFacade
class. One example isUnsafeFragmentLifecycleObserverDetector
from the libraryandroidx.fragment:fragment
. -
Run a series of Gradle commands such that some
lintReport
task runs before any otherlintAnalyze
tasks within a given Gradle daemon. Example:./gradlew clean ./gradlew :app:lintAnalyzeDebug ./gradlew --stop ./gradlew :app:lintReportDebug # <edit some source file so that Lint is no longer up-to-date> ./gradlew :app:lintAnalyzeDebug
-
This results in the
:app:lintAnalyzeDebug
task failing withNoClassDefFoundError
.
Next steps:
I'll change LintJarVerifier
so that it no longer triggers class initialization. This should fix the issue. If it does not—and there are other places where the UastFacade
static initializer can be triggered during a lintReport
task—then I think we should revert
tn...@google.com <tn...@google.com> #15
Wow, that's super interesting, nice analysis!
So I'm assuming you're replacing Class.forName(className)
with Class.forName(className, false, this.javaClass.classLoader)
? That seems like a good idea. I guess this would technically also catch cases where lint issue registries referenced classes that had errors (e.g. referencing some other class that hasn't been properly packaged) but that's probably fine; those codepaths would be touched eventually when the lint check runs -- and the goal here is to see whether you're referencing a lint API that doesn't exist due to version mismatches, and those shouldn't have initialization errors.
gh...@google.com <gh...@google.com> #16
So I'm assuming you're replacing Class.forName(className) with Class.forName(className, false, this.javaClass.classLoader) ?
Yup! That's the plan.
tn...@google.com <tn...@google.com> #17
(I realized afterwards that my comment about checking the bytecode for the lint check isn't important -- because here we're talking about the bytecode verifier loading the platform APIs that are referenced. When the JarFileIssueRegistry runs, it will initialize the classes in the jar file itself, so those errors should be surfaced either way.)
gh...@google.com <gh...@google.com> #18
This should be fixed by
p.s. I still consider it a bug that UastFacade
has a static initializer which can fail depending on some global state, so I may look into that some more too.
au...@google.com <au...@google.com> #19
Thanks you for the quick fix!
Description
After upgrading from AGP 7.4.0-alpha08 to 7.4.0-alpha09 android lintAnalyze tasks started flakily failing.