Re: Unexpected data flow error with `CheckClassAdapter`

"ebruneton" ([email protected]) <[email protected]> Thu, 20 Apr 2023 19:36:16 +0200
Newsgroups gmane.comp.java.objectweb.asm
Message-ID <[email protected]>
This is a multi-part message in MIME format...

------------=_1682012203-21111-31
Content-Type: text/plain; charset=UTF-8;
 format=flowed
Content-Transfer-Encoding: 8bit

Hi,

Thanks for your report. This is a bug in ASM, which will be fixed by 
https://gitlab.ow2.org/asm/asm/-/merge_requests/374.

Eric

Le 18/04/2023 18:13, "Pascal Kesseli" a écrit :
> Hi everyone,
> 
> I tried to enable `CheckClassAdapter` with `checkDataFlow` set to 
> `true` to
> make sure that my instrumentation is valid. However, I encountered an 
> issue
> that even original classes from third party libraries are considered 
> invalid.
> As an example the method `getCanonicalName` in the class 
> `AssertionUtils` in
> JUnit 5.9.2:
> 
> ```pom.xml
> <dependency>
> 	<groupId>org.ow2.asm</groupId>
> 	<artifactId>asm-util</artifactId>
> 	<version>9.5</version>
> </dependency>
> <dependency>
> 	<groupId>org.junit.jupiter</groupId>
> 	<artifactId>junit-jupiter-engine</artifactId>
> 	<version>5.9.2</version>
> 	<scope>test</scope>
> </dependency>
> ```
> 
> ```java
> @Test
> void assertionUtils() throws Exception {
> 	String classRes = "/org/junit/jupiter/api/AssertionUtils.class";
> 	byte[] classfileBuffer;
> 	try (InputStream is = MyTest.class.getResourceAsStream(classRes)) {
> 	classfileBuffer = IOUtils.toByteArray(is);
> 	}
> 
> 	ClassReader reader = new ClassReader(classfileBuffer);
> 	TraceClassVisitor trace = new TraceClassVisitor(null, new Textifier(),
> new PrintWriter(System.err));
> 	reader.accept(trace, 0);
> 
> 	reader = new ClassReader(classfileBuffer);
> 	ClassWriter writer = new ClassWriter(reader, 0);
> 	CheckClassAdapter checker = new CheckClassAdapter(writer, true);
> 	try {
> 		reader.accept(checker, 0);
> 	} catch (IllegalArgumentException e) {
> 		e.printStackTrace();
> 	}
> 
> 	Class.forName("org.junit.jupiter.api.AssertionUtils");
> }
> ```
> 
> This unit test passes the byte content for `AssertionUtils` without
> instrumentation or modification into a `ClassReader`, outputs the byte 
> code
> using `TraceClassVisitor` and then independently runs it through
> `CheckClassAdapter`. The output of this test is as follows:
> 
> ```
> // `TraceClassVisitor` output
> // class version 52.0 (52)
> // access flags 0x20
> class org/junit/jupiter/api/AssertionUtils {
> // ...
>   // access flags 0x8
>   // signature (Ljava/lang/Class<*>;)Ljava/lang/String;
>   // declaration: java.lang.String getCanonicalName(java.lang.Class<?>)
>   static getCanonicalName(Ljava/lang/Class;)Ljava/lang/String;
>     TRYCATCHBLOCK L0 L1 L2 java/lang/Throwable
>    L0
>     LINENUMBER 59 L0
>     ALOAD 0
>     INVOKEVIRTUAL java/lang/Class.getCanonicalName ()Ljava/lang/String;
>     ASTORE 1
>    L3
>     LINENUMBER 60 L3
>     ALOAD 1
>     IFNULL L4
>     ALOAD 1
>     GOTO L1
>    L4
>    FRAME APPEND [java/lang/String]
>     ALOAD 0
>     INVOKEVIRTUAL java/lang/Class.getName ()Ljava/lang/String;
>    L1
>    FRAME SAME1 java/lang/String
>     ARETURN
>    L2
>     LINENUMBER 62 L2
>    FRAME FULL [java/lang/Class] [java/lang/Throwable]
>     ASTORE 1
>    L5
>     LINENUMBER 63 L5
>     ALOAD 1
>     INVOKESTATIC org/junit/platform/commons/util/
> UnrecoverableExceptions.rethrowIfUnrecoverable (Ljava/lang/Throwable;)V
>    L6
>     LINENUMBER 64 L6
>     ALOAD 0
>     INVOKEVIRTUAL java/lang/Class.getName ()Ljava/lang/String;
>     ARETURN
>    L7
>     LOCALVARIABLE canonicalName Ljava/lang/String; L3 L2 1
>     LOCALVARIABLE t Ljava/lang/Throwable; L5 L7 1
>     LOCALVARIABLE clazz Ljava/lang/Class; L0 L7 0
>     // signature Ljava/lang/Class<*>;
>     // declaration: clazz extends java.lang.Class<?>
>     MAXSTACK = 1
>     MAXLOCALS = 2
> // ...
> }
> // `e.printStackTrace();` for `IllegalArgumentException`
> java.lang.IllegalArgumentException: Error at instruction 12: Cannot 
> append
> more locals than maxLocals 
> getCanonicalName(Ljava/lang/Class;)Ljava/lang/
> String;
> 00000 R .  :  :    L0
> 00001 ?    :     LINENUMBER 59 L0
> 00002 ?    :     ALOAD 0
> 00003 ?    :     INVOKEVIRTUAL java/lang/Class.getCanonicalName 
> ()Ljava/lang/
> String;
> 00004 ?    :     ASTORE 1
> 00005 ?    :    L1
> 00006 ?    :     LINENUMBER 60 L1
> 00007 ?    :     ALOAD 1
> 00008 ?    :     IFNULL L2
> 00009 ?    :     ALOAD 1
> 00010 ?    :     GOTO L3
> 00011 ?    :    L2
> 00012 ?    :    FRAME APPEND [java/lang/String]
> 00013 ?    :     ALOAD 0
> 00014 ?    :     INVOKEVIRTUAL java/lang/Class.getName 
> ()Ljava/lang/String;
> 00015 ?    :    L3
> 00016 ?    :    FRAME SAME1 java/lang/String
> 00017 ?    :     ARETURN
> 00018 ?    :    L4
> 00019 ?    :     LINENUMBER 62 L4
> 00020 ?    :    FRAME FULL [java/lang/Class] [java/lang/Throwable]
> 00021 ?    :     ASTORE 1
> 00022 ?    :    L5
> 00023 ?    :     LINENUMBER 63 L5
> 00024 ?    :     ALOAD 1
> 00025 ?    :     INVOKESTATIC org/junit/platform/commons/util/
> UnrecoverableExceptions.rethrowIfUnrecoverable (Ljava/lang/Throwable;)V
> 00026 ?    :    L6
> 00027 ?    :     LINENUMBER 64 L6
> 00028 ?    :     ALOAD 0
> 00029 ?    :     INVOKEVIRTUAL java/lang/Class.getName 
> ()Ljava/lang/String;
> 00030 ?    :     ARETURN
> 00031 ?    :    L7
>      TRYCATCHBLOCK L0 L3 L4 java/lang/Throwable
> 
> 	at org.objectweb.asm.util.CheckMethodAdapter
> $1.throwError(CheckMethodAdapter.java:489)
> 	at org.objectweb.asm.util.CheckMethodAdapter
> $1.visitEnd(CheckMethodAdapter.java:475)
> 	at org.objectweb.asm.MethodVisitor.visitEnd(MethodVisitor.java:796)
> 	at
> org.objectweb.asm.util.CheckMethodAdapter.visitEnd(CheckMethodAdapter.java:1044)
> 	at org.objectweb.asm.ClassReader.readMethod(ClassReader.java:1518)
> 	at org.objectweb.asm.ClassReader.accept(ClassReader.java:744)
> 	at org.objectweb.asm.ClassReader.accept(ClassReader.java:424)
> 	at
> net.lacework.caa.java.instrument.TracepointClassFileTransformerTest.assertionUtils(TracepointClassFileTransformerTest.java:98)
> 	at java.base/
> jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
> 	at java.base/java.lang.reflect.Method.invoke(Method.java:578)
> 	at
> org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
> 	at
> org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
> 	at org.junit.jupiter.engine.execution.InvocationInterceptorChain
> $ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
> 	at
> org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
> 	at
> org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
> 	at
> org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
> 	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker
> $ReflectiveInterceptorCall.lambda$ofVoidMethod
> $0(InterceptingExecutableInvoker.java:103)
> 	at
> org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke
> $0(InterceptingExecutableInvoker.java:93)
> 	at org.junit.jupiter.engine.execution.InvocationInterceptorChain
> $InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
> 	at
> org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
> 	at
> org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
> 	at
> org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
> 	at
> org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
> 	at
> org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
> 	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda
> $invokeTestMethod$7(TestMethodTestDescriptor.java:217)
> 	at
> org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
> 	at
> org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
> 	at
> org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
> 	at
> org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
> 	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda
> $executeRecursively$6(NodeTestTask.java:151)
> 	at
> org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
> 	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda
> $executeRecursively$8(NodeTestTask.java:141)
> 	at
> org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
> 	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda
> $executeRecursively$9(NodeTestTask.java:139)
> 	at
> org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
> 	at
> org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
> 	at
> org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
> 	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
> 	at
> org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
> 	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda
> $executeRecursively$6(NodeTestTask.java:155)
> 	at
> org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
> 	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda
> $executeRecursively$8(NodeTestTask.java:141)
> 	at
> org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
> 	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda
> $executeRecursively$9(NodeTestTask.java:139)
> 	at
> org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
> 	at
> org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
> 	at
> org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
> 	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
> 	at
> org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
> 	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda
> $executeRecursively$6(NodeTestTask.java:155)
> 	at
> org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
> 	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda
> $executeRecursively$8(NodeTestTask.java:141)
> 	at
> org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
> 	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda
> $executeRecursively$9(NodeTestTask.java:139)
> 	at
> org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
> 	at
> org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
> 	at
> org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
> 	at
> org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
> 	at
> org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
> 	at
> org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
> 	at
> org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
> 	at
> org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
> 	at
> org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
> 	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda
> $execute$0(EngineExecutionOrchestrator.java:55)
> 	at
> org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
> 	at
> org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
> 	at
> org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
> 	at
> org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:95)
> 	at org.junit.platform.launcher.core.DefaultLauncherSession
> $DelegatingLauncher.execute(DefaultLauncherSession.java:91)
> 	at
> org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:60)
> 	at
> org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
> 	at
> org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
> 	at
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529)
> 	at
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)
> 	at
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
> 	at
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
> Caused by: org.objectweb.asm.tree.analysis.AnalyzerException: Error at
> instruction 12: Cannot append more locals than maxLocals
> 	at
> org.objectweb.asm.util.CheckFrameAnalyzer.expandFrames(CheckFrameAnalyzer.java:240)
> 	at
> org.objectweb.asm.util.CheckFrameAnalyzer.init(CheckFrameAnalyzer.java:124)
> 	at org.objectweb.asm.tree.analysis.Analyzer.analyze(Analyzer.java:142)
> 	at org.objectweb.asm.util.CheckMethodAdapter
> $1.visitEnd(CheckMethodAdapter.java:470)
> 	... 74 more
> Caused by: org.objectweb.asm.tree.analysis.AnalyzerException: Cannot 
> append
> more locals than maxLocals
> 	at
> org.objectweb.asm.util.CheckFrameAnalyzer.expandFrame(CheckFrameAnalyzer.java:277)
> 	at
> org.objectweb.asm.util.CheckFrameAnalyzer.expandFrames(CheckFrameAnalyzer.java:237)
> 	... 77 more
> ```
> 
> The class loads without issues in
> `Class.forName("org.junit.jupiter.api.AssertionUtils");`, complaining 
> that the
> `FRAME APPEND` on offset `12` exceeds the maximum of 2 local variables. 
> Given
> that the preceding implicit init frame based on the single argument and 
> the
> fact that it is a `static` method should have 1 local variable, my
> understanding is that this is not actually the case and that the 
> bytecode is
> valid. I see similar errors like this for multiple classes in JUnit and 
> other
> JARs.
> 
> Conversely, this check works as well:
> ```
> reader = new ClassReader(classfileBuffer);
> CheckClassAdapter.verify(reader,
> TracepointClassFileTransformerTest.class.getClassLoader(), false, new
> PrintWriter(System.err));
> ```
> 
> Thus my question: Am I misusing the `CheckClassAdapter` or the 
> `Analyzer` API
> somehow? Or is this a bug in the analysis?
> 
> Thanks,
> 
> 
> --
> You receive this message as a subscriber of the [email protected] mailing 
> list.
> To unsubscribe: mailto:[email protected]
> For general help: mailto:[email protected]?subject=help
> OW2 mailing lists service home page: http://www.ow2.org/wws

------------=_1682012203-21111-31
Content-Type: text/plain; charset="UTF-8"
Content-Disposition: inline
Content-Transfer-Encoding: 8bit


-- 
You receive this message as a subscriber of the [email protected] mailing list.
To unsubscribe: mailto:[email protected]
For general help: mailto:[email protected]?subject=help
OW2 mailing lists service home page: http://www.ow2.org/wws

------------=_1682012203-21111-31--