From de38696f29d0bfe934a1a03c2c5dddd5c475f5c5 Mon Sep 17 00:00:00 2001 From: Eric Bruneton Date: Sat, 9 Apr 2022 17:21:32 +0200 Subject: [PATCH 1/3] Add a CheckFrameAnalyzer class in asm-util. This will be used in CheckClassAdapter to check frames if they are required and not computed with COMPUTE_FRAMES. --- .../objectweb/asm/tree/analysis/Analyzer.java | 2 +- .../asm/util/CheckFrameAnalyzer.java | 439 ++++++++++++++++++ .../asm/util/CheckFrameAnalyzerTest.java | 362 +++++++++++++++ .../objectweb/asm/util/MethodNodeBuilder.java | 109 +++++ 4 files changed, 911 insertions(+), 1 deletion(-) create mode 100644 asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java create mode 100644 asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java create mode 100644 asm-util/src/test/java/org/objectweb/asm/util/MethodNodeBuilder.java 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 60c443fcc..b622f4ac1 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 @@ -48,7 +48,7 @@ import org.objectweb.asm.tree.VarInsnNode; * A semantic bytecode analyzer. This class does not fully check that JSR and RET instructions * are valid. * - * @param type of the Value used for the analysis. + * @param type of the {@link Value} used for the analysis. * @author Eric Bruneton */ public class Analyzer implements Opcodes { diff --git a/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java b/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java new file mode 100644 index 000000000..a7379d12e --- /dev/null +++ b/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java @@ -0,0 +1,439 @@ +// 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.util; + +import java.util.Collections; +import java.util.List; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FrameNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.TryCatchBlockNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.Interpreter; +import org.objectweb.asm.tree.analysis.Value; + +/** + * An {@link Analyzer} subclass which checks that methods provide stack map frames where expected + * (i.e. at jump target and after instructions without immediate successor), and that these stack + * map frames are valid (for the provided interpreter; they may still be invalid for the JVM, if the + * {@link Interpreter} uses a simplified type system compared to the JVM verifier). + * + * @author Eric Bruneton + * @param type of the {@link Value} used for the analysis. + */ +class CheckFrameAnalyzer extends Analyzer { + + /** The interpreter to use to symbolically interpret the bytecode instructions. */ + private final Interpreter interpreter; + + /** The instructions of the currently analyzed method. */ + private InsnList insnList; + + /** + * The number of locals in the last stack map frame processed by {@link expandFrame}. Long and + * double values are represented with two elements. + */ + private int currentLocals; + + CheckFrameAnalyzer(final Interpreter interpreter) { + super(interpreter); + this.interpreter = interpreter; + } + + @Override + protected void init(final String owner, final MethodNode method) throws AnalyzerException { + insnList = method.instructions; + currentLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2; + + Frame[] frames = getFrames(); + Frame currentFrame = frames[0]; + expandFrames(owner, method, currentFrame); + for (int insnIndex = 0; insnIndex < insnList.size(); ++insnIndex) { + Frame oldFrame = frames[insnIndex]; + + // Simulate the execution of this instruction. + AbstractInsnNode insnNode = null; + try { + insnNode = method.instructions.get(insnIndex); + int insnOpcode = insnNode.getOpcode(); + int insnType = insnNode.getType(); + + if (insnType == AbstractInsnNode.LABEL + || insnType == AbstractInsnNode.LINE + || insnType == AbstractInsnNode.FRAME) { + checkFrame(insnIndex + 1, oldFrame, /* requireFrame = */ false); + } else { + currentFrame.init(oldFrame).execute(insnNode, interpreter); + + if (insnNode instanceof JumpInsnNode) { + JumpInsnNode jumpInsn = (JumpInsnNode) insnNode; + if (insnOpcode != GOTO && insnOpcode != JSR) { + checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false); + } else if (insnOpcode == GOTO) { + endControlFlow(insnIndex); + } + int jumpInsnIndex = insnList.indexOf(jumpInsn.label); + if (insnOpcode == JSR) { + throw new AnalyzerException(insnNode, "JSR instructions are unsupported"); + } else { + checkFrame(jumpInsnIndex, currentFrame, /* requireFrame = */ true); + } + } else if (insnNode instanceof LookupSwitchInsnNode) { + LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) insnNode; + int targetInsnIndex = insnList.indexOf(lookupSwitchInsn.dflt); + checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true); + for (int i = 0; i < lookupSwitchInsn.labels.size(); ++i) { + LabelNode label = lookupSwitchInsn.labels.get(i); + targetInsnIndex = insnList.indexOf(label); + currentFrame.initJumpTarget(insnOpcode, label); + checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true); + } + endControlFlow(insnIndex); + } else if (insnNode instanceof TableSwitchInsnNode) { + TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) insnNode; + int targetInsnIndex = insnList.indexOf(tableSwitchInsn.dflt); + currentFrame.initJumpTarget(insnOpcode, tableSwitchInsn.dflt); + checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true); + newControlFlowEdge(insnIndex, targetInsnIndex); + for (int i = 0; i < tableSwitchInsn.labels.size(); ++i) { + LabelNode label = tableSwitchInsn.labels.get(i); + currentFrame.initJumpTarget(insnOpcode, label); + targetInsnIndex = insnList.indexOf(label); + checkFrame(targetInsnIndex, currentFrame, /* requireFrame = */ true); + } + endControlFlow(insnIndex); + } else if (insnOpcode == RET) { + throw new AnalyzerException(insnNode, "RET instructions are unsupported"); + } else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) { + checkFrame(insnIndex + 1, currentFrame, /* requireFrame = */ false); + } else { + endControlFlow(insnIndex); + } + } + + List insnHandlers = getHandlers(insnIndex); + if (insnHandlers != null) { + for (TryCatchBlockNode tryCatchBlock : insnHandlers) { + Type catchType; + if (tryCatchBlock.type == null) { + catchType = Type.getObjectType("java/lang/Throwable"); + } else { + catchType = Type.getObjectType(tryCatchBlock.type); + } + Frame handler = newFrame(oldFrame); + handler.clearStack(); + handler.push(interpreter.newExceptionValue(tryCatchBlock, handler, catchType)); + checkFrame(insnList.indexOf(tryCatchBlock.handler), handler, /* requireFrame = */ true); + } + } + + if (!hasNextJvmInsnOrFrame(insnIndex)) { + break; + } + } catch (AnalyzerException e) { + throw new AnalyzerException( + e.node, "Error at instruction " + insnIndex + ": " + e.getMessage(), e); + } catch (RuntimeException e) { + // DontCheck(IllegalCatch): can't be fixed, for backward compatibility. + throw new AnalyzerException( + insnNode, "Error at instruction " + insnIndex + ": " + e.getMessage(), e); + } + } + } + + /** + * Expands the {@link FrameNode} "instructions" of the given method into {@link Frame} objects and + * stores them at the corresponding indices of the {@link #frames} array. The expanded frames are + * also associated with the label and line number nodes immediately preceding each frame node. + * + * @param owner the internal name of the class to which 'method' belongs. + * @param method the method whose frames must be expanded. + * @param initialFrame the implicit initial frame of 'method'. + * @throws AnalyzerException if the stack map frames of 'method', i.e. its FrameNode + * "instructions", are invalid. + */ + private void expandFrames( + final String owner, final MethodNode method, final Frame initialFrame) + throws AnalyzerException { + int lastJvmOrFrameInsnIndex = -1; + Frame currentFrame = initialFrame; + int currentInsnIndex = 0; + for (AbstractInsnNode insnNode : method.instructions) { + if (insnNode instanceof FrameNode) { + try { + currentFrame = expandFrame(owner, currentFrame, (FrameNode) insnNode); + } catch (AnalyzerException e) { + throw new AnalyzerException( + e.node, "Error at instruction " + currentInsnIndex + ": " + e.getMessage(), e); + } + for (int index = lastJvmOrFrameInsnIndex + 1; index <= currentInsnIndex; ++index) { + getFrames()[index] = currentFrame; + } + } + if (isJvmInsnNode(insnNode) || insnNode instanceof FrameNode) { + lastJvmOrFrameInsnIndex = currentInsnIndex; + } + currentInsnIndex += 1; + } + } + + /** + * Returns the expanded representation of the given FrameNode. + * + * @param owner the internal name of the class to which 'frameNode' belongs. + * @param previousFrame the frame before 'frameNode', in expanded form. + * @param frameNode a possibly compressed stack map frame. + * @return the expanded version of 'frameNode'. + * @throws AnalyzerException if 'frameNode' is invalid. + */ + private Frame expandFrame( + final String owner, final Frame previousFrame, final FrameNode frameNode) + throws AnalyzerException { + Frame frame = newFrame(previousFrame); + List locals = frameNode.local == null ? Collections.emptyList() : frameNode.local; + int currentLocal = currentLocals; + switch (frameNode.type) { + case Opcodes.F_NEW: + case Opcodes.F_FULL: + currentLocal = 0; + // fall through + case Opcodes.F_APPEND: + for (Object type : locals) { + V value = newFrameValue(owner, frameNode, type); + if (currentLocal + value.getSize() > frame.getLocals()) { + throw new AnalyzerException(frameNode, "Cannot append more locals than maxLocals"); + } + frame.setLocal(currentLocal++, value); + if (value.getSize() == 2) { + frame.setLocal(currentLocal++, interpreter.newValue(null)); + } + } + break; + case Opcodes.F_CHOP: + for (Object unusedType : locals) { + if (currentLocal <= 0) { + throw new AnalyzerException(frameNode, "Cannot chop more locals than defined"); + } + if (currentLocal > 1 && frame.getLocal(currentLocal - 2).getSize() == 2) { + currentLocal -= 2; + } else { + currentLocal -= 1; + } + } + break; + case Opcodes.F_SAME: + case Opcodes.F_SAME1: + break; + default: + throw new AnalyzerException(frameNode, "Illegal frame type " + frameNode.type); + } + currentLocals = currentLocal; + while (currentLocal < frame.getLocals()) { + frame.setLocal(currentLocal++, interpreter.newValue(null)); + } + + List stack = frameNode.stack == null ? Collections.emptyList() : frameNode.stack; + frame.clearStack(); + for (Object type : stack) { + frame.push(newFrameValue(owner, frameNode, type)); + } + return frame; + } + + /** + * Creates a new value that represents the given stack map frame type. + * + * @param owner the internal name of the class to which 'frameNode' belongs. + * @param frameNode the stack map frame to which 'type' belongs. + * @param type an Integer, String or LabelNode object representing a primitive, reference or + * uninitialized a stack map frame type, respectively. See {@link FrameNode}. + * @return a value that represents the given type. + * @throws AnalyzerException if 'type' is an invalid stack map frame type. + */ + private V newFrameValue(final String owner, final FrameNode frameNode, final Object type) + throws AnalyzerException { + if (type == Opcodes.TOP) { + return interpreter.newValue(null); + } else if (type == Opcodes.INTEGER) { + return interpreter.newValue(Type.INT_TYPE); + } else if (type == Opcodes.FLOAT) { + return interpreter.newValue(Type.FLOAT_TYPE); + } else if (type == Opcodes.LONG) { + return interpreter.newValue(Type.LONG_TYPE); + } else if (type == Opcodes.DOUBLE) { + return interpreter.newValue(Type.DOUBLE_TYPE); + } else if (type == Opcodes.NULL) { + return interpreter.newOperation(new InsnNode(Opcodes.ACONST_NULL)); + } else if (type == Opcodes.UNINITIALIZED_THIS) { + return interpreter.newValue(Type.getObjectType(owner)); + } else if (type instanceof String) { + return interpreter.newValue(Type.getObjectType((String) type)); + } else if (type instanceof LabelNode) { + AbstractInsnNode referencedNode = (LabelNode) type; + while (referencedNode != null && !isJvmInsnNode(referencedNode)) { + referencedNode = referencedNode.getNext(); + } + if (referencedNode == null || referencedNode.getOpcode() != Opcodes.NEW) { + throw new AnalyzerException(frameNode, "LabelNode does not designate a NEW instruction"); + } + return interpreter.newValue(Type.getObjectType(((TypeInsnNode) referencedNode).desc)); + } + throw new AnalyzerException(frameNode, "Illegal stack map frame value " + type); + } + + /** + * Merges (or checks) the given frame into (or with respect to) the frame at the given + * instruction. If the frame or the subroutine at the given instruction index changes as a result + * of this merge, the instruction index is added to the list of instructions to process (if it is + * not already the case). + * + * @param insnIndex an instruction index. + * @param frame a frame. This frame is left unchanged by this method. + * @param requireFrame whether a frame must already exist or not in {@link #frames} at + * 'insnIndex'. + * @throws AnalyzerException if the frames have incompatible sizes or if the frame at 'insnIndex' + * is missing (if required) or not compatible with 'frame'. + */ + private void checkFrame(final int insnIndex, final Frame frame, final boolean requireFrame) + throws AnalyzerException { + Frame oldFrame = getFrames()[insnIndex]; + if (oldFrame == null) { + if (requireFrame) { + throw new AnalyzerException(null, "Expected stack map frame at instruction " + insnIndex); + } + getFrames()[insnIndex] = newFrame(frame); + } else { + String error = checkMerge(frame, oldFrame); + if (error != null) { + throw new AnalyzerException( + null, + "Stack map frame incompatible with frame at instruction " + + insnIndex + + " (" + + error + + ")"); + } + } + } + + /** + * Checks that merging the two given frame would not produce any change, i.e. that the types in + * the source frame are super types of the corresponding types in the destination frame. + * + * @param srcFrame a source frame. This frame is left unchanged by this method. + * @param dstFrame a destination frame. This frame is left unchanged by this method. + * @return an error message if the frames have incompatible sizes, or if a type in the source + * frame is not a super type of the corresponding type in the destination frame. Returns + * {@literal null} otherwise. + */ + private String checkMerge(final Frame srcFrame, final Frame dstFrame) { + int numLocals = srcFrame.getLocals(); + if (numLocals != dstFrame.getLocals()) { + throw new AssertionError(); + } + for (int i = 0; i < numLocals; ++i) { + V v = interpreter.merge(srcFrame.getLocal(i), dstFrame.getLocal(i)); + if (!v.equals(dstFrame.getLocal(i))) { + return "incompatible types at local " + + i + + ": " + + srcFrame.getLocal(i) + + " and " + + dstFrame.getLocal(i); + } + } + int numStack = srcFrame.getStackSize(); + if (numStack != dstFrame.getStackSize()) { + return "incompatible stack heights"; + } + for (int i = 0; i < numStack; ++i) { + V v = interpreter.merge(srcFrame.getStack(i), dstFrame.getStack(i)); + if (!v.equals(dstFrame.getStack(i))) { + return "incompatible types at stack item " + + i + + ": " + + srcFrame.getStack(i) + + " and " + + dstFrame.getStack(i); + } + } + return null; + } + + /** + * Ends the control flow graph at the given instruction. + * + * @param insnIndex an instruction index. + * @throws AnalyzerException if 'insnIndex' is not the last instruction and there is no frame at + * 'insnIndex' + 1 in {@link #getFrames}. + */ + private void endControlFlow(final int insnIndex) throws AnalyzerException { + if (hasNextJvmInsnOrFrame(insnIndex) && getFrames()[insnIndex + 1] == null) { + throw new AnalyzerException( + null, "Expected stack map frame at instruction " + (insnIndex + 1)); + } + } + + /** + * Returns true if the given instruction is followed by a JVM instruction or a by stack map frame. + * + * @param insnIndex an instruction index. + * @return true if 'insnIndex' is followed by a JVM instruction or a by stack map frame. + */ + private boolean hasNextJvmInsnOrFrame(final int insnIndex) { + AbstractInsnNode insn = insnList.get(insnIndex).getNext(); + while (insn != null) { + if (isJvmInsnNode(insn) || insn instanceof FrameNode) { + return true; + } + insn = insn.getNext(); + } + return false; + } + + /** + * Returns true if the given instruction node corresponds to a real JVM instruction. + * + * @param insnNode an instruction node. + * @return true except for label, line number and stack map frame nodes. + */ + private static boolean isJvmInsnNode(final AbstractInsnNode insnNode) { + return insnNode.getOpcode() >= 0; + } +} diff --git a/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java b/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java new file mode 100644 index 000000000..2f28d45cb --- /dev/null +++ b/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java @@ -0,0 +1,362 @@ +// 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.util; + +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.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.test.AsmTest; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.BasicValue; +import org.objectweb.asm.tree.analysis.BasicVerifier; +import org.objectweb.asm.tree.analysis.Frame; + +/** + * Unit tests for {@link CheckFrameanalyzer}. + * + * @author Eric Bruneton + */ +class CheckFrameAnalyzerTest extends AsmTest implements Opcodes { + + private static final String CLASS_NAME = "C"; + + // Labels used to generate test cases. + private final Label label0 = new Label(); + + @Test + void testAnalyze_invalidJsr() { + MethodNode methodNode = new MethodNodeBuilder().jsr(label0).label(label0).vreturn().build(); + + Executable analyze = () -> newAnalyzer().analyze(CLASS_NAME, methodNode); + + String message = assertThrows(AnalyzerException.class, analyze).getMessage(); + assertTrue(message.contains("Error at instruction 0: JSR instructions are unsupported")); + } + + @Test + void testAnalyze_invalidRet() { + MethodNode methodNode = new MethodNodeBuilder().ret(0).vreturn().build(); + + Executable analyze = () -> newAnalyzer().analyze(CLASS_NAME, methodNode); + + String message = assertThrows(AnalyzerException.class, analyze).getMessage(); + assertTrue(message.contains("Error at instruction 0: RET instructions are unsupported")); + } + + @Test + void testAnalyze_missingFrameAtJumpTarget() { + MethodNode methodNode = + new MethodNodeBuilder().iconst_0().ifne(label0).iconst_0().label(label0).vreturn().build(); + + Executable analyze = () -> newAnalyzer().analyze(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 testAnalyze_missingFrameAfterGoto() { + MethodNode methodNode = + new MethodNodeBuilder().nop().go(label0).nop().label(label0).vreturn().build(); + + Executable analyze = () -> newAnalyzer().analyze(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 testAnalyze_illegalFrameType() { + MethodNode methodNode = + new MethodNodeBuilder() + .nop() + .go(label0) + .frame(123456, null, null) + .nop() + .label(label0) + .vreturn() + .build(); + + Executable analyze = () -> newAnalyzer().analyze(CLASS_NAME, methodNode); + + String message = assertThrows(AnalyzerException.class, analyze).getMessage(); + assertTrue(message.contains("Error at instruction 2: Illegal frame type 123456")); + } + + @Test + void testAnalyze_invalidAppendFrame() { + MethodNode methodNode = + new MethodNodeBuilder(/* maxStack = */ 0, /* maxLocals = */ 1) + .nop() + .frame(Opcodes.F_APPEND, new Object[] {Opcodes.INTEGER}, null) + .vreturn() + .build(); + + Executable analyze = () -> newAnalyzer().analyze(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 testAnalyze_invalidChopFrame() { + MethodNode methodNode = + new MethodNodeBuilder(/* maxStack = */ 0, /* maxLocals = */ 1) + .nop() + .frame(Opcodes.F_CHOP, new Object[] {null, null}, null) + .vreturn() + .build(); + + Executable analyze = () -> newAnalyzer().analyze(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 testAnalyze_illegalStackMapFrameValue() { + MethodNode methodNode = + new MethodNodeBuilder(/* maxStack = */ 0, /* maxLocals = */ 2) + .nop() + .frame(Opcodes.F_APPEND, new Object[] {new Object()}, null) + .vreturn() + .build(); + + Executable analyze = () -> newAnalyzer().analyze(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 testAnalyze_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().analyze(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 testAnalyze_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().analyze(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 testAnalyze_frameAtJumpTargetHasIncompatibleLocalValues() { + 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().analyze(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 local 0: R and I)")); + } + + @Test + void testAnalyze_frameAtJumpTargetHasIncompatibleStackValues() { + MethodNode methodNode = + new MethodNodeBuilder() + .iconst_0() + .iconst_0() + .ifne(label0) + .iconst_0() + .iconst_0() + .label(label0) + .frame(Opcodes.F_NEW, new Object[] {"C"}, new Object[] {"C"}) + .vreturn() + .build(); + + Executable analyze = () -> newAnalyzer().analyze(CLASS_NAME, methodNode); + + String message = assertThrows(AnalyzerException.class, analyze).getMessage(); + assertTrue( + message.contains( + "Error at instruction 2: Stack map frame incompatible with frame at instruction 5 " + + "(incompatible types at stack item 0: I and R)")); + } + + /** + * 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 testAnalyze_basicVerifier(final PrecompiledClass classParameter, final Api apiParameter) + throws AnalyzerException { + assumeFalse(hasJsrOrRetInstructions(classParameter)); + ClassNode classNode = computeFrames(classParameter); + Analyzer analyzer = newAnalyzer(); + + ArrayList[]> methodFrames = new ArrayList<>(); + for (MethodNode methodNode : classNode.methods) { + Frame[] result = analyzer.analyze(classNode.name, methodNode); + methodFrames.add(result); + } + + for (int i = 0; i < classNode.methods.size(); ++i) { + Frame[] 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 testAnalyzeAndComputeMaxs_basicVerifier( + final PrecompiledClass classParameter, final Api apiParameter) throws AnalyzerException { + assumeFalse(hasJsrOrRetInstructions(classParameter)); + ClassNode classNode = computeFrames(classParameter); + ArrayList methodMaxs = MethodMaxs.getAndClear(classNode); + Analyzer analyzer = newAnalyzer(); + + ArrayList analyzedMethodMaxs = new ArrayList<>(); + for (MethodNode methodNode : classNode.methods) { + analyzer.analyzeAndComputeMaxs(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 newAnalyzer() { + return new CheckFrameAnalyzer<>(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; + } + + private static class MethodMaxs { + + final int maxStack; + final int maxLocals; + + MethodMaxs(final int maxStack, final int maxLocals) { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + + static ArrayList getAndClear(final ClassNode classNode) { + ArrayList methodMaxs = new ArrayList<>(); + for (MethodNode methodNode : classNode.methods) { + methodMaxs.add(new MethodMaxs(methodNode.maxStack, methodNode.maxLocals)); + methodNode.maxLocals = 0; + methodNode.maxStack = 0; + } + return methodMaxs; + } + } +} diff --git a/asm-util/src/test/java/org/objectweb/asm/util/MethodNodeBuilder.java b/asm-util/src/test/java/org/objectweb/asm/util/MethodNodeBuilder.java new file mode 100644 index 000000000..936d4ce96 --- /dev/null +++ b/asm-util/src/test/java/org/objectweb/asm/util/MethodNodeBuilder.java @@ -0,0 +1,109 @@ +// ASM: a very sm14all 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.util; + +import java.util.Arrays; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.FrameNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * A builder of {@link MethodNode}, to construct test cases for unit tests. + * + * @author Eric Bruneton + */ +final class MethodNodeBuilder { + + private final MethodNode methodNode; + + MethodNodeBuilder() { + this(/* maxStack = */ 10, /* maxLocals = */ 10); + } + + MethodNodeBuilder(final int maxStack, final int maxLocals) { + methodNode = new MethodNode(Opcodes.ACC_PUBLIC, "m", "()V", null, null); + methodNode.maxStack = maxStack; + methodNode.maxLocals = maxLocals; + methodNode.visitCode(); + } + + MethodNodeBuilder nop() { + methodNode.visitInsn(Opcodes.NOP); + return this; + } + + MethodNodeBuilder iconst_0() { + methodNode.visitInsn(Opcodes.ICONST_0); + return this; + } + + MethodNodeBuilder vreturn() { + methodNode.visitInsn(Opcodes.RETURN); + return this; + } + + MethodNodeBuilder label(final Label label) { + methodNode.visitLabel(label); + return this; + } + + MethodNodeBuilder go(final Label label) { + methodNode.visitJumpInsn(Opcodes.GOTO, label); + return this; + } + + MethodNodeBuilder jsr(final Label label) { + methodNode.visitJumpInsn(Opcodes.JSR, label); + return this; + } + + MethodNodeBuilder ret(final int varIndex) { + methodNode.visitVarInsn(Opcodes.RET, varIndex); + return this; + } + + MethodNodeBuilder ifne(final Label label) { + methodNode.visitJumpInsn(Opcodes.IFNE, label); + 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; + } +} -- GitLab From c8a265348cd655d0433ea3fca2abe7e66aced458 Mon Sep 17 00:00:00 2001 From: Eric Bruneton Date: Sun, 22 May 2022 13:16:26 +0200 Subject: [PATCH 2/3] Add more comments. --- .../asm/util/CheckFrameAnalyzer.java | 61 +++++++++++++++---- .../asm/util/CheckAnnotationAdapterTest.java | 3 +- .../asm/util/CheckFieldAdapterTest.java | 3 +- .../asm/util/CheckFrameAnalyzerTest.java | 2 +- .../util/CheckRecordComponentAdapterTest.java | 3 +- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java b/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java index a7379d12e..2726229aa 100644 --- a/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java +++ b/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java @@ -52,7 +52,45 @@ import org.objectweb.asm.tree.analysis.Value; * An {@link Analyzer} subclass which checks that methods provide stack map frames where expected * (i.e. at jump target and after instructions without immediate successor), and that these stack * map frames are valid (for the provided interpreter; they may still be invalid for the JVM, if the - * {@link Interpreter} uses a simplified type system compared to the JVM verifier). + * {@link Interpreter} uses a simplified type system compared to the JVM verifier). This is done in + * two steps: + * + *
    + *
  • First, the stack map frames in {@link FrameNode}s are expanded, and stored at their + * respective instruction offsets. The expansion process uncompresses the APPEND, CHOP and + * SAME frames to FULL frames. It also converts the stack map frame verification types to + * {@link Value}s, via the provided {@link Interpreter}. The expansion is done in {@link + * #expandFrames}, by looking at each {@link FrameNode} in sequence (compressed frames are + * defined relatively to the previous {@link FrameNode}, or the implicit first frame). The + * actual decompression is done in {@link #expandFrame}, and the type conversion in {@link + * #newFrameValue}. + *
  • Next, the method instructions are checked in sequence. Starting from the implicit initial + * frame, the execution of each instruction i is simulated on the current stack map + * frame, with the {@link Frame#execute} method. This gives a new stack map frame f, + * representing the stack map frame state after the execution of i. Then: + *
      + *
    • If there is a next instruction and if the control flow cannot continue to it (e.g. if + * i is a RETURN or an ATHROW, for instance): an existing stack map frame + * f0 (coming from the first step) is expected after i. + *
    • If there is a next instruction and if the control flow can continue to it (e.g. if + * i is a ALOAD, for instance): either there an existing stack map frame + * f0 (coming from the first step) after i, or there is none. In the + * first case f and f0 must be compatible: the types in + * f must be sub types of the corresponding types in the existing frame + * f0 (otherwise an exception is thrown). In the second case, f0 is + * simply set to the value of f. + *
    • If the control can continue to some instruction j (e.g. if i is an + * IF_EQ, for instance): an existing stack map frame f0 (coming from the first + * step) is expected at j, which must be compatible with f (as defined + * previously). + *
    + * The sequential loop over the instruction is done in {@link #init}, which is called from the + * {@link Analyzer#analyze} method. Cases where the control flow cannot continue to the next + * instruction are handled in {@link #endControlFlow}. Cases where the control flow can + * continue to the next instruction, or jump to another instruction, are handled in {@link + * #checkFrame}. This method checks that an existing stack map frame is present when required, + * and checks the stack map frames compatibility with {@link #checkMerge}. + *
* * @author Eric Bruneton * @param type of the {@link Value} used for the analysis. @@ -214,7 +252,7 @@ class CheckFrameAnalyzer extends Analyzer { } /** - * Returns the expanded representation of the given FrameNode. + * Returns the expanded representation of the given {@link FrameNode}. * * @param owner the internal name of the class to which 'frameNode' belongs. * @param previousFrame the frame before 'frameNode', in expanded form. @@ -277,7 +315,7 @@ class CheckFrameAnalyzer extends Analyzer { } /** - * Creates a new value that represents the given stack map frame type. + * Creates a new {@link Value} that represents the given stack map frame type. * * @param owner the internal name of the class to which 'frameNode' belongs. * @param frameNode the stack map frame to which 'type' belongs. @@ -318,10 +356,10 @@ class CheckFrameAnalyzer extends Analyzer { } /** - * Merges (or checks) the given frame into (or with respect to) the frame at the given - * instruction. If the frame or the subroutine at the given instruction index changes as a result - * of this merge, the instruction index is added to the list of instructions to process (if it is - * not already the case). + * Checks that the given frame is compatible with the frame at the given instruction index, if + * any. If there is no frame at this instruction index and none is required, the frame at + * 'insnIndex' is set to the given frame. Otherwise, if the merge of the two frames is not equal + * to the current frame at 'insnIndex', an exception is thrown. * * @param insnIndex an instruction index. * @param frame a frame. This frame is left unchanged by this method. @@ -353,13 +391,13 @@ class CheckFrameAnalyzer extends Analyzer { } /** - * Checks that merging the two given frame would not produce any change, i.e. that the types in - * the source frame are super types of the corresponding types in the destination frame. + * Checks that merging the two given frames would not produce any change, i.e. that the types in + * the source frame are sub types of the corresponding types in the destination frame. * * @param srcFrame a source frame. This frame is left unchanged by this method. * @param dstFrame a destination frame. This frame is left unchanged by this method. * @return an error message if the frames have incompatible sizes, or if a type in the source - * frame is not a super type of the corresponding type in the destination frame. Returns + * frame is not a sub type of the corresponding type in the destination frame. Returns * {@literal null} otherwise. */ private String checkMerge(final Frame srcFrame, final Frame dstFrame) { @@ -397,7 +435,8 @@ class CheckFrameAnalyzer extends Analyzer { } /** - * Ends the control flow graph at the given instruction. + * Ends the control flow graph at the given instruction. This method checks that there is an + * existing frame for the next instruction, if any. * * @param insnIndex an instruction index. * @throws AnalyzerException if 'insnIndex' is not the last instruction and there is no frame at diff --git a/asm-util/src/test/java/org/objectweb/asm/util/CheckAnnotationAdapterTest.java b/asm-util/src/test/java/org/objectweb/asm/util/CheckAnnotationAdapterTest.java index 377150aea..8276a0f80 100644 --- a/asm-util/src/test/java/org/objectweb/asm/util/CheckAnnotationAdapterTest.java +++ b/asm-util/src/test/java/org/objectweb/asm/util/CheckAnnotationAdapterTest.java @@ -32,7 +32,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.test.AsmTest; @@ -41,7 +40,7 @@ import org.objectweb.asm.test.AsmTest; * * @author Eric Bruneton */ -class CheckAnnotationAdapterTest extends AsmTest implements Opcodes { +class CheckAnnotationAdapterTest extends AsmTest { @Test void testVisit_illegalAnnotationName() { diff --git a/asm-util/src/test/java/org/objectweb/asm/util/CheckFieldAdapterTest.java b/asm-util/src/test/java/org/objectweb/asm/util/CheckFieldAdapterTest.java index 5f4e525a5..ae332d576 100644 --- a/asm-util/src/test/java/org/objectweb/asm/util/CheckFieldAdapterTest.java +++ b/asm-util/src/test/java/org/objectweb/asm/util/CheckFieldAdapterTest.java @@ -33,7 +33,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.TypeReference; import org.objectweb.asm.test.AsmTest; @@ -42,7 +41,7 @@ import org.objectweb.asm.test.AsmTest; * * @author Eric Bruneton */ -class CheckFieldAdapterTest extends AsmTest implements Opcodes { +class CheckFieldAdapterTest extends AsmTest { @Test void testConstructor() { diff --git a/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java b/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java index 2f28d45cb..0de826e6f 100644 --- a/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java +++ b/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java @@ -56,7 +56,7 @@ import org.objectweb.asm.tree.analysis.Frame; * * @author Eric Bruneton */ -class CheckFrameAnalyzerTest extends AsmTest implements Opcodes { +class CheckFrameAnalyzerTest extends AsmTest { private static final String CLASS_NAME = "C"; diff --git a/asm-util/src/test/java/org/objectweb/asm/util/CheckRecordComponentAdapterTest.java b/asm-util/src/test/java/org/objectweb/asm/util/CheckRecordComponentAdapterTest.java index 7724a8a1f..d406701b0 100644 --- a/asm-util/src/test/java/org/objectweb/asm/util/CheckRecordComponentAdapterTest.java +++ b/asm-util/src/test/java/org/objectweb/asm/util/CheckRecordComponentAdapterTest.java @@ -33,7 +33,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.TypeReference; import org.objectweb.asm.test.AsmTest; @@ -42,7 +41,7 @@ import org.objectweb.asm.test.AsmTest; * * @author Eric Bruneton */ -class CheckRecordComponentAdapterTest extends AsmTest implements Opcodes { +class CheckRecordComponentAdapterTest extends AsmTest { @Test void testConstructor() { -- GitLab From 92b2043277824b6d418cc091e78dcf9e8e128c2e Mon Sep 17 00:00:00 2001 From: Eric Bruneton Date: Sun, 22 May 2022 16:25:32 +0200 Subject: [PATCH 3/3] Fix typos. --- .../org/objectweb/asm/util/CheckFrameAnalyzer.java | 14 +++++++------- .../objectweb/asm/util/CheckFrameAnalyzerTest.java | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java b/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java index 2726229aa..c2eaf8ea1 100644 --- a/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java +++ b/asm-util/src/main/java/org/objectweb/asm/util/CheckFrameAnalyzer.java @@ -79,14 +79,14 @@ import org.objectweb.asm.tree.analysis.Value; * f must be sub types of the corresponding types in the existing frame * f0 (otherwise an exception is thrown). In the second case, f0 is * simply set to the value of f. - *
  • If the control can continue to some instruction j (e.g. if i is an - * IF_EQ, for instance): an existing stack map frame f0 (coming from the first - * step) is expected at j, which must be compatible with f (as defined - * previously). + *
  • If the control flow can continue to some instruction j (e.g. if i + * is an IF_EQ, for instance): an existing stack map frame f0 (coming from the + * first step) is expected at j, which must be compatible with f (as + * defined previously). * - * The sequential loop over the instruction is done in {@link #init}, which is called from the - * {@link Analyzer#analyze} method. Cases where the control flow cannot continue to the next - * instruction are handled in {@link #endControlFlow}. Cases where the control flow can + * The sequential loop over the instructions is done in {@link #init}, which is called from + * the {@link Analyzer#analyze} method. Cases where the control flow cannot continue to the + * next instruction are handled in {@link #endControlFlow}. Cases where the control flow can * continue to the next instruction, or jump to another instruction, are handled in {@link * #checkFrame}. This method checks that an existing stack map frame is present when required, * and checks the stack map frames compatibility with {@link #checkMerge}. diff --git a/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java b/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java index 0de826e6f..b6b4b553f 100644 --- a/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java +++ b/asm-util/src/test/java/org/objectweb/asm/util/CheckFrameAnalyzerTest.java @@ -52,7 +52,7 @@ import org.objectweb.asm.tree.analysis.BasicVerifier; import org.objectweb.asm.tree.analysis.Frame; /** - * Unit tests for {@link CheckFrameanalyzer}. + * Unit tests for {@link CheckFrameAnalyzer}. * * @author Eric Bruneton */ -- GitLab