Skip to content
Commits on Source (6)
......@@ -4,7 +4,11 @@ build/
# Files generated by Eclipse
**/.classpath
**/.gitignore
**/.project
**/.settings/
**/bin/
# Files generated by IntelliJ
.idea/
out/
*.iml
......@@ -96,8 +96,8 @@ public class Analyzer<V extends Value> implements Opcodes {
* @param method the method to be analyzed.
* @return the symbolic state of the execution stack frame at each bytecode instruction of the
* method. The size of the returned array is equal to the number of instructions (and labels)
* of the method. A given frame is <tt>null</tt> if and only if the corresponding instruction
* cannot be reached (dead code).
* of the method. A given frame is {@literal null} if and only if the corresponding
* instruction cannot be reached (dead code).
* @throws AnalyzerException if a problem occurs during the analysis.
*/
@SuppressWarnings("unchecked")
......@@ -190,10 +190,12 @@ public class Analyzer<V extends Value> implements Opcodes {
if (insnNode instanceof JumpInsnNode) {
JumpInsnNode jumpInsn = (JumpInsnNode) insnNode;
if (insnOpcode != GOTO && insnOpcode != JSR) {
currentFrame.initJumpTarget(insnOpcode, /* target = */ null);
merge(insnIndex + 1, currentFrame, subroutine);
newControlFlowEdge(insnIndex, insnIndex + 1);
}
int jumpInsnIndex = insnList.indexOf(jumpInsn.label);
currentFrame.initJumpTarget(insnOpcode, jumpInsn.label);
if (insnOpcode == JSR) {
merge(
jumpInsnIndex,
......@@ -206,20 +208,26 @@ public class Analyzer<V extends Value> implements Opcodes {
} else if (insnNode instanceof LookupSwitchInsnNode) {
LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) insnNode;
int targetInsnIndex = insnList.indexOf(lookupSwitchInsn.dflt);
currentFrame.initJumpTarget(insnOpcode, lookupSwitchInsn.dflt);
merge(targetInsnIndex, currentFrame, subroutine);
newControlFlowEdge(insnIndex, targetInsnIndex);
for (int i = 0; i < lookupSwitchInsn.labels.size(); ++i) {
targetInsnIndex = insnList.indexOf(lookupSwitchInsn.labels.get(i));
LabelNode label = lookupSwitchInsn.labels.get(i);
targetInsnIndex = insnList.indexOf(label);
currentFrame.initJumpTarget(insnOpcode, label);
merge(targetInsnIndex, currentFrame, subroutine);
newControlFlowEdge(insnIndex, targetInsnIndex);
}
} else if (insnNode instanceof TableSwitchInsnNode) {
TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) insnNode;
int targetInsnIndex = insnList.indexOf(tableSwitchInsn.dflt);
currentFrame.initJumpTarget(insnOpcode, tableSwitchInsn.dflt);
merge(targetInsnIndex, currentFrame, subroutine);
newControlFlowEdge(insnIndex, targetInsnIndex);
for (int i = 0; i < tableSwitchInsn.labels.size(); ++i) {
targetInsnIndex = insnList.indexOf(tableSwitchInsn.labels.get(i));
LabelNode label = tableSwitchInsn.labels.get(i);
currentFrame.initJumpTarget(insnOpcode, label);
targetInsnIndex = insnList.indexOf(label);
merge(targetInsnIndex, currentFrame, subroutine);
newControlFlowEdge(insnIndex, targetInsnIndex);
}
......@@ -263,8 +271,7 @@ public class Analyzer<V extends Value> implements Opcodes {
List<TryCatchBlockNode> insnHandlers = handlers[insnIndex];
if (insnHandlers != null) {
for (int i = 0; i < insnHandlers.size(); ++i) {
TryCatchBlockNode tryCatchBlock = insnHandlers.get(i);
for (TryCatchBlockNode tryCatchBlock : insnHandlers) {
Type catchType;
if (tryCatchBlock.type == null) {
catchType = Type.getObjectType("java/lang/Throwable");
......@@ -282,7 +289,8 @@ public class Analyzer<V extends Value> implements Opcodes {
} catch (AnalyzerException e) {
throw new AnalyzerException(
e.node, "Error at instruction " + insnIndex + ": " + e.getMessage(), e);
} catch (Exception e) {
} catch (RuntimeException e) {
// DontCheck(IllegalCatch): can't be fixed, for backward compatibility.
throw new AnalyzerException(
insnNode, "Error at instruction " + insnIndex + ": " + e.getMessage(), e);
}
......@@ -305,52 +313,54 @@ public class Analyzer<V extends Value> implements Opcodes {
private void findSubroutine(
final int insnIndex, final Subroutine subroutine, final List<AbstractInsnNode> jsrInsns)
throws AnalyzerException {
int currentInsnIndex = insnIndex;
while (true) {
ArrayList<Integer> instructionIndicesToProcess = new ArrayList<Integer>();
instructionIndicesToProcess.add(insnIndex);
while (!instructionIndicesToProcess.isEmpty()) {
int currentInsnIndex =
instructionIndicesToProcess.remove(instructionIndicesToProcess.size() - 1);
if (currentInsnIndex < 0 || currentInsnIndex >= insnListSize) {
throw new AnalyzerException(null, "Execution can fall off the end of the code");
}
if (subroutines[currentInsnIndex] != null) {
return;
continue;
}
subroutines[currentInsnIndex] = new Subroutine(subroutine);
AbstractInsnNode currentInsn = insnList.get(currentInsnIndex);
// Call findSubroutine recursively on the normal successors of currentInsn.
// Push the normal successors of currentInsn onto instructionIndicesToProcess.
if (currentInsn instanceof JumpInsnNode) {
if (currentInsn.getOpcode() == JSR) {
// Do not follow a jsr, it leads to another subroutine!
jsrInsns.add(currentInsn);
} else {
JumpInsnNode jumpInsn = (JumpInsnNode) currentInsn;
findSubroutine(insnList.indexOf(jumpInsn.label), subroutine, jsrInsns);
instructionIndicesToProcess.add(insnList.indexOf(jumpInsn.label));
}
} else if (currentInsn instanceof TableSwitchInsnNode) {
TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) currentInsn;
findSubroutine(insnList.indexOf(tableSwitchInsn.dflt), subroutine, jsrInsns);
for (int i = tableSwitchInsn.labels.size() - 1; i >= 0; --i) {
LabelNode l = tableSwitchInsn.labels.get(i);
findSubroutine(insnList.indexOf(l), subroutine, jsrInsns);
LabelNode labelNode = tableSwitchInsn.labels.get(i);
instructionIndicesToProcess.add(insnList.indexOf(labelNode));
}
} else if (currentInsn instanceof LookupSwitchInsnNode) {
LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) currentInsn;
findSubroutine(insnList.indexOf(lookupSwitchInsn.dflt), subroutine, jsrInsns);
for (int i = lookupSwitchInsn.labels.size() - 1; i >= 0; --i) {
LabelNode l = lookupSwitchInsn.labels.get(i);
findSubroutine(insnList.indexOf(l), subroutine, jsrInsns);
LabelNode labelNode = lookupSwitchInsn.labels.get(i);
instructionIndicesToProcess.add(insnList.indexOf(labelNode));
}
}
// Call findSubroutine recursively on the exception handler successors of currentInsn.
// Push the exception handler successors of currentInsn onto instructionIndicesToProcess.
List<TryCatchBlockNode> insnHandlers = handlers[currentInsnIndex];
if (insnHandlers != null) {
for (int i = 0; i < insnHandlers.size(); ++i) {
TryCatchBlockNode tryCatchBlock = insnHandlers.get(i);
findSubroutine(insnList.indexOf(tryCatchBlock.handler), subroutine, jsrInsns);
for (TryCatchBlockNode tryCatchBlock : insnHandlers) {
instructionIndicesToProcess.add(insnList.indexOf(tryCatchBlock.handler));
}
}
// If currentInsn does not fall through to the next instruction, return.
// Push the next instruction, if the control flow can go from currentInsn to the next.
switch (currentInsn.getOpcode()) {
case GOTO:
case RET:
......@@ -363,11 +373,11 @@ public class Analyzer<V extends Value> implements Opcodes {
case ARETURN:
case RETURN:
case ATHROW:
return;
break;
default:
instructionIndicesToProcess.add(currentInsnIndex + 1);
break;
}
currentInsnIndex++;
}
}
......@@ -389,12 +399,12 @@ public class Analyzer<V extends Value> implements Opcodes {
currentLocal++;
}
Type[] argumentTypes = Type.getArgumentTypes(method.desc);
for (int i = 0; i < argumentTypes.length; ++i) {
for (Type argumentType : argumentTypes) {
frame.setLocal(
currentLocal,
interpreter.newParameterValue(isInstanceMethod, currentLocal, argumentTypes[i]));
interpreter.newParameterValue(isInstanceMethod, currentLocal, argumentType));
currentLocal++;
if (argumentTypes[i].getSize() == 2) {
if (argumentType.getSize() == 2) {
frame.setLocal(currentLocal, interpreter.newEmptyValue(currentLocal));
currentLocal++;
}
......@@ -412,7 +422,7 @@ public class Analyzer<V extends Value> implements Opcodes {
*
* @return the symbolic state of the execution stack frame at each bytecode instruction of the
* method. The size of the returned array is equal to the number of instructions (and labels)
* of the method. A given frame is <tt>null</tt> if the corresponding instruction cannot be
* of the method. A given frame is {@literal null} if the corresponding instruction cannot be
* reached, or if an error occurred during the analysis of the method.
*/
public Frame<V>[] getFrames() {
......@@ -444,12 +454,12 @@ public class Analyzer<V extends Value> implements Opcodes {
/**
* Constructs a new frame with the given size.
*
* @param nLocals the maximum number of local variables of the frame.
* @param nStack the maximum stack size of the frame.
* @param numLocals the maximum number of local variables of the frame.
* @param numStack the maximum stack size of the frame.
* @return the created frame.
*/
protected Frame<V> newFrame(final int nLocals, final int nStack) {
return new Frame<V>(nLocals, nStack);
protected Frame<V> newFrame(final int numLocals, final int numStack) {
return new Frame<V>(numLocals, numStack);
}
/**
......
......@@ -50,7 +50,7 @@ import org.objectweb.asm.tree.TypeInsnNode;
public class BasicInterpreter extends Interpreter<BasicValue> implements Opcodes {
/**
* Special type used for the <tt>null</tt> literal. This is an object reference type with
* Special type used for the {@literal null} literal. This is an object reference type with
* descriptor 'Lnull;'.
*/
public static final Type NULL_TYPE = Type.getObjectType("null");
......@@ -61,7 +61,7 @@ public class BasicInterpreter extends Interpreter<BasicValue> implements Opcodes
* version.
*/
public BasicInterpreter() {
super(ASM6);
super(ASM7);
if (getClass() != BasicInterpreter.class) {
throw new IllegalStateException();
}
......@@ -72,7 +72,7 @@ public class BasicInterpreter extends Interpreter<BasicValue> implements Opcodes
*
* @param api the ASM API version supported by this interpreter. Must be one of {@link
* org.objectweb.asm.Opcodes#ASM4}, {@link org.objectweb.asm.Opcodes#ASM5}, {@link
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7_EXPERIMENTAL}.
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7}.
*/
protected BasicInterpreter(final int api) {
super(api);
......
......@@ -59,7 +59,7 @@ public class BasicValue implements Value {
/** A return address value (produced by a jsr instruction). */
public static final BasicValue RETURNADDRESS_VALUE = new BasicValue(Type.VOID_TYPE);
/** The {@link Type} of this value, or <tt>null</tt> for uninitialized values. */
/** The {@link Type} of this value, or {@literal null} for uninitialized values. */
private final Type type;
/**
......@@ -80,6 +80,7 @@ public class BasicValue implements Value {
return type;
}
@Override
public int getSize() {
return type == Type.LONG_TYPE || type == Type.DOUBLE_TYPE ? 2 : 1;
}
......
......@@ -47,7 +47,7 @@ public class BasicVerifier extends BasicInterpreter {
* use this constructor</i>. Instead, they must use the {@link #BasicVerifier(int)} version.
*/
public BasicVerifier() {
super(ASM6);
super(ASM7);
if (getClass() != BasicVerifier.class) {
throw new IllegalStateException();
}
......@@ -58,7 +58,7 @@ public class BasicVerifier extends BasicInterpreter {
*
* @param api the ASM API version supported by this interpreter. Must be one of {@link
* org.objectweb.asm.Opcodes#ASM4}, {@link org.objectweb.asm.Opcodes#ASM5}, {@link
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7_EXPERIMENTAL}.
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7}.
*/
protected BasicVerifier(final int api) {
super(api);
......@@ -375,9 +375,9 @@ public class BasicVerifier extends BasicInterpreter {
throws AnalyzerException {
int opcode = insn.getOpcode();
if (opcode == MULTIANEWARRAY) {
for (int i = 0; i < values.size(); ++i) {
if (!BasicValue.INT_VALUE.equals(values.get(i))) {
throw new AnalyzerException(insn, null, BasicValue.INT_VALUE, values.get(i));
for (BasicValue value : values) {
if (!BasicValue.INT_VALUE.equals(value)) {
throw new AnalyzerException(insn, null, BasicValue.INT_VALUE, value);
}
}
} else {
......
......@@ -34,6 +34,7 @@ import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
......@@ -49,43 +50,43 @@ import org.objectweb.asm.tree.VarInsnNode;
public class Frame<V extends Value> {
/**
* The expected return type of the analyzed method, or <tt>null</tt> if the method returns void.
* The expected return type of the analyzed method, or {@literal null} if the method returns void.
*/
private V returnValue;
/**
* The local variables and the operand stack of this frame. The first {@link #nLocals} elements
* correspond to the local variables. The following {@link #nStack} elements correspond to the
* The local variables and the operand stack of this frame. The first {@link #numLocals} elements
* correspond to the local variables. The following {@link #numStack} elements correspond to the
* operand stack.
*/
private V[] values;
/** The number of local variables of this frame. */
private int nLocals;
private int numLocals;
/** The number of elements in the operand stack. */
private int nStack;
private int numStack;
/**
* Constructs a new frame with the given size.
*
* @param nLocals the maximum number of local variables of the frame.
* @param nStack the maximum stack size of the frame.
* @param numLocals the maximum number of local variables of the frame.
* @param numStack the maximum stack size of the frame.
*/
@SuppressWarnings("unchecked")
public Frame(final int nLocals, final int nStack) {
this.values = (V[]) new Value[nLocals + nStack];
this.nLocals = nLocals;
public Frame(final int numLocals, final int numStack) {
this.values = (V[]) new Value[numLocals + numStack];
this.numLocals = numLocals;
}
/**
* Constructs a copy of the given.
* Constructs a copy of the given Frame.
*
* @param frame a frame.
*/
public Frame(final Frame<? extends V> frame) {
this(frame.nLocals, frame.values.length - frame.nLocals);
init(frame);
this(frame.numLocals, frame.values.length - frame.numLocals);
init(frame); // NOPMD(ConstructorCallsOverridableMethod): can't fix for backward compatibility.
}
/**
......@@ -97,14 +98,34 @@ public class Frame<V extends Value> {
public Frame<V> init(final Frame<? extends V> frame) {
returnValue = frame.returnValue;
System.arraycopy(frame.values, 0, values, 0, values.length);
nStack = frame.nStack;
numStack = frame.numStack;
return this;
}
/**
* Initializes a frame corresponding to the target or to the successor of a jump instruction. This
* method is called by {@link Analyzer#analyze(String, org.objectweb.asm.tree.MethodNode)} while
* interpreting jump instructions. It is called once for each possible target of the jump
* instruction, and once for its successor instruction (except for GOTO and JSR), before the frame
* is merged with the existing frame at this location. The default implementation of this method
* does nothing.
*
* <p>Overriding this method and changing the frame values allows implementing branch-sensitive
* analyses.
*
* @param opcode the opcode of the jump instruction. Can be IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE,
* IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE,
* GOTO, JSR, IFNULL, IFNONNULL, TABLESWITCH or LOOKUPSWITCH.
* @param target a target of the jump instruction this frame corresponds to, or {@literal null} if
* this frame corresponds to the successor of the jump instruction (i.e. the next instruction
* in the instructions sequence).
*/
public void initJumpTarget(final int opcode, final LabelNode target) {}
/**
* Sets the expected return type of the analyzed method.
*
* @param v the expected return type of the analyzed method, or <tt>null</tt> if the method
* @param v the expected return type of the analyzed method, or {@literal null} if the method
* returns void.
*/
public void setReturn(final V v) {
......@@ -117,7 +138,7 @@ public class Frame<V extends Value> {
* @return the maximum number of local variables of this frame.
*/
public int getLocals() {
return nLocals;
return numLocals;
}
/**
......@@ -126,7 +147,7 @@ public class Frame<V extends Value> {
* @return the maximum stack size of this frame.
*/
public int getMaxStackSize() {
return values.length - nLocals;
return values.length - numLocals;
}
/**
......@@ -137,7 +158,7 @@ public class Frame<V extends Value> {
* @throws IndexOutOfBoundsException if the variable does not exist.
*/
public V getLocal(final int index) {
if (index >= nLocals) {
if (index >= numLocals) {
throw new IndexOutOfBoundsException("Trying to access an inexistant local variable");
}
return values[index];
......@@ -151,7 +172,7 @@ public class Frame<V extends Value> {
* @throws IndexOutOfBoundsException if the variable does not exist.
*/
public void setLocal(final int index, final V value) {
if (index >= nLocals) {
if (index >= numLocals) {
throw new IndexOutOfBoundsException("Trying to access an inexistant local variable " + index);
}
values[index] = value;
......@@ -164,7 +185,7 @@ public class Frame<V extends Value> {
* @return the number of values in the operand stack of this frame.
*/
public int getStackSize() {
return nStack;
return numStack;
}
/**
......@@ -175,7 +196,7 @@ public class Frame<V extends Value> {
* @throws IndexOutOfBoundsException if the operand stack slot does not exist.
*/
public V getStack(final int index) {
return values[nLocals + index];
return values[numLocals + index];
}
/**
......@@ -186,12 +207,12 @@ public class Frame<V extends Value> {
* @throws IndexOutOfBoundsException if the stack slot does not exist.
*/
public void setStack(final int index, final V value) throws IndexOutOfBoundsException {
values[nLocals + index] = value;
values[numLocals + index] = value;
}
/** Clears the operand stack of this frame. */
public void clearStack() {
nStack = 0;
numStack = 0;
}
/**
......@@ -201,10 +222,10 @@ public class Frame<V extends Value> {
* @throws IndexOutOfBoundsException if the operand stack is empty.
*/
public V pop() {
if (nStack == 0) {
if (numStack == 0) {
throw new IndexOutOfBoundsException("Cannot pop operand off an empty stack.");
}
return values[nLocals + (--nStack)];
return values[numLocals + (--numStack)];
}
/**
......@@ -214,10 +235,10 @@ public class Frame<V extends Value> {
* @throws IndexOutOfBoundsException if the operand stack is full.
*/
public void push(final V value) {
if (nLocals + nStack >= values.length) {
if (numLocals + numStack >= values.length) {
throw new IndexOutOfBoundsException("Insufficient maximum stack size.");
}
values[nLocals + (nStack++)] = value;
values[numLocals + (numStack++)] = value;
}
/**
......@@ -647,17 +668,17 @@ public class Frame<V extends Value> {
*
* @param frame a frame. This frame is left unchanged by this method.
* @param interpreter the interpreter used to merge values.
* @return <tt>true</tt> if this frame has been changed as a result of the merge operation, or
* <tt>false</tt> otherwise.
* @return {@literal true} if this frame has been changed as a result of the merge operation, or
* {@literal false} otherwise.
* @throws AnalyzerException if the frames have incompatible sizes.
*/
public boolean merge(final Frame<? extends V> frame, final Interpreter<V> interpreter)
throws AnalyzerException {
if (nStack != frame.nStack) {
if (numStack != frame.numStack) {
throw new AnalyzerException(null, "Incompatible stack heights");
}
boolean changed = false;
for (int i = 0; i < nLocals + nStack; ++i) {
for (int i = 0; i < numLocals + numStack; ++i) {
V v = interpreter.merge(values[i], frame.values[i]);
if (!v.equals(values[i])) {
values[i] = v;
......@@ -675,12 +696,12 @@ public class Frame<V extends Value> {
* @param localsUsed the local variables that are read or written by the subroutine. The i-th
* element is true if and only if the local variable at index i is read or written by the
* subroutine.
* @return <tt>true</tt> if this frame has been changed as a result of the merge operation, or
* <tt>false</tt> otherwise.
* @return {@literal true} if this frame has been changed as a result of the merge operation, or
* {@literal false} otherwise.
*/
public boolean merge(final Frame<? extends V> frame, final boolean[] localsUsed) {
boolean changed = false;
for (int i = 0; i < nLocals; ++i) {
for (int i = 0; i < numLocals; ++i) {
if (!localsUsed[i] && !values[i].equals(frame.values[i])) {
values[i] = frame.values[i];
changed = true;
......
......@@ -47,7 +47,7 @@ public abstract class Interpreter<V extends Value> {
/**
* The ASM API version supported by this interpreter. The value of this field must be one of
* {@link org.objectweb.asm.Opcodes#ASM4}, {@link org.objectweb.asm.Opcodes#ASM5}, {@link
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7_EXPERIMENTAL}.
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7}.
*/
protected final int api;
......@@ -56,7 +56,7 @@ public abstract class Interpreter<V extends Value> {
*
* @param api the ASM API version supported by this interpreter. Must be one of {@link
* org.objectweb.asm.Opcodes#ASM4}, {@link org.objectweb.asm.Opcodes#ASM5}, {@link
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7_EXPERIMENTAL}.
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7}.
*/
protected Interpreter(final int api) {
this.api = api;
......@@ -74,7 +74,7 @@ public abstract class Interpreter<V extends Value> {
* Interpreter#newExceptionValue(TryCatchBlockNode, Frame, Type)} to distinguish different types
* of new value.
*
* @param type a primitive or reference type, or <tt>null</tt> to represent an uninitialized
* @param type a primitive or reference type, or {@literal null} to represent an uninitialized
* value.
* @return a value that represents the given type. The size of the returned value must be equal to
* the size of the given type.
......@@ -87,7 +87,7 @@ public abstract class Interpreter<V extends Value> {
*
* <p>By default, calls <code>newValue(type)</code>.
*
* @param isInstanceMethod <tt>true</tt> if the method is non-static.
* @param isInstanceMethod {@literal true} if the method is non-static.
* @param local the local variable index.
* @param type a primitive or reference type.
* @return a value that represents the given type. The size of the returned value must be equal to
......@@ -135,8 +135,8 @@ public abstract class Interpreter<V extends Value> {
* @param tryCatchBlockNode the exception handler.
* @param handlerFrame the exception handler frame.
* @param exceptionType the exception type handled by this handler.
* @return a value that represents the given <tt>exceptionType</tt>. The size of the returned
* value must be equal to 1.
* @return a value that represents the given {@code exceptionType}. The size of the returned value
* must be equal to 1.
*/
public V newExceptionValue(
final TryCatchBlockNode tryCatchBlockNode,
......@@ -169,7 +169,7 @@ public abstract class Interpreter<V extends Value> {
* @param insn the bytecode instruction to be interpreted.
* @param value the value that must be moved by the instruction.
* @return the result of the interpretation of the given instruction. The returned value must be
* <tt>equal</tt> to the given value.
* {@code equal} to the given value.
* @throws AnalyzerException if an error occurred during the interpretation.
*/
public abstract V copyOperation(AbstractInsnNode insn, V value) throws AnalyzerException;
......@@ -261,8 +261,8 @@ public abstract class Interpreter<V extends Value> {
*
* @param value1 a value.
* @param value2 another value.
* @return the merged value. If the merged value is equal to <tt>value1</tt>, this method
* <i>must</i> return <tt>value1</tt>.
* @return the merged value. If the merged value is equal to {@code value1}, this method
* <i>must</i> return {@code value1}.
*/
public abstract V merge(V value1, V value2);
}
......@@ -93,7 +93,7 @@ public class SimpleVerifier extends BasicVerifier {
final Type currentSuperClass,
final List<Type> currentClassInterfaces,
final boolean isInterface) {
this(ASM6, currentClass, currentSuperClass, currentClassInterfaces, isInterface);
this(ASM7, currentClass, currentSuperClass, currentClassInterfaces, isInterface);
if (getClass() != SimpleVerifier.class) {
throw new IllegalStateException();
}
......@@ -105,7 +105,7 @@ public class SimpleVerifier extends BasicVerifier {
*
* @param api the ASM API version supported by this verifier. Must be one of {@link
* org.objectweb.asm.Opcodes#ASM4}, {@link org.objectweb.asm.Opcodes#ASM5}, {@link
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7_EXPERIMENTAL}.
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7}.
* @param currentClass the type of the class to be verified.
* @param currentSuperClass the type of the super class of the class to be verified.
* @param currentClassInterfaces the types of the interfaces directly implemented by the class to
......@@ -204,7 +204,17 @@ public class SimpleVerifier extends BasicVerifier {
if (type.equals(NULL_TYPE)) {
return true;
} else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return isAssignableFrom(expectedType, type);
if (isAssignableFrom(expectedType, type)) {
return true;
} else if (getClass(expectedType).isInterface()) {
// The merge of class or interface types can only yield class types (because it is not
// possible in general to find an unambiguous common super interface, due to multiple
// inheritance). Because of this limitation, we need to relax the subtyping check here
// if 'value' is an interface.
return Object.class.isAssignableFrom(getClass(type));
} else {
return false;
}
} else {
return false;
}
......@@ -246,11 +256,11 @@ public class SimpleVerifier extends BasicVerifier {
}
do {
if (type1 == null || isInterface(type1)) {
return newValue(Type.getObjectType("java/lang/Object"), numDimensions);
return newArrayValue(Type.getObjectType("java/lang/Object"), numDimensions);
}
type1 = getSuperClass(type1);
if (isAssignableFrom(type1, type2)) {
return newValue(type1, numDimensions);
return newArrayValue(type1, numDimensions);
}
} while (true);
}
......@@ -259,7 +269,7 @@ public class SimpleVerifier extends BasicVerifier {
return value1;
}
private BasicValue newValue(final Type type, final int dimensions) {
private BasicValue newArrayValue(final Type type, final int dimensions) {
if (dimensions == 0) {
return newValue(type);
} else {
......@@ -281,7 +291,7 @@ public class SimpleVerifier extends BasicVerifier {
* @return whether 'type' corresponds to an interface.
*/
protected boolean isInterface(final Type type) {
if (currentClass != null && type.equals(currentClass)) {
if (currentClass != null && currentClass.equals(type)) {
return isInterface;
}
return getClass(type).isInterface();
......@@ -296,7 +306,7 @@ public class SimpleVerifier extends BasicVerifier {
* @return the type corresponding to the super class of 'type'.
*/
protected Type getSuperClass(final Type type) {
if (currentClass != null && type.equals(currentClass)) {
if (currentClass != null && currentClass.equals(type)) {
return currentSuperClass;
}
Class<?> superClass = getClass(type).getSuperclass();
......@@ -319,7 +329,7 @@ public class SimpleVerifier extends BasicVerifier {
if (type1.equals(type2)) {
return true;
}
if (currentClass != null && type1.equals(currentClass)) {
if (currentClass != null && currentClass.equals(type1)) {
if (getSuperClass(type2) == null) {
return false;
} else {
......@@ -329,13 +339,12 @@ public class SimpleVerifier extends BasicVerifier {
return isAssignableFrom(type1, getSuperClass(type2));
}
}
if (currentClass != null && type2.equals(currentClass)) {
if (currentClass != null && currentClass.equals(type2)) {
if (isAssignableFrom(type1, currentSuperClass)) {
return true;
}
if (currentClassInterfaces != null) {
for (int i = 0; i < currentClassInterfaces.size(); ++i) {
Type currentClassInterface = currentClassInterfaces.get(i);
for (Type currentClassInterface : currentClassInterfaces) {
if (isAssignableFrom(type1, currentClassInterface)) {
return true;
}
......@@ -343,11 +352,7 @@ public class SimpleVerifier extends BasicVerifier {
}
return false;
}
Class<?> class1 = getClass(type1);
if (class1.isInterface()) {
class1 = Object.class;
}
return class1.isAssignableFrom(getClass(type2));
return getClass(type1).isAssignableFrom(getClass(type2));
}
/**
......
......@@ -41,12 +41,12 @@ import java.util.Set;
*/
final class SmallSet<T> extends AbstractSet<T> {
/** The first element of this set, maybe <tt>null</tt>. */
/** The first element of this set, maybe {@literal null}. */
private final T element1;
/**
* The second element of this set, maybe <tt>null</tt>. If {@link #element1} is <tt>null</tt> then
* this field must be <tt>null</tt>, otherwise it must be different from {@link #element1}.
* The second element of this set, maybe {@literal null}. If {@link #element1} is {@literal null}
* then this field must be {@literal null}, otherwise it must be different from {@link #element1}.
*/
private final T element2;
......@@ -90,43 +90,6 @@ final class SmallSet<T> extends AbstractSet<T> {
return new IteratorImpl<T>(element1, element2);
}
static class IteratorImpl<T> implements Iterator<T> {
/** The next element to return in {@link #next}. Maybe <tt>null</tt>. */
private T firstElement;
/**
* The element to return in {@link #next}, after {@link #firstElement} is returned. If {@link
* #firstElement} is <tt>null</tt> then this field must be <tt>null</tt>, otherwise it must be
* different from {@link #firstElement}.
*/
private T secondElement;
IteratorImpl(final T firstElement, final T secondElement) {
this.firstElement = firstElement;
this.secondElement = secondElement;
}
public boolean hasNext() {
return firstElement != null;
}
public T next() {
if (firstElement == null) {
throw new NoSuchElementException();
}
T element = firstElement;
firstElement = secondElement;
secondElement = null;
return element;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
@Override
public int size() {
return element1 == null ? 0 : (element2 == null ? 1 : 2);
......@@ -187,4 +150,43 @@ final class SmallSet<T> extends AbstractSet<T> {
}
return result;
}
static class IteratorImpl<T> implements Iterator<T> {
/** The next element to return in {@link #next}. Maybe {@literal null}. */
private T firstElement;
/**
* The element to return in {@link #next}, after {@link #firstElement} is returned. If {@link
* #firstElement} is {@literal null} then this field must be {@literal null}, otherwise it must
* be different from {@link #firstElement}.
*/
private T secondElement;
IteratorImpl(final T firstElement, final T secondElement) {
this.firstElement = firstElement;
this.secondElement = secondElement;
}
@Override
public boolean hasNext() {
return firstElement != null;
}
@Override
public T next() {
if (firstElement == null) {
throw new NoSuchElementException();
}
T element = firstElement;
firstElement = secondElement;
secondElement = null;
return element;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}
......@@ -51,7 +51,7 @@ public class SourceInterpreter extends Interpreter<SourceValue> implements Opcod
* version.
*/
public SourceInterpreter() {
super(ASM6);
super(ASM7);
if (getClass() != SourceInterpreter.class) {
throw new IllegalStateException();
}
......@@ -62,7 +62,7 @@ public class SourceInterpreter extends Interpreter<SourceValue> implements Opcod
*
* @param api the ASM API version supported by this interpreter. Must be one of {@link
* org.objectweb.asm.Opcodes#ASM4}, {@link org.objectweb.asm.Opcodes#ASM5}, {@link
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7_EXPERIMENTAL}.
* org.objectweb.asm.Opcodes#ASM6} or {@link org.objectweb.asm.Opcodes#ASM7}.
*/
protected SourceInterpreter(final int api) {
super(api);
......@@ -95,6 +95,7 @@ public class SourceInterpreter extends Interpreter<SourceValue> implements Opcod
break;
default:
size = 1;
break;
}
return new SourceValue(size, insn);
}
......@@ -123,6 +124,7 @@ public class SourceInterpreter extends Interpreter<SourceValue> implements Opcod
break;
default:
size = 1;
break;
}
return new SourceValue(size, insn);
}
......@@ -154,6 +156,7 @@ public class SourceInterpreter extends Interpreter<SourceValue> implements Opcod
break;
default:
size = 1;
break;
}
return new SourceValue(size, insn);
}
......
......@@ -45,7 +45,7 @@ public class SourceValue implements Value {
/**
* The instructions that can produce this value. For example, for the Java code below, the
* instructions that can produce the value of <tt>i</tt> at line 5 are the two ISTORE instructions
* instructions that can produce the value of {@code i} at line 5 are the two ISTORE instructions
* at line 1 and 3:
*
* <pre>
......@@ -98,6 +98,7 @@ public class SourceValue implements Value {
* @return the size of this value, in 32 bits words. This size is 1 for byte, boolean, char,
* short, int, float, object and array types, and 2 for long and double.
*/
@Override
public int getSize() {
return size;
}
......
......@@ -33,8 +33,9 @@ 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 java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
......@@ -60,14 +61,14 @@ public class BasicInterpreterTest extends AsmTest {
* Tests that stack map frames are correctly merged when a JSR instruction can be reached from two
* different control flow paths, with different local variable types (#316204).
*
* @throws IOException
* @throws AnalyzerException
* @throws IOException if the test class can't be loaded.
* @throws AnalyzerException if the test class can't be analyzed.
*/
@Test
public void testMergeWithJsrReachableFromTwoDifferentPaths()
throws IOException, AnalyzerException {
ClassReader classReader =
new ClassReader(new FileInputStream("src/test/resources/Issue316204.class"));
new ClassReader(Files.newInputStream(Paths.get("src/test/resources/Issue316204.class")));
ClassNode classNode = new ClassNode();
classReader.accept(classNode, 0);
Analyzer<BasicValue> analyzer = new Analyzer<>(new BasicInterpreter());
......@@ -80,7 +81,7 @@ public class BasicInterpreterTest extends AsmTest {
* does not follow its required contract (namely that if the merge result is equal to the first
* argument, the first argument should be returned - see #316326).
*
* @throws AnalyzerException
* @throws AnalyzerException if the test class can't be analyzed.
*/
@Test
public void testAnalyzeWithBadInterpreter() throws AnalyzerException {
......@@ -89,7 +90,7 @@ public class BasicInterpreterTest extends AsmTest {
for (MethodNode methodNode : classNode.methods) {
Analyzer<BasicValue> analyzer =
new Analyzer<BasicValue>(
new BasicInterpreter(Opcodes.ASM7_EXPERIMENTAL) {
new BasicInterpreter(Opcodes.ASM7) {
@Override
public BasicValue merge(final BasicValue value1, final BasicValue value2) {
return new BasicValue(super.merge(value1, value2).getType());
......@@ -103,7 +104,7 @@ public class BasicInterpreterTest extends AsmTest {
* Tests that the precompiled classes can be successfully analyzed with a BasicInterpreter, and
* that Analyzer can be subclassed to use custom frames.
*
* @throws AnalyzerException
* @throws AnalyzerException if the test class can't be analyzed.
*/
@ParameterizedTest
@MethodSource(ALL_CLASSES_AND_LATEST_API)
......@@ -115,8 +116,8 @@ public class BasicInterpreterTest extends AsmTest {
Analyzer<BasicValue> analyzer =
new Analyzer<BasicValue>(new BasicInterpreter()) {
@Override
protected Frame<BasicValue> newFrame(final int nLocals, final int nStack) {
return new CustomFrame(nLocals, nStack);
protected Frame<BasicValue> newFrame(final int numLocals, final int numStack) {
return new CustomFrame(numLocals, numStack);
}
@Override
......@@ -142,8 +143,8 @@ public class BasicInterpreterTest extends AsmTest {
private static class CustomFrame extends Frame<BasicValue> {
CustomFrame(final int nLocals, final int nStack) {
super(nLocals, nStack);
CustomFrame(final int numLocals, final int numStack) {
super(numLocals, numStack);
}
CustomFrame(final Frame<? extends BasicValue> frame) {
......
......@@ -52,7 +52,7 @@ public class BasicVerifierTest extends AsmTest {
/**
* Tests that the precompiled classes can be successfully analyzed with a BasicVerifier.
*
* @throws AnalyzerException
* @throws AnalyzerException if the test class can't be analyzed.
*/
@ParameterizedTest
@MethodSource(ALL_CLASSES_AND_LATEST_API)
......
......@@ -27,6 +27,7 @@
// THE POSSIBILITY OF SUCH DAMAGE.
package org.objectweb.asm.tree.analysis;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
......@@ -36,6 +37,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Label;
......@@ -69,9 +71,9 @@ public class SimpleVerifierTest extends AsmTest implements Opcodes {
methodNode.visitMaxs(10, 10);
anaylzer.analyze("C", methodNode);
Frame<?>[] frames = anaylzer.getFrames();
for (int i = 0; i < frames.length; ++i) {
if (frames[i] != null) {
frames[i].toString();
for (Frame<?> frame : frames) {
if (frame != null) {
frame.toString();
}
}
anaylzer.getHandlers(0);
......@@ -458,6 +460,24 @@ public class SimpleVerifierTest extends AsmTest implements Opcodes {
assertValid();
}
@ParameterizedTest
@CsvSource({
"java/lang/String, java/lang/Number, java/lang/Object",
"java/lang/Integer, java/lang/Number, java/lang/Number",
"java/lang/Float, java/lang/Integer, java/lang/Number",
"java/lang/Long, java/util/List, java/lang/Object",
"java/util/Map, java/util/List, java/lang/Object"
})
public void testMergeObjectTypes(
final String internalName1, final String internalName2, final String expectedInternalName) {
BasicValue value1 = new BasicValue(Type.getObjectType(internalName1));
BasicValue value2 = new BasicValue(Type.getObjectType(internalName2));
BasicValue expectedValue = new BasicValue(Type.getObjectType(expectedInternalName));
SimpleVerifier verifier = new SimpleVerifier();
assertEquals(expectedValue, verifier.merge(value1, value2));
assertEquals(expectedValue, verifier.merge(value2, value1));
}
@Test
public void testClassNotFound() {
Label loopLabel = new Label();
......@@ -473,12 +493,11 @@ public class SimpleVerifierTest extends AsmTest implements Opcodes {
}
@Test
void testIsAssignableFrom() {
public void testIsAssignableFrom() {
Type baseType = Type.getObjectType("C");
Type superType = Type.getObjectType("D");
Type interfaceType = Type.getObjectType("I");
new SimpleVerifier(
ASM7_EXPERIMENTAL, baseType, superType, Arrays.asList(interfaceType), false) {
new SimpleVerifier(ASM7, baseType, superType, Arrays.asList(interfaceType), false) {
void test() {
assertTrue(isAssignableFrom(baseType, baseType));
......@@ -489,14 +508,20 @@ public class SimpleVerifierTest extends AsmTest implements Opcodes {
@Override
protected Class<?> getClass(final Type type) {
// Return dummy classes, to make sure isAssignable in test() does not rely on them.
if (type == baseType) return int.class;
if (type == superType) return float.class;
if (type == interfaceType) return double.class;
if (type == baseType) {
return int.class;
}
if (type == superType) {
return float.class;
}
if (type == interfaceType) {
return double.class;
}
return super.getClass(type);
}
}.test();
new SimpleVerifier(ASM7_EXPERIMENTAL, interfaceType, null, null, true) {
new SimpleVerifier(ASM7, interfaceType, null, null, true) {
void test() {
assertTrue(isAssignableFrom(interfaceType, baseType));
......@@ -511,10 +536,42 @@ public class SimpleVerifierTest extends AsmTest implements Opcodes {
}.test();
}
/**
* Checks that the merge of an ArrayList and an SQLException can be returned as an Iterable. The
* merged type is recomputed by SimpleVerifier as Object (because of limitations of the merging
* algorithm, due to multiple interface inheritance), but the subtyping check is relaxed if the
* super type is an interface type.
*
* @throws AnalyzerException if the test class can't be analyzed.
*/
@Test
public void testIsAssignableFromInterface() throws AnalyzerException {
methodNode =
new MethodNode(
ACC_PUBLIC | ACC_STATIC,
"m",
"(Ljava/util/ArrayList;Ljava/sql/SQLException;)Ljava/lang/Iterable;",
null,
null);
methodNode.visitCode();
methodNode.visitVarInsn(ALOAD, 0);
Label elseLabel = new Label();
methodNode.visitJumpInsn(IFNULL, elseLabel);
methodNode.visitVarInsn(ALOAD, 0);
Label endIfLabel = new Label();
methodNode.visitJumpInsn(GOTO, endIfLabel);
methodNode.visitLabel(elseLabel);
methodNode.visitVarInsn(ALOAD, 1);
methodNode.visitLabel(endIfLabel);
methodNode.visitInsn(ARETURN);
methodNode.visitMaxs(1, 2);
assertValid();
}
/**
* Tests that the precompiled classes can be successfully analyzed with a SimpleVerifier.
*
* @throws AnalyzerException
* @throws AnalyzerException if the test class can't be analyzed.
*/
@ParameterizedTest
@MethodSource(ALL_CLASSES_AND_LATEST_API)
......
......@@ -114,7 +114,7 @@ public class SmallSetTest {
assertThrows(UnsupportedOperationException.class, () -> iterator.remove());
}
private SmallSet<Object> newSmallSet(Object element1, Object element2) {
private SmallSet<Object> newSmallSet(final Object element1, final Object element2) {
return (SmallSet<Object>) new SmallSet<Object>(element1).union(new SmallSet<Object>(element2));
}
}
......@@ -52,7 +52,7 @@ public class SourceInterpreterTest extends AsmTest {
/**
* Tests that the precompiled classes can be successfully analyzed with a SourceInterpreter.
*
* @throws AnalyzerException
* @throws AnalyzerException if the test class can't be analyzed.
*/
@ParameterizedTest
@MethodSource(ALL_CLASSES_AND_LATEST_API)
......
......@@ -46,10 +46,10 @@ public class ValueTest {
@Test
public void testBasicValue() {
assertTrue(BasicValue.UNINITIALIZED_VALUE.equals(new BasicValue(null)));
assertTrue(BasicValue.INT_VALUE.equals(new BasicValue(Type.INT_TYPE)));
assertTrue(BasicValue.INT_VALUE.equals(BasicValue.INT_VALUE));
assertFalse(BasicValue.INT_VALUE.equals(new Object()));
assertEquals(new BasicValue(null), BasicValue.UNINITIALIZED_VALUE);
assertEquals(new BasicValue(Type.INT_TYPE), BasicValue.INT_VALUE);
assertEquals(BasicValue.INT_VALUE, BasicValue.INT_VALUE);
assertNotEquals(new Object(), BasicValue.INT_VALUE);
assertTrue(BasicValue.REFERENCE_VALUE.isReference());
assertTrue(new BasicValue(Type.getObjectType("[I")).isReference());
......@@ -69,10 +69,10 @@ public class ValueTest {
public void testSourceValue() {
assertEquals(2, new SourceValue(2).getSize());
assertTrue(new SourceValue(1).equals(new SourceValue(1)));
assertFalse(new SourceValue(1).equals(new SourceValue(1, new InsnNode(Opcodes.NOP))));
assertFalse(new SourceValue(1).equals(new SourceValue(2)));
assertFalse(new SourceValue(1).equals(null));
assertEquals(new SourceValue(1), new SourceValue(1));
assertNotEquals(new SourceValue(1), new SourceValue(1, new InsnNode(Opcodes.NOP)));
assertNotEquals(new SourceValue(1), new SourceValue(2));
assertNotEquals(new SourceValue(1), null);
assertEquals(0, new SourceValue(1).hashCode());
assertNotEquals(0, new SourceValue(1, new InsnNode(Opcodes.NOP)).hashCode());
......
......@@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
......@@ -39,16 +40,10 @@ import org.objectweb.asm.Type;
/**
* A {@link MethodVisitor} to insert before, after and around advices in methods and constructors.
*
* <p>The behavior for constructors is the following:
*
* <ol>
* <li>as long as the INVOKESPECIAL for the object initialization has not been reached, every
* bytecode instruction is dispatched in the ctor code visitor
* <li>when this one is reached, it is only added in the ctor code visitor and a JP invoke is
* added
* <li>after that, only the other code visitor receives the instructions
* </ol>
* For constructors, the code keeps track of the elements on the stack in order to detect when the
* super class constructor is called (note that there can be multiple such calls in different
* branches). {@code onMethodEnter} is called after each super class constructor call, because the
* object cannot be used before it is properly initialized.
*
* @author Eugene Kuleshov
* @author Eric Bruneton
......@@ -61,6 +56,7 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes
/** Any value other than "uninitialized this". */
private static final Object OTHER = new Object();
/** Prefix of the error message when invalid opcodes are found. */
private static final String INVALID_OPCODE = "Invalid opcode ";
/** The access flags of the visited method. */
......@@ -70,10 +66,15 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes
protected String methodDesc;
/** Whether the visited method is a constructor. */
private boolean isConstructor;
private final boolean isConstructor;
/**
* Whether the super class constructor has been called (if the visited method is a constructor).
* Whether the super class constructor has been called (if the visited method is a constructor),
* at the current instruction. There can be multiple call sites to the super constructor (e.g. for
* Java code such as {@code super(expr ? value1 : value2);}), in different branches. When scanning
* the bytecode linearly, we can move from one branch where the super constructor has been called
* to another where it has not been called yet. Therefore, this value can change from false to
* true, and vice-versa.
*/
private boolean superClassConstructorCalled;
......@@ -86,9 +87,11 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes
private List<Object> stackFrame;
/**
* The stack map frames corresponding to the labels of the forward jumps made before the super
* The stack map frames corresponding to the labels of the forward jumps made *before* the super
* class constructor has been called (note that the Java Virtual Machine forbids backward jumps
* before the super class constructor is called). This field is only maintained for constructors.
* before the super class constructor is called). Note that by definition (cf. the 'before'), when
* we reach a label from this map, {@link #superClassConstructorCalled} must be reset to false.
* This field is only maintained for constructors.
*/
private Map<Label, List<Object>> forwardJumpStackFrames;
......@@ -96,8 +99,7 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes
* Constructs a new {@link AdviceAdapter}.
*
* @param api the ASM API version implemented by this visitor. Must be one of {@link
* Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
* Opcodes#ASM7_EXPERIMENTAL}.
* Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
* @param methodVisitor the method visitor to which this adapter delegates calls.
* @param access the method's access flags (see {@link Opcodes}).
* @param name the method's name.
......@@ -406,7 +408,9 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes
super.visitLdcInsn(value);
if (isConstructor && !superClassConstructorCalled) {
pushValue(OTHER);
if (value instanceof Double || value instanceof Long) {
if (value instanceof Double
|| value instanceof Long
|| (value instanceof ConstantDynamic && ((ConstantDynamic) value).getSize() == 2)) {
pushValue(OTHER);
}
}
......@@ -432,7 +436,11 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes
}
}
/** @deprecated */
/**
* Deprecated.
*
* @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead.
*/
@Deprecated
@Override
public void visitMethodInsn(
......@@ -476,8 +484,8 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes
case INVOKESPECIAL:
Object value = popValue();
if (value == UNINITIALIZED_THIS && !superClassConstructorCalled) {
onMethodEnter();
superClassConstructorCalled = true;
onMethodEnter();
}
break;
default:
......@@ -560,11 +568,18 @@ public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
public void visitTryCatchBlock(
final Label start, final Label end, final Label handler, final String type) {
super.visitTryCatchBlock(start, end, handler, type);
if (isConstructor
&& !superClassConstructorCalled
&& !forwardJumpStackFrames.containsKey(handler)) {
// By definition of 'forwardJumpStackFrames', 'handler' should be pushed only if there is an
// instruction between 'start' and 'end' at which the super class constructor is not yet
// called. Unfortunately, try catch blocks must be visited before their labels, so we have no
// way to know this at this point. Instead, we suppose that the super class constructor has not
// been called at the start of *any* exception handler. If this is wrong, normally there should
// not be a second super class constructor call in the exception handler (an object can't be
// initialized twice), so this is not issue (in the sense that there is no risk to emit a wrong
// 'onMethodEnter').
if (isConstructor && !forwardJumpStackFrames.containsKey(handler)) {
List<Object> handlerStackFrame = new ArrayList<Object>();
handlerStackFrame.add(OTHER);
forwardJumpStackFrames.put(handler, handlerStackFrame);
......
......@@ -62,7 +62,7 @@ public class AnalyzerAdapter extends MethodVisitor {
* double are represented by two elements, the second one being TOP). 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). This
* field is <tt>null</tt> for unreachable instructions.
* field is {@literal null} for unreachable instructions.
*/
public List<Object> locals;
......@@ -73,11 +73,11 @@ public class AnalyzerAdapter extends MethodVisitor {
* double are represented by two elements, the second one being TOP). 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). This
* field is <tt>null</tt> for unreachable instructions.
* field is {@literal null} for unreachable instructions.
*/
public List<Object> stack;
/** The labels that designate the next instruction to be visited. May be <tt>null</tt>. */
/** The labels that designate the next instruction to be visited. May be {@literal null}. */
private List<Label> labels;
/**
......@@ -106,8 +106,8 @@ public class AnalyzerAdapter extends MethodVisitor {
* @param access the method's access flags (see {@link Opcodes}).
* @param name the method's name.
* @param descriptor the method's descriptor (see {@link Type}).
* @param methodVisitor the method visitor to which this adapter delegates calls. May be
* <tt>null</tt>.
* @param methodVisitor the method visitor to which this adapter delegates calls. May be {@literal
* null}.
* @throws IllegalStateException If a subclass calls this constructor.
*/
public AnalyzerAdapter(
......@@ -116,7 +116,7 @@ public class AnalyzerAdapter extends MethodVisitor {
final String name,
final String descriptor,
final MethodVisitor methodVisitor) {
this(Opcodes.ASM6, owner, access, name, descriptor, methodVisitor);
this(Opcodes.ASM7, owner, access, name, descriptor, methodVisitor);
if (getClass() != AnalyzerAdapter.class) {
throw new IllegalStateException();
}
......@@ -126,14 +126,13 @@ public class AnalyzerAdapter extends MethodVisitor {
* Constructs a new {@link AnalyzerAdapter}.
*
* @param api the ASM API version implemented by this visitor. Must be one of {@link
* Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link
* Opcodes#ASM7_EXPERIMENTAL}.
* Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
* @param owner the owner's class name.
* @param access the method's access flags (see {@link Opcodes}).
* @param name the method's name.
* @param descriptor the method's descriptor (see {@link Type}).
* @param methodVisitor the method visitor to which this adapter delegates calls. May be
* <tt>null</tt>.
* @param methodVisitor the method visitor to which this adapter delegates calls. May be {@literal
* null}.
*/
protected AnalyzerAdapter(
final int api,
......@@ -191,16 +190,16 @@ public class AnalyzerAdapter extends MethodVisitor {
@Override
public void visitFrame(
final int type,
final int nLocal,
final int numLocal,
final Object[] local,
final int nStack,
final int numStack,
final Object[] stack) {
if (type != Opcodes.F_NEW) { // Uncompressed frame.
throw new IllegalArgumentException(
"AnalyzerAdapter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)");
}
super.visitFrame(type, nLocal, local, nStack, stack);
super.visitFrame(type, numLocal, local, numStack, stack);
if (this.locals != null) {
this.locals.clear();
......@@ -209,8 +208,8 @@ public class AnalyzerAdapter extends MethodVisitor {
this.locals = new ArrayList<Object>();
this.stack = new ArrayList<Object>();
}
visitFrameTypes(nLocal, local, this.locals);
visitFrameTypes(nStack, stack, this.stack);
visitFrameTypes(numLocal, local, this.locals);
visitFrameTypes(numStack, stack, this.stack);
maxLocals = Math.max(maxLocals, this.locals.size());
maxStack = Math.max(maxStack, this.stack.size());
}
......@@ -280,7 +279,11 @@ public class AnalyzerAdapter extends MethodVisitor {
execute(opcode, 0, descriptor);
}
/** @deprecated */
/**
* Deprecated.
*
* @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead.
*/
@Deprecated
@Override
public void visitMethodInsn(
......@@ -450,7 +453,12 @@ public class AnalyzerAdapter extends MethodVisitor {
@Override
public void visitLocalVariable(
String name, String descriptor, String signature, Label start, Label end, int index) {
final String name,
final String descriptor,
final String signature,
final Label start,
final Label end,
final int index) {
char firstDescriptorChar = descriptor.charAt(0);
maxLocals =
Math.max(
......@@ -546,8 +554,8 @@ public class AnalyzerAdapter extends MethodVisitor {
if (firstDescriptorChar == '(') {
int numSlots = 0;
Type[] types = Type.getArgumentTypes(descriptor);
for (int i = 0; i < types.length; ++i) {
numSlots += types[i].getSize();
for (Type type : types) {
numSlots += type.getSize();
}
pop(numSlots);
} else if (firstDescriptorChar == 'J' || firstDescriptorChar == 'D') {
......