Annotate lambdas capturing newer-framework classes
b/391766151 is caused by the combination of three things :
• Newer compilers, such as the Kotlin K2 compiler, will use the
`invokedynamic` instruction introduced in Java 7.
→ Before then, lambdas had to be wrapped in synthetic classes,
which would be generated by the compiler. The compiler would
then generate code that instantiates the synthetic class and
pass it to the callee as the lambda.
→ After then, there is no need for synthetic classes. Instead, the
compiler synthesizes a static method that corresponds to the
lambda and emits an invokedynamic opcode at that call site.
The JVM will then lookup the class dynamically and create a
MethodHandle object from which it can substitute an immediate
method call at runtime.
→ The new way means a much smaller footprint (because
there are less classes in bytecode) and typically much better
call performance after the initial resolution, at the cost of linking
the method at the first execution of the lambda. For most
modern apps, the trade-off is very advantageous.
→ In this case, the methods that accept the lambda are of the
`runAsShell` family.
→ Because there are captured local variables of the *WaiterCallback
types, the generated static methods take instances of these types.
• JUnit uses reflection to enumerate the methods of test classes.
It does not take care to only enumerate public methods, and as
such will try to resolve all private methods including the types of
their arguments.
→ Note that the `Class` class has a way to retrieve all methods
of public visibility including inherited methods, a way to
retrieve all non-inherited methods including all visibilities, but
not a way to retrieve only public locally-declared methods, so
JUnit's implementation is understandable.
→ In this case, this means JUnit has code that will try to resolve
the *WaiterCallback classes.
• The test code is compiled against newer SDKs but run with the
JUnit test harness on older SDKs which don't have the base classes
for the *WaiterCallback classes.
The net result is that as JUnit enumerates methods, it finds the
private synthetic methods emitted by the newer compilers, which
causes the JVM to try and resolve the type of the arguments, which
in turn is not possible on older frameworks. The test run promptly
crashes with ClassNotFoundException before it can run any test
method.
Note that this would also happen with newer Java compilers.
This patch solves this method by annotating the relevant
lambdas with @JvmSerializableLambda, which causes the
Kotlin compiler to use the old behavior on just those lambdas.
The drawback of this patch is that the @JvmSerializableLambda
feature is pretty obscure and it needs to be managed per-lambda.
Bug: 391766151
Test: revert aosp/3464183 on top of this, then atest NetworkAgentTest
on an S device, then atest CtsNetTestCases to test whether there
are other instances of this issue
Change-Id: Iacaadddb597c4a5dfdd8c497e6ebc1243ae1e923
1 file changed