Commit 8fc735d8 authored by Eric Bruneton's avatar Eric Bruneton
Browse files

Add an analyzeFromFrames method in Analyzer. This will be used in...

Add an analyzeFromFrames method in Analyzer. This will be used in CheckClassAdapter to check frames if they are required and not computed with COMPUTE_FRAMES.
parent d7888a87
Pipeline #20510 passed with stage
in 5 minutes and 48 seconds
......@@ -685,6 +685,28 @@ public class Frame<V extends Value> {
}
}
/**
* Checks that merging the given frame into this frame would not produce any change, i.e. that the
* types in this frame are super types of the corresponding types in the given frame.
*
* @param frame a frame. This frame is left unchanged by this method.
* @param interpreter the interpreter used to merge values.
* @return an error message if the frames have incompatible sizes, or if a type in this frame is
* not a super type of the corresponding type in 'frame'. Returns {@literal null} otherwise.
*/
String checkMerge(final Frame<? extends V> frame, final Interpreter<V> interpreter) {
if (numStack != frame.numStack) {
return "incompatible stack heights";
}
for (int i = 0; i < numLocals + numStack; ++i) {
V v = interpreter.merge(values[i], frame.values[i]);
if (!v.equals(values[i])) {
return "incompatible types at index " + i + ": " + values[i] + " and " + frame.values[i];
}
}
return null;
}
/**
* Merges the given frame into this frame.
*
......
......@@ -41,6 +41,7 @@ import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.test.AsmTest;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodNode;
/**
......@@ -1130,6 +1131,169 @@ class AnalyzerTest extends AsmTest {
assertDoesNotThrow(() -> MethodNodeBuilder.buildClassWithMethod(methodNode).newInstance());
}
@Test
void testAnalyzeFromFrames_invalidJsr() {
MethodNode methodNode = new MethodNodeBuilder(4, 4).jsr(label0).label(label0).vreturn().build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(message.contains("Error at instruction 0: JSR instructions are unsupported"));
}
@Test
void testAnalyzeFromFrames_missingFrameAtJumpTarget() {
Label ifLabel = new Label();
MethodNode methodNode =
new MethodNodeBuilder()
.iconst_0()
.ifne(ifLabel)
.iconst_0()
.label(ifLabel)
.vreturn()
.build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(
message.contains("Error at instruction 1: Expected stack map frame at instruction 3"));
}
@Test
void testAnalyzeFromFrames_missingFrameAfterGoto() {
MethodNode methodNode =
new MethodNodeBuilder().nop().go(label0).nop().label(label0).vreturn().build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(
message.contains("Error at instruction 1: Expected stack map frame at instruction 2"));
}
@Test
void testAnalyzeFromFrames_illegalFrameType() {
MethodNode methodNode =
new MethodNodeBuilder()
.nop()
.go(label0)
.frame(123456, null, null)
.nop()
.label(label0)
.vreturn()
.build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(message.contains("Error at instruction 2: Illegal frame type 123456"));
}
@Test
void testAnalyzeFromFrames_invalidAppendFrame() {
MethodNode methodNode =
new MethodNodeBuilder(/* maxStack = */ 0, /* maxLocals = */ 1)
.nop()
.frame(Opcodes.F_APPEND, new Object[] {Opcodes.INTEGER}, null)
.vreturn()
.build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(
message.contains("Error at instruction 1: Cannot append more locals than maxLocals"));
}
@Test
void testAnalyzeFromFrames_invalidChopFrame() {
MethodNode methodNode =
new MethodNodeBuilder(/* maxStack = */ 0, /* maxLocals = */ 1)
.nop()
.frame(Opcodes.F_CHOP, new Object[] {null, null}, null)
.vreturn()
.build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(message.contains("Error at instruction 1: Cannot chop more locals than defined"));
}
@Test
void testAnalyzeFromFrames_illegalStackMapFrameValue() {
MethodNode methodNode =
new MethodNodeBuilder(/* maxStack = */ 0, /* maxLocals = */ 2)
.nop()
.frame(Opcodes.F_APPEND, new Object[] {new Object()}, null)
.vreturn()
.build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(
message.contains("Error at instruction 1: Illegal stack map frame value java.lang.Object"));
}
@Test
void testAnalyzeFromFrames_illegalLabelNodeStackMapFrameValue() {
MethodNode methodNode =
new MethodNodeBuilder(/* maxStack = */ 0, /* maxLocals = */ 2)
.nop()
.frame(Opcodes.F_APPEND, new Object[] {new LabelNode(label0)}, null)
.label(label0)
.vreturn()
.build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(
message.contains("Error at instruction 1: LabelNode does not designate a NEW instruction"));
}
@Test
void testAnalyzeFromFrames_frameAtJumpTargetHasIncompatibleStackHeight() {
MethodNode methodNode =
new MethodNodeBuilder()
.iconst_0()
.ifne(label0)
.iconst_0()
.label(label0)
.frame(Opcodes.F_SAME1, null, new Object[] {Opcodes.INTEGER})
.vreturn()
.build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(
message.contains(
"Error at instruction 1: Stack map frame incompatible with frame at instruction 3 (incompatible stack heights)"));
}
@Test
void testAnalyzeFromFrames_frameAtJumpTargetHasIncompatibleTypes() {
MethodNode methodNode =
new MethodNodeBuilder()
.iconst_0()
.ifne(label0)
.iconst_0()
.label(label0)
.frame(Opcodes.F_NEW, new Object[] {Opcodes.INTEGER}, null)
.vreturn()
.build();
Executable analyze = () -> newAnalyzer().analyzeFromFrames(CLASS_NAME, methodNode);
String message = assertThrows(AnalyzerException.class, analyze).getMessage();
assertTrue(
message.contains(
"Error at instruction 1: Stack map frame incompatible with frame at instruction 3 (incompatible types at index 0: INT and REFERENCE)"));
}
private static Analyzer<MockValue> newAnalyzer() {
return new Analyzer<>(new MockInterpreter());
}
......
......@@ -124,12 +124,7 @@ class AnalyzerWithBasicInterpreterTest extends AsmTest {
final PrecompiledClass classParameter, final Api apiParameter) throws AnalyzerException {
ClassNode classNode = new ClassNode();
new ClassReader(classParameter.getBytes()).accept(classNode, 0);
ArrayList<MethodMaxs> methodMaxs = new ArrayList<>();
for (MethodNode methodNode : classNode.methods) {
methodMaxs.add(new MethodMaxs(methodNode.maxStack, methodNode.maxLocals));
methodNode.maxLocals = 0;
methodNode.maxStack = 0;
}
ArrayList<MethodMaxs> methodMaxs = MethodMaxs.getAndClear(classNode);
Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>(new BasicInterpreter());
ArrayList<MethodMaxs> analyzedMethodMaxs = new ArrayList<>();
......@@ -220,15 +215,4 @@ class AnalyzerWithBasicInterpreterTest extends AsmTest {
return super.init(frame);
}
}
private static class MethodMaxs {
public final int maxStack;
public final int maxLocals;
public MethodMaxs(final int maxStack, final int maxLocals) {
this.maxStack = maxStack;
this.maxLocals = maxLocals;
}
}
}
......@@ -29,14 +29,18 @@ package org.objectweb.asm.tree.analysis;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import java.util.ArrayList;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.test.AsmTest;
import org.objectweb.asm.tree.ClassNode;
......@@ -237,7 +241,88 @@ class AnalyzerWithBasicVerifierTest extends AsmTest {
}
}
/**
* Tests that the precompiled classes can be successfully analyzed from their existing stack map
* frames with a BasicVerifier.
*
* @throws AnalyzerException if the test class can't be analyzed.
*/
@ParameterizedTest
@MethodSource(ALL_CLASSES_AND_LATEST_API)
void testAnalyzeFromFrames_basicVerifier(
final PrecompiledClass classParameter, final Api apiParameter) throws AnalyzerException {
assumeFalse(hasJsrOrRetInstructions(classParameter));
ClassNode classNode = computeFrames(classParameter);
Analyzer<BasicValue> analyzer = newAnalyzer();
ArrayList<Frame<? extends BasicValue>[]> methodFrames = new ArrayList<>();
for (MethodNode methodNode : classNode.methods) {
Frame<? extends BasicValue>[] result = analyzer.analyzeFromFrames(classNode.name, methodNode);
methodFrames.add(result);
}
for (int i = 0; i < classNode.methods.size(); ++i) {
Frame<? extends BasicValue>[] frames = methodFrames.get(i);
for (int j = 0; j < lastJvmInsnIndex(classNode.methods.get(i)); ++j) {
assertNotNull(frames[j]);
}
}
}
/**
* Tests that the precompiled classes can be successfully analyzed from their existing stack map
* frames with a BasicVerifier, even if the method node's max locals and max stack size are not
* set.
*
* @throws AnalyzerException if the test class can't be analyzed.
*/
@ParameterizedTest
@MethodSource(ALL_CLASSES_AND_LATEST_API)
void testAnalyzeAndComputeMaxsFromFrames_basicVerifier(
final PrecompiledClass classParameter, final Api apiParameter) throws AnalyzerException {
assumeFalse(hasJsrOrRetInstructions(classParameter));
ClassNode classNode = computeFrames(classParameter);
ArrayList<MethodMaxs> methodMaxs = MethodMaxs.getAndClear(classNode);
Analyzer<BasicValue> analyzer = newAnalyzer();
ArrayList<MethodMaxs> analyzedMethodMaxs = new ArrayList<>();
for (MethodNode methodNode : classNode.methods) {
analyzer.analyzeAndComputeMaxsFromFrames(classNode.name, methodNode);
analyzedMethodMaxs.add(new MethodMaxs(methodNode.maxStack, methodNode.maxLocals));
}
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);
}
}
private static boolean hasJsrOrRetInstructions(final PrecompiledClass classParameter) {
return classParameter == PrecompiledClass.JDK3_ALL_INSTRUCTIONS
|| classParameter == PrecompiledClass.JDK3_LARGE_METHOD;
}
private static ClassNode computeFrames(final PrecompiledClass classParameter) {
byte[] classFile = classParameter.getBytes();
ClassReader classReader = new ClassReader(classFile);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
classReader.accept(classWriter, 0);
classFile = classWriter.toByteArray();
ClassNode classNode = new ClassNode();
new ClassReader(classFile).accept(classNode, 0);
return classNode;
}
private static Analyzer<BasicValue> newAnalyzer() {
return new Analyzer<>(new BasicVerifier());
}
private static int lastJvmInsnIndex(final MethodNode method) {
for (int i = method.instructions.size() - 1; i >= 0; --i) {
if (method.instructions.get(i).getOpcode() >= 0) {
return i;
}
}
return -1;
}
}
// 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 org.objectweb.asm.tree.analysis;
import java.util.ArrayList;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
/**
* The maximum stack size and maximum number of local variables of a method.
*
* @author Eric Bruneton
*/
final class MethodMaxs {
public final int maxStack;
public final int maxLocals;
MethodMaxs(final int maxStack, final int maxLocals) {
this.maxStack = maxStack;
this.maxLocals = maxLocals;
}
static ArrayList<MethodMaxs> getAndClear(final ClassNode classNode) {
ArrayList<MethodMaxs> methodMaxs = new ArrayList<>();
for (MethodNode methodNode : classNode.methods) {
methodMaxs.add(new MethodMaxs(methodNode.maxStack, methodNode.maxLocals));
methodNode.maxLocals = 0;
methodNode.maxStack = 0;
}
return methodMaxs;
}
}
......@@ -27,11 +27,13 @@
// THE POSSIBILITY OF SUCH DAMAGE.
package org.objectweb.asm.tree.analysis;
import java.util.Arrays;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.test.ClassFile;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.MethodNode;
/**
......@@ -204,6 +206,15 @@ final class MethodNodeBuilder {
return this;
}
MethodNodeBuilder frame(final int type, final Object[] local, final Object[] stack) {
FrameNode frameNode = new FrameNode(Opcodes.F_NEW, 0, null, 0, null);
frameNode.type = type;
frameNode.local = local == null ? null : Arrays.asList(local);
frameNode.stack = stack == null ? null : Arrays.asList(stack);
methodNode.instructions.add(frameNode);
return this;
}
MethodNode build() {
methodNode.visitEnd();
return methodNode;
......
......@@ -131,7 +131,8 @@ public abstract class AbstractInsnNode {
/**
* Returns the opcode of this instruction.
*
* @return the opcode of this instruction.
* @return the opcode of this instruction, or -1 if this is not a JVM instruction (e.g. a label or
* a line number).
*/
public int getOpcode() {
return opcode;
......
......@@ -79,14 +79,18 @@ public class FrameNode extends AbstractInsnNode {
* @param type the type of this frame. Must be {@link Opcodes#F_NEW} for expanded frames, or
* {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link
* Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames.
* @param numLocal number of local variables of this stack map frame.
* @param numLocal number of local variables of this stack map frame. Long and double values count
* for one variable.
* @param local the types of the local variables of this stack map frame. Elements of this list
* can be Integer, String or LabelNode objects (for primitive, reference and uninitialized
* types respectively - see {@link MethodVisitor}).
* @param numStack number of operand stack elements of this stack map frame.
* types respectively - see {@link MethodVisitor}). Long and double values are represented by
* a single element.
* @param numStack number of operand stack elements of this stack map frame. Long and double
* values count for one stack element.
* @param stack the types of the operand stack elements of this stack map frame. Elements of this
* list can be Integer, String or LabelNode objects (for primitive, reference and
* uninitialized types respectively - see {@link MethodVisitor}).
* uninitialized types respectively - see {@link MethodVisitor}). Long and double values are
* represented by a single element.
*/
public FrameNode(
final int type,
......
......@@ -275,7 +275,8 @@ public abstract class MethodVisitor {
* @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded
* frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link
* Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames.
* @param numLocal the number of local variables in the visited frame.
* @param numLocal the number of local variables in the visited frame. Long and double values
* count for one variable.
* @param local the local variable types in this frame. This array must not be modified. Primitive
* types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
* Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or
......@@ -283,7 +284,8 @@ public abstract class MethodVisitor {
* Reference types are represented by String objects (representing internal names), and
* uninitialized types by Label objects (this label designates the NEW instruction that
* created this uninitialized value).
* @param numStack the number of operand stack elements in the visited frame.
* @param numStack the number of operand stack elements in the visited frame. Long and double
* values count for one stack element.
* @param stack the operand stack types in this frame. This array must not be modified. Its
* content has the same format as the "local" array.
* @throws IllegalStateException if a frame is visited just after another one, without any
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment