diff --git a/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/Analyzer.java b/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/Analyzer.java index f22e9b0f81f70985620b5aa6be3ecf99268c8b1a..7aad8db5279c51d1db771ac223e05bfd6da7ceac 100644 --- a/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/Analyzer.java +++ b/asm-analysis/src/main/java/org/objectweb/asm/tree/analysis/Analyzer.java @@ -305,6 +305,9 @@ public class Analyzer implements Opcodes { */ private static int computeMaxLocals(final MethodNode method) { int maxLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2; + if ((method.access & Opcodes.ACC_STATIC) != 0) { + maxLocals -= 1; + } for (AbstractInsnNode insnNode : method.instructions) { if (insnNode instanceof VarInsnNode) { int local = ((VarInsnNode) insnNode).var; diff --git a/asm-analysis/src/test/java/org/objectweb/asm/tree/analysis/AnalyzerWithBasicInterpreterTest.java b/asm-analysis/src/test/java/org/objectweb/asm/tree/analysis/AnalyzerWithBasicInterpreterTest.java index 6cefb4d43ef8d26972ef827499e1a7345c0a79e5..fc1999344814f9eee74b86829f5c590687b2f044 100644 --- a/asm-analysis/src/test/java/org/objectweb/asm/tree/analysis/AnalyzerWithBasicInterpreterTest.java +++ b/asm-analysis/src/test/java/org/objectweb/asm/tree/analysis/AnalyzerWithBasicInterpreterTest.java @@ -33,6 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.IOException; import java.nio.file.Files; @@ -45,6 +46,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.objectweb.asm.test.AsmTest; +import org.objectweb.asm.test.AsmTest.PrecompiledClass; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; @@ -138,12 +140,32 @@ class AnalyzerWithBasicInterpreterTest extends AsmTest { analyzedMethodMaxs.add(new MethodMaxs(methodNode.maxStack, methodNode.maxLocals)); } + // jdk3.SubOptimalMaxStackAndLocals has non optimal max stack and max local values on purpose. + assumeTrue(classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS); for (int i = 0; i < analyzedMethodMaxs.size(); ++i) { assertTrue(analyzedMethodMaxs.get(i).maxLocals >= methodMaxs.get(i).maxLocals); assertTrue(analyzedMethodMaxs.get(i).maxStack >= methodMaxs.get(i).maxStack); } } + /** + * Tests that analyzeAndComputeMaxs computes the smallest possible maxLocals for static methods. + * + * @throws AnalyzerException if the test class can't be analyzed. + */ + @Test + void testAnalyzeAndComputeMaxs_staticMethod() throws AnalyzerException { + MethodNode methodNode = + new MethodNodeBuilder("(I)V", /* maxStack = */ 0, /* maxLocals = */ 0).vreturn().build(); + methodNode.access |= Opcodes.ACC_STATIC; + Analyzer analyzer = new Analyzer(new BasicInterpreter()); + + analyzer.analyzeAndComputeMaxs("C", methodNode); + + assertEquals(1, methodNode.maxLocals); + assertEquals(0, methodNode.maxStack); + } + /** * Tests that the analyzer does not loop infinitely, even if the {@link Interpreter#merge} method * does not follow its required contract (namely that if the merge result is equal to the first diff --git a/asm-test/src/main/java/org/objectweb/asm/test/AsmTest.java b/asm-test/src/main/java/org/objectweb/asm/test/AsmTest.java index 48eb1863ee294fc5a6e7580176e9c9783a79ffb9..ee0ad0994541067216b87ac9bd46d645da2de67e 100644 --- a/asm-test/src/main/java/org/objectweb/asm/test/AsmTest.java +++ b/asm-test/src/main/java/org/objectweb/asm/test/AsmTest.java @@ -143,6 +143,7 @@ public abstract class AsmTest { JDK3_ARTIFICIAL_STRUCTURES("jdk3.ArtificialStructures"), JDK3_INNER_CLASS("jdk3.AllStructures$InnerClass"), JDK3_LARGE_METHOD("jdk3.LargeMethod"), + JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS("jdk3.SubOptimalMaxStackAndLocals"), JDK5_ALL_INSTRUCTIONS("jdk5.AllInstructions"), JDK5_ALL_STRUCTURES("jdk5.AllStructures"), JDK5_ANNOTATION("jdk5.AllStructures$InvisibleAnnotation"), diff --git a/asm-test/src/main/resources/jdk3/SubOptimalMaxStackAndLocals.class b/asm-test/src/main/resources/jdk3/SubOptimalMaxStackAndLocals.class new file mode 100644 index 0000000000000000000000000000000000000000..b494edc31cdffd57ccb37f3bd60af6b4fe07a100 Binary files /dev/null and b/asm-test/src/main/resources/jdk3/SubOptimalMaxStackAndLocals.class differ diff --git a/asm-test/src/resources/java/jdk11/AllStructures.jasm b/asm-test/src/resources/java/jdk11/AllStructures.jasm index ecc72c7b22d1e296827d5e6e9c947f4703d20d4a..61fcb028d68725bdc01b5eea83757a28bc2328de 100644 --- a/asm-test/src/resources/java/jdk11/AllStructures.jasm +++ b/asm-test/src/resources/java/jdk11/AllStructures.jasm @@ -29,7 +29,7 @@ package jdk11; /** * A class with JDK11 specific class file feature. A corresponding class file can be generated - with the OpenJDK asmtools (https://wiki.openjdk.java.net/display/CodeTools/asmtools), + * with the OpenJDK asmtools (https://wiki.openjdk.java.net/display/CodeTools/asmtools), * version 7 or more. Usage: * * java -jar asmtools.jar jasm AllStructures.jasm diff --git a/asm-test/src/resources/java/jdk3/SubOptimalMaxStackAndLocals.jasm b/asm-test/src/resources/java/jdk3/SubOptimalMaxStackAndLocals.jasm new file mode 100644 index 0000000000000000000000000000000000000000..7e21bb5f762d08217cf077acc13e115ae4ee5404 --- /dev/null +++ b/asm-test/src/resources/java/jdk3/SubOptimalMaxStackAndLocals.jasm @@ -0,0 +1,50 @@ +// ASM: a very small and fast Java bytecode manipulation framework +// Copyright (c) 2000-2011 INRIA, France Telecom +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holders nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +package jdk3; + +/** + * A class with valid but non optimal max_stack and max_locals, on purpose (to test cases like + * this). A corresponding class file can be generated with the OpenJDK asmtools + * (https://wiki.openjdk.java.net/display/CodeTools/asmtools), version 7 or more. Usage: + * + * java -jar asmtools.jar jasm SubOptimalMaxStackAndLocals.jasm + * + * @author Eric Bruneton + */ +super public class SubOptimalMaxStackAndLocals + version 47:0 +{ + +public Method "":"()V" + stack 2 locals 3 +{ + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; +} +} // end Class SubOptimalMaxStackAndLocals diff --git a/asm-util/src/main/java/org/objectweb/asm/util/CheckMethodAdapter.java b/asm-util/src/main/java/org/objectweb/asm/util/CheckMethodAdapter.java index 8b4ba7a9261fa86a278fec25feb8d5871005348b..6dc2e58321192415b04e13334a3ca19ee43e5329 100644 --- a/asm-util/src/main/java/org/objectweb/asm/util/CheckMethodAdapter.java +++ b/asm-util/src/main/java/org/objectweb/asm/util/CheckMethodAdapter.java @@ -446,6 +446,8 @@ public class CheckMethodAdapter extends MethodVisitor { new MethodNode(api, access, name, descriptor, null, null) { @Override public void visitEnd() { + int originalMaxLocals = maxLocals; + int originalMaxStack = maxStack; boolean checkMaxStackAndLocals = false; boolean checkFrames = false; if (methodVisitor instanceof MethodWriterWrapper) { @@ -473,6 +475,8 @@ public class CheckMethodAdapter extends MethodVisitor { throwError(analyzer, e); } if (methodVisitor != null) { + maxLocals = originalMaxLocals; + maxStack = originalMaxStack; accept(methodVisitor); } } diff --git a/asm-util/src/test/java/org/objectweb/asm/util/CheckClassAdapterTest.java b/asm-util/src/test/java/org/objectweb/asm/util/CheckClassAdapterTest.java index 4206bbcd52ecff6d14b68a02e5c71cc811b194e1..280ec62864955251d883c3eabb60f14af54a287a 100644 --- a/asm-util/src/test/java/org/objectweb/asm/util/CheckClassAdapterTest.java +++ b/asm-util/src/test/java/org/objectweb/asm/util/CheckClassAdapterTest.java @@ -468,7 +468,7 @@ class CheckClassAdapterTest extends AsmTest implements Opcodes { */ @ParameterizedTest @MethodSource(ALL_CLASSES_AND_ALL_APIS) - void testVisitMethods_precompiledClass( + void testVisitMethods_classWriterDelegate_precompiledClass( final PrecompiledClass classParameter, final Api apiParameter) { byte[] classFile = classParameter.getBytes(); ClassReader classReader = new ClassReader(classFile); @@ -481,7 +481,32 @@ class CheckClassAdapterTest extends AsmTest implements Opcodes { Exception exception = assertThrows(UnsupportedOperationException.class, accept); assertTrue(exception.getMessage().matches(UNSUPPORTED_OPERATION_MESSAGE_PATTERN)); } else { - classReader.accept(classVisitor, attributes(), 0); + assertDoesNotThrow(accept); + assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); + } + } + + /** + * Tests that classes are unchanged with a ClassReader->CheckClassAdapter->ClassVisitor transform. + */ + @ParameterizedTest + @MethodSource(ALL_CLASSES_AND_ALL_APIS) + void testVisitMethods_nonClassWriterDelegate_precompiledClass( + final PrecompiledClass classParameter, final Api apiParameter) { + byte[] classFile = classParameter.getBytes(); + ClassReader classReader = new ClassReader(classFile); + ClassWriter classWriter = new ClassWriter(0); + ClassVisitor noOpClassVisitor = + new ClassVisitor(/* latest */ Opcodes.ASM10_EXPERIMENTAL, classWriter) {}; + ClassVisitor classVisitor = new CheckClassAdapter(apiParameter.value(), noOpClassVisitor, true); + + Executable accept = () -> classReader.accept(classVisitor, attributes(), 0); + + if (classParameter.isMoreRecentThan(apiParameter)) { + Exception exception = assertThrows(UnsupportedOperationException.class, accept); + assertTrue(exception.getMessage().matches(UNSUPPORTED_OPERATION_MESSAGE_PATTERN)); + } else { + assertDoesNotThrow(accept); assertEquals(new ClassFile(classFile), new ClassFile(classWriter.toByteArray())); } } diff --git a/asm-util/src/test/resources/jdk3.SubOptimalMaxStackAndLocals.txt b/asm-util/src/test/resources/jdk3.SubOptimalMaxStackAndLocals.txt new file mode 100644 index 0000000000000000000000000000000000000000..c349b069e7718e4d8a305c2f0137199ba6fe575a --- /dev/null +++ b/asm-util/src/test/resources/jdk3.SubOptimalMaxStackAndLocals.txt @@ -0,0 +1,14 @@ +// class version 47.0 (47) +// access flags 0x21 +public class jdk3/SubOptimalMaxStackAndLocals { + + // compiled from: SubOptimalMaxStackAndLocals.jasm + + // access flags 0x1 + public ()V + ALOAD 0 + INVOKESPECIAL java/lang/Object. ()V + RETURN + MAXSTACK = 2 + MAXLOCALS = 3 +} diff --git a/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java b/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java index 22afbd4bb5e902e9d645552389839029aa4049dc..0871e90c23a11b035ad2d7ee2426da79a3beb9ca 100644 --- a/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java +++ b/asm/src/test/java/org/objectweb/asm/ClassWriterTest.java @@ -556,11 +556,13 @@ class ClassWriterTest extends AsmTest { /** * Tests that a ClassReader -> ClassWriter transform with the COMPUTE_MAXS option leaves classes * unchanged. This is not true in general (the valid max stack and max locals for a given method - * are not unique), but this should be the case with our precompiled classes. + * are not unique), but this should be the case with our precompiled classes (except + * jdk3.SubOptimalMaxStackAndLocals, which has non optimal max values on purpose). */ @ParameterizedTest @MethodSource(ALL_CLASSES_AND_ALL_APIS) void testReadAndWrite_computeMaxs(final PrecompiledClass classParameter, final Api apiParameter) { + assumeTrue(classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS); byte[] classFile = classParameter.getBytes(); ClassReader classReader = new ClassReader(classFile); ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); @@ -614,8 +616,10 @@ class ClassWriterTest extends AsmTest { assertTrue(classWriter.hasFlags(ClassWriter.COMPUTE_FRAMES)); // The computed stack map frames should be equal to the original ones, if any (classes before // JDK8 don't have ones). This is not true in general (the valid frames for a given method are - // not unique), but this should be the case with our precompiled classes. - if (classParameter.isMoreRecentThan(Api.ASM4)) { + // not unique), but this should be the case with our precompiled classes (except + // jdk3.SubOptimalMaxStackAndLocals, which has non optimal max values on purpose). + if (classParameter.isMoreRecentThan(Api.ASM4) + && classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS) { assertEquals(new ClassFile(classFile), new ClassFile(newClassFile)); } Executable newInstance = () -> new ClassFile(newClassFile).newInstance(); @@ -663,8 +667,10 @@ class ClassWriterTest extends AsmTest { // The computed stack map frames should be equal to the original ones, if any (classes before // JDK8 don't have ones). This is not true in general (the valid frames for a given method are - // not unique), but this should be the case with our precompiled classes. - if (classParameter.isMoreRecentThan(Api.ASM4)) { + // not unique), but this should be the case with our precompiled classes (except + // jdk3.SubOptimalMaxStackAndLocals, which has non optimal max values on purpose). + if (classParameter.isMoreRecentThan(Api.ASM4) + && classParameter != PrecompiledClass.JDK3_SUB_OPTIMAL_MAX_STACK_AND_LOCALS) { assertEquals(new ClassFile(classFile), new ClassFile(newClassFile)); } Executable newInstance = () -> new ClassFile(newClassFile).newInstance();