package org.objectweb.asm.util;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.TypeReference;
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;
public class CheckMethodAdapter extends MethodVisitor {
private enum Method {
VISIT_INSN,
VISIT_INT_INSN,
VISIT_VAR_INSN,
VISIT_TYPE_INSN,
VISIT_FIELD_INSN,
VISIT_METHOD_INSN,
VISIT_JUMP_INSN
}
private static final Method[] OPCODE_METHODS = {
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INT_INSN,
Method.VISIT_INT_INSN,
null,
null,
null,
Method.VISIT_VAR_INSN,
Method.VISIT_VAR_INSN,
Method.VISIT_VAR_INSN,
Method.VISIT_VAR_INSN,
Method.VISIT_VAR_INSN,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_VAR_INSN,
Method.VISIT_VAR_INSN,
Method.VISIT_VAR_INSN,
Method.VISIT_VAR_INSN,
Method.VISIT_VAR_INSN,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
null,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN,
Method.VISIT_VAR_INSN,
null,
null,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_FIELD_INSN,
Method.VISIT_FIELD_INSN,
Method.VISIT_FIELD_INSN,
Method.VISIT_FIELD_INSN,
Method.VISIT_METHOD_INSN,
Method.VISIT_METHOD_INSN,
Method.VISIT_METHOD_INSN,
Method.VISIT_METHOD_INSN,
null,
Method.VISIT_TYPE_INSN,
Method.VISIT_INT_INSN,
Method.VISIT_TYPE_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
Method.VISIT_TYPE_INSN,
Method.VISIT_TYPE_INSN,
Method.VISIT_INSN,
Method.VISIT_INSN,
null,
null,
Method.VISIT_JUMP_INSN,
Method.VISIT_JUMP_INSN
};
private static final String INVALID = "Invalid ";
private static final String INVALID_DESCRIPTOR = "Invalid descriptor: ";
private static final String INVALID_TYPE_REFERENCE = "Invalid type reference sort 0x";
private static final String INVALID_LOCAL_VARIABLE_INDEX = "Invalid local variable index";
private static final String MUST_NOT_BE_NULL_OR_EMPTY = " (must not be null or empty)";
private static final String START_LABEL = "start label";
private static final String END_LABEL = "end label";
public int version;
private int access;
private int visibleAnnotableParameterCount;
private int invisibleAnnotableParameterCount;
private boolean visitCodeCalled;
private boolean visitMaxCalled;
private boolean visitEndCalled;
private int insnCount;
private final Map<Label, Integer> labelInsnIndices;
private Set<Label> referencedLabels;
private int lastFrameInsnIndex = -1;
private int numExpandedFrames;
private int numCompressedFrames;
private List<Label> handlers;
public CheckMethodAdapter(final MethodVisitor methodvisitor) {
this(methodvisitor, new HashMap<Label, Integer>());
}
public CheckMethodAdapter(
final MethodVisitor methodVisitor, final Map<Label, Integer> labelInsnIndices) {
this( Opcodes.ASM9, methodVisitor, labelInsnIndices);
if (getClass() != CheckMethodAdapter.class) {
throw new IllegalStateException();
}
}
protected CheckMethodAdapter(
final int api,
final MethodVisitor methodVisitor,
final Map<Label, Integer> labelInsnIndices) {
super(api, methodVisitor);
this.labelInsnIndices = labelInsnIndices;
this.referencedLabels = new HashSet<>();
this.handlers = new ArrayList<>();
}
public CheckMethodAdapter(
final int access,
final String name,
final String descriptor,
final MethodVisitor methodVisitor,
final Map<Label, Integer> labelInsnIndices) {
this(
Opcodes.ASM9, access, name, descriptor, methodVisitor, labelInsnIndices);
if (getClass() != CheckMethodAdapter.class) {
throw new IllegalStateException();
}
}
protected CheckMethodAdapter(
final int api,
final int access,
final String name,
final String descriptor,
final MethodVisitor methodVisitor,
final Map<Label, Integer> labelInsnIndices) {
this(
api,
new MethodNode(api, access, name, descriptor, null, null) {
@Override
public void visitEnd() {
Analyzer<BasicValue> analyzer = new Analyzer<>(new BasicVerifier());
try {
analyzer.analyze("dummy", this);
} catch (IndexOutOfBoundsException e) {
if (maxLocals == 0 && maxStack == 0) {
throw new IllegalArgumentException(
"Data flow checking option requires valid, non zero maxLocals and maxStack.",
e);
}
throwError(analyzer, e);
} catch (AnalyzerException e) {
throwError(analyzer, e);
}
if (methodVisitor != null) {
accept(methodVisitor);
}
}
private void throwError(final Analyzer<BasicValue> analyzer, final Exception e) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter, true);
CheckClassAdapter.printAnalyzerResult(this, analyzer, printWriter);
printWriter.close();
throw new IllegalArgumentException(e.getMessage() + ' ' + stringWriter.toString(), e);
}
},
labelInsnIndices);
this.access = access;
}
@Override
public void visitParameter(final String name, final int access) {
if (name != null) {
checkUnqualifiedName(version, name, "name");
}
CheckClassAdapter.checkAccess(
access, Opcodes.ACC_FINAL + Opcodes.ACC_MANDATED + Opcodes.ACC_SYNTHETIC);
super.visitParameter(name, access);
}
@Override
public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
checkVisitEndNotCalled();
checkDescriptor(version, descriptor, false);
return new CheckAnnotationAdapter(super.visitAnnotation(descriptor, visible));
}
@Override
public AnnotationVisitor visitTypeAnnotation(
final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
checkVisitEndNotCalled();
int sort = new TypeReference(typeRef).getSort();
if (sort != TypeReference.METHOD_TYPE_PARAMETER
&& sort != TypeReference.METHOD_TYPE_PARAMETER_BOUND
&& sort != TypeReference.METHOD_RETURN
&& sort != TypeReference.METHOD_RECEIVER
&& sort != TypeReference.METHOD_FORMAL_PARAMETER
&& sort != TypeReference.THROWS) {
throw new IllegalArgumentException(INVALID_TYPE_REFERENCE + Integer.toHexString(sort));
}
CheckClassAdapter.checkTypeRef(typeRef);
CheckMethodAdapter.checkDescriptor(version, descriptor, false);
return new CheckAnnotationAdapter(
super.visitTypeAnnotation(typeRef, typePath, descriptor, visible));
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
checkVisitEndNotCalled();
return new CheckAnnotationAdapter(super.visitAnnotationDefault(), false);
}
@Override
public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
checkVisitEndNotCalled();
if (visible) {
visibleAnnotableParameterCount = parameterCount;
} else {
invisibleAnnotableParameterCount = parameterCount;
}
super.visitAnnotableParameterCount(parameterCount, visible);
}
@Override
public AnnotationVisitor visitParameterAnnotation(
final int parameter, final String descriptor, final boolean visible) {
checkVisitEndNotCalled();
if ((visible
&& visibleAnnotableParameterCount > 0
&& parameter >= visibleAnnotableParameterCount)
|| (!visible
&& invisibleAnnotableParameterCount > 0
&& parameter >= invisibleAnnotableParameterCount)) {
throw new IllegalArgumentException("Invalid parameter index");
}
checkDescriptor(version, descriptor, false);
return new CheckAnnotationAdapter(
super.visitParameterAnnotation(parameter, descriptor, visible));
}
@Override
public void visitAttribute(final Attribute attribute) {
checkVisitEndNotCalled();
if (attribute == null) {
throw new IllegalArgumentException("Invalid attribute (must not be null)");
}
super.visitAttribute(attribute);
}
@Override
public void visitCode() {
if ((access & Opcodes.ACC_ABSTRACT) != 0) {
throw new UnsupportedOperationException("Abstract methods cannot have code");
}
visitCodeCalled = true;
super.visitCode();
}
@Override
public void visitFrame(
final int type,
final int numLocal,
final Object[] local,
final int numStack,
final Object[] stack) {
if (insnCount == lastFrameInsnIndex) {
throw new IllegalStateException("At most one frame can be visited at a given code location.");
}
lastFrameInsnIndex = insnCount;
int maxNumLocal;
int maxNumStack;
switch (type) {
case Opcodes.F_NEW:
case Opcodes.F_FULL:
maxNumLocal = Integer.MAX_VALUE;
maxNumStack = Integer.MAX_VALUE;
break;
case Opcodes.F_SAME:
maxNumLocal = 0;
maxNumStack = 0;
break;
case Opcodes.F_SAME1:
maxNumLocal = 0;
maxNumStack = 1;
break;
case Opcodes.F_APPEND:
case Opcodes.F_CHOP:
maxNumLocal = 3;
maxNumStack = 0;
break;
default:
throw new IllegalArgumentException("Invalid frame type " + type);
}
if (numLocal > maxNumLocal) {
throw new IllegalArgumentException(
"Invalid numLocal=" + numLocal + " for frame type " + type);
}
if (numStack > maxNumStack) {
throw new IllegalArgumentException(
"Invalid numStack=" + numStack + " for frame type " + type);
}
if (type != Opcodes.F_CHOP) {
if (numLocal > 0 && (local == null || local.length < numLocal)) {
throw new IllegalArgumentException("Array local[] is shorter than numLocal");
}
for (int i = 0; i < numLocal; ++i) {
checkFrameValue(local[i]);
}
}
if (numStack > 0 && (stack == null || stack.length < numStack)) {
throw new IllegalArgumentException("Array stack[] is shorter than numStack");
}
for (int i = 0; i < numStack; ++i) {
checkFrameValue(stack[i]);
}
if (type == Opcodes.F_NEW) {
++numExpandedFrames;
} else {
++numCompressedFrames;
}
if (numExpandedFrames > 0 && numCompressedFrames > 0) {
throw new IllegalArgumentException("Expanded and compressed frames must not be mixed.");
}
super.visitFrame(type, numLocal, local, numStack, stack);
}
@Override
public void visitInsn(final int opcode) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkOpcodeMethod(opcode, Method.VISIT_INSN);
super.visitInsn(opcode);
++insnCount;
}
@Override
public void visitIntInsn(final int opcode, final int operand) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkOpcodeMethod(opcode, Method.VISIT_INT_INSN);
switch (opcode) {
case Opcodes.BIPUSH:
checkSignedByte(operand, "Invalid operand");
break;
case Opcodes.SIPUSH:
checkSignedShort(operand, "Invalid operand");
break;
case Opcodes.NEWARRAY:
if (operand < Opcodes.T_BOOLEAN || operand > Opcodes.T_LONG) {
throw new IllegalArgumentException(
"Invalid operand (must be an array type code T_...): " + operand);
}
break;
default:
throw new AssertionError();
}
super.visitIntInsn(opcode, operand);
++insnCount;
}
@Override
public void visitVarInsn(final int opcode, final int var) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkOpcodeMethod(opcode, Method.VISIT_VAR_INSN);
checkUnsignedShort(var, INVALID_LOCAL_VARIABLE_INDEX);
super.visitVarInsn(opcode, var);
++insnCount;
}
@Override
public void visitTypeInsn(final int opcode, final String type) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkOpcodeMethod(opcode, Method.VISIT_TYPE_INSN);
checkInternalName(version, type, "type");
if (opcode == Opcodes.NEW && type.charAt(0) == '[') {
throw new IllegalArgumentException("NEW cannot be used to create arrays: " + type);
}
super.visitTypeInsn(opcode, type);
++insnCount;
}
@Override
public void visitFieldInsn(
final int opcode, final String owner, final String name, final String descriptor) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkOpcodeMethod(opcode, Method.VISIT_FIELD_INSN);
checkInternalName(version, owner, "owner");
checkUnqualifiedName(version, name, "name");
checkDescriptor(version, descriptor, false);
super.visitFieldInsn(opcode, owner, name, descriptor);
++insnCount;
}
@Override
public void visitMethodInsn(
final int opcodeAndSource,
final String owner,
final String name,
final String descriptor,
final boolean isInterface) {
if (api < Opcodes.ASM5 && (opcodeAndSource & Opcodes.SOURCE_DEPRECATED) == 0) {
super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
return;
}
int opcode = opcodeAndSource & ~Opcodes.SOURCE_MASK;
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkOpcodeMethod(opcode, Method.VISIT_METHOD_INSN);
if (opcode != Opcodes.INVOKESPECIAL || !"<init>".equals(name)) {
checkMethodIdentifier(version, name, "name");
}
checkInternalName(version, owner, "owner");
checkMethodDescriptor(version, descriptor);
if (opcode == Opcodes.INVOKEVIRTUAL && isInterface) {
throw new IllegalArgumentException("INVOKEVIRTUAL can't be used with interfaces");
}
if (opcode == Opcodes.INVOKEINTERFACE && !isInterface) {
throw new IllegalArgumentException("INVOKEINTERFACE can't be used with classes");
}
if (opcode == Opcodes.INVOKESPECIAL && isInterface && (version & 0xFFFF) < Opcodes.V1_8) {
throw new IllegalArgumentException(
"INVOKESPECIAL can't be used with interfaces prior to Java 8");
}
super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface);
++insnCount;
}
@Override
public void visitInvokeDynamicInsn(
final String name,
final String descriptor,
final Handle bootstrapMethodHandle,
final Object... bootstrapMethodArguments) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkMethodIdentifier(version, name, "name");
checkMethodDescriptor(version, descriptor);
if (bootstrapMethodHandle.getTag() != Opcodes.H_INVOKESTATIC
&& bootstrapMethodHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
throw new IllegalArgumentException("invalid handle tag " + bootstrapMethodHandle.getTag());
}
for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
checkLdcConstant(bootstrapMethodArgument);
}
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
++insnCount;
}
@Override
public void visitJumpInsn(final int opcode, final Label label) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkOpcodeMethod(opcode, Method.VISIT_JUMP_INSN);
checkLabel(label, false, "label");
super.visitJumpInsn(opcode, label);
referencedLabels.add(label);
++insnCount;
}
@Override
public void visitLabel(final Label label) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkLabel(label, false, "label");
if (labelInsnIndices.get(label) != null) {
throw new IllegalArgumentException("Already visited label");
}
labelInsnIndices.put(label, insnCount);
super.visitLabel(label);
}
@Override
public void visitLdcInsn(final Object value) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkLdcConstant(value);
super.visitLdcInsn(value);
++insnCount;
}
@Override
public void visitIincInsn(final int var, final int increment) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkUnsignedShort(var, INVALID_LOCAL_VARIABLE_INDEX);
checkSignedShort(increment, "Invalid increment");
super.visitIincInsn(var, increment);
++insnCount;
}
@Override
public void visitTableSwitchInsn(
final int min, final int max, final Label dflt, final Label... labels) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
if (max < min) {
throw new IllegalArgumentException(
"Max = " + max + " must be greater than or equal to min = " + min);
}
checkLabel(dflt, false, "default label");
if (labels == null || labels.length != max - min + 1) {
throw new IllegalArgumentException("There must be max - min + 1 labels");
}
for (int i = 0; i < labels.length; ++i) {
checkLabel(labels[i], false, "label at index " + i);
}
super.visitTableSwitchInsn(min, max, dflt, labels);
Collections.addAll(referencedLabels, labels);
++insnCount;
}
@Override
public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
checkVisitMaxsNotCalled();
checkVisitCodeCalled();
checkLabel(dflt, false, "default label");
if (keys == null || labels == null || keys.length != labels.length) {
throw new IllegalArgumentException("There must be the same number of keys and labels");
}
for (int i = 0; i < labels.length; ++i) {
checkLabel(labels[i], false, "label at index " + i);
}
super.visitLookupSwitchInsn(dflt, keys, labels);
referencedLabels.add(dflt);
Collections.addAll(referencedLabels, labels);
++insnCount;
}
@Override
public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkDescriptor(version, descriptor, false);
if (descriptor.charAt(0) != '[') {
throw new IllegalArgumentException(
"Invalid descriptor (must be an array type descriptor): " + descriptor);
}
if (numDimensions < 1) {
throw new IllegalArgumentException(
"Invalid dimensions (must be greater than 0): " + numDimensions);
}
if (numDimensions > descriptor.lastIndexOf('[') + 1) {
throw new IllegalArgumentException(
"Invalid dimensions (must not be greater than numDimensions(descriptor)): "
+ numDimensions);
}
super.visitMultiANewArrayInsn(descriptor, numDimensions);
++insnCount;
}
@Override
public AnnotationVisitor visitInsnAnnotation(
final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
int sort = new TypeReference(typeRef).getSort();
if (sort != TypeReference.INSTANCEOF
&& sort != TypeReference.NEW
&& sort != TypeReference.CONSTRUCTOR_REFERENCE
&& sort != TypeReference.METHOD_REFERENCE
&& sort != TypeReference.CAST
&& sort != TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
&& sort != TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT
&& sort != TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
&& sort != TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT) {
throw new IllegalArgumentException(INVALID_TYPE_REFERENCE + Integer.toHexString(sort));
}
CheckClassAdapter.checkTypeRef(typeRef);
CheckMethodAdapter.checkDescriptor(version, descriptor, false);
return new CheckAnnotationAdapter(
super.visitInsnAnnotation(typeRef, typePath, descriptor, visible));
}
@Override
public void visitTryCatchBlock(
final Label start, final Label end, final Label handler, final String type) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkLabel(start, false, START_LABEL);
checkLabel(end, false, END_LABEL);
checkLabel(handler, false, "handler label");
if (labelInsnIndices.get(start) != null
|| labelInsnIndices.get(end) != null
|| labelInsnIndices.get(handler) != null) {
throw new IllegalStateException("Try catch blocks must be visited before their labels");
}
if (type != null) {
checkInternalName(version, type, "type");
}
super.visitTryCatchBlock(start, end, handler, type);
handlers.add(start);
handlers.add(end);
}
@Override
public AnnotationVisitor visitTryCatchAnnotation(
final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
int sort = new TypeReference(typeRef).getSort();
if (sort != TypeReference.EXCEPTION_PARAMETER) {
throw new IllegalArgumentException(INVALID_TYPE_REFERENCE + Integer.toHexString(sort));
}
CheckClassAdapter.checkTypeRef(typeRef);
CheckMethodAdapter.checkDescriptor(version, descriptor, false);
return new CheckAnnotationAdapter(
super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible));
}
@Override
public void visitLocalVariable(
final String name,
final String descriptor,
final String signature,
final Label start,
final Label end,
final int index) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkUnqualifiedName(version, name, "name");
checkDescriptor(version, descriptor, false);
if (signature != null) {
CheckClassAdapter.checkFieldSignature(signature);
}
checkLabel(start, true, START_LABEL);
checkLabel(end, true, END_LABEL);
checkUnsignedShort(index, INVALID_LOCAL_VARIABLE_INDEX);
int startInsnIndex = labelInsnIndices.get(start).intValue();
int endInsnIndex = labelInsnIndices.get(end).intValue();
if (endInsnIndex < startInsnIndex) {
throw new IllegalArgumentException(
"Invalid start and end labels (end must be greater than start)");
}
super.visitLocalVariable(name, descriptor, signature, start, end, index);
}
@Override
public AnnotationVisitor visitLocalVariableAnnotation(
final int typeRef,
final TypePath typePath,
final Label[] start,
final Label[] end,
final int[] index,
final String descriptor,
final boolean visible) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
int sort = new TypeReference(typeRef).getSort();
if (sort != TypeReference.LOCAL_VARIABLE && sort != TypeReference.RESOURCE_VARIABLE) {
throw new IllegalArgumentException(INVALID_TYPE_REFERENCE + Integer.toHexString(sort));
}
CheckClassAdapter.checkTypeRef(typeRef);
checkDescriptor(version, descriptor, false);
if (start == null
|| end == null
|| index == null
|| end.length != start.length
|| index.length != start.length) {
throw new IllegalArgumentException(
"Invalid start, end and index arrays (must be non null and of identical length");
}
for (int i = 0; i < start.length; ++i) {
checkLabel(start[i], true, START_LABEL);
checkLabel(end[i], true, END_LABEL);
checkUnsignedShort(index[i], INVALID_LOCAL_VARIABLE_INDEX);
int startInsnIndex = labelInsnIndices.get(start[i]).intValue();
int endInsnIndex = labelInsnIndices.get(end[i]).intValue();
if (endInsnIndex < startInsnIndex) {
throw new IllegalArgumentException(
"Invalid start and end labels (end must be greater than start)");
}
}
return super.visitLocalVariableAnnotation(
typeRef, typePath, start, end, index, descriptor, visible);
}
@Override
public void visitLineNumber(final int line, final Label start) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
checkUnsignedShort(line, "Invalid line number");
checkLabel(start, true, START_LABEL);
super.visitLineNumber(line, start);
}
@Override
public void visitMaxs(final int maxStack, final int maxLocals) {
checkVisitCodeCalled();
checkVisitMaxsNotCalled();
visitMaxCalled = true;
for (Label l : referencedLabels) {
if (labelInsnIndices.get(l) == null) {
throw new IllegalStateException("Undefined label used");
}
}
for (int i = 0; i < handlers.size(); i += 2) {
Integer startInsnIndex = labelInsnIndices.get(handlers.get(i));
Integer endInsnIndex = labelInsnIndices.get(handlers.get(i + 1));
if (startInsnIndex == null || endInsnIndex == null) {
throw new IllegalStateException("Undefined try catch block labels");
}
if (endInsnIndex.intValue() <= startInsnIndex.intValue()) {
throw new IllegalStateException("Emty try catch block handler range");
}
}
checkUnsignedShort(maxStack, "Invalid max stack");
checkUnsignedShort(maxLocals, "Invalid max locals");
super.visitMaxs(maxStack, maxLocals);
}
@Override
public void visitEnd() {
checkVisitEndNotCalled();
visitEndCalled = true;
super.visitEnd();
}
private void checkVisitCodeCalled() {
if (!visitCodeCalled) {
throw new IllegalStateException(
"Cannot visit instructions before visitCode has been called.");
}
}
private void checkVisitMaxsNotCalled() {
if (visitMaxCalled) {
throw new IllegalStateException("Cannot visit instructions after visitMaxs has been called.");
}
}
private void checkVisitEndNotCalled() {
if (visitEndCalled) {
throw new IllegalStateException("Cannot visit elements after visitEnd has been called.");
}
}
private void checkFrameValue(final Object value) {
if (value == Opcodes.TOP
|| value == Opcodes.INTEGER
|| value == Opcodes.FLOAT
|| value == Opcodes.LONG
|| value == Opcodes.DOUBLE
|| value == Opcodes.NULL
|| value == Opcodes.UNINITIALIZED_THIS) {
return;
}
if (value instanceof String) {
checkInternalName(version, (String) value, "Invalid stack frame value");
} else if (value instanceof Label) {
referencedLabels.add((Label) value);
} else {
throw new IllegalArgumentException("Invalid stack frame value: " + value);
}
}
private static void checkOpcodeMethod(final int opcode, final Method method) {
if (opcode < Opcodes.NOP || opcode > Opcodes.IFNONNULL || OPCODE_METHODS[opcode] != method) {
throw new IllegalArgumentException("Invalid opcode: " + opcode);
}
}
private static void checkSignedByte(final int value, final String message) {
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
throw new IllegalArgumentException(message + " (must be a signed byte): " + value);
}
}
private static void checkSignedShort(final int value, final String message) {
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
throw new IllegalArgumentException(message + " (must be a signed short): " + value);
}
}
private static void checkUnsignedShort(final int value, final String message) {
if (value < 0 || value > 65535) {
throw new IllegalArgumentException(message + " (must be an unsigned short): " + value);
}
}
static void checkConstant(final Object value) {
if (!(value instanceof Integer)
&& !(value instanceof Float)
&& !(value instanceof Long)
&& !(value instanceof Double)
&& !(value instanceof String)) {
throw new IllegalArgumentException("Invalid constant: " + value);
}
}
private void checkLdcConstant(final Object value) {
if (value instanceof Type) {
int sort = ((Type) value).getSort();
if (sort != Type.OBJECT && sort != Type.ARRAY && sort != Type.METHOD) {
throw new IllegalArgumentException("Illegal LDC constant value");
}
if (sort != Type.METHOD && (version & 0xFFFF) < Opcodes.V1_5) {
throw new IllegalArgumentException("ldc of a constant class requires at least version 1.5");
}
if (sort == Type.METHOD && (version & 0xFFFF) < Opcodes.V1_7) {
throw new IllegalArgumentException("ldc of a method type requires at least version 1.7");
}
} else if (value instanceof Handle) {
if ((version & 0xFFFF) < Opcodes.V1_7) {
throw new IllegalArgumentException("ldc of a Handle requires at least version 1.7");
}
Handle handle = (Handle) value;
int tag = handle.getTag();
if (tag < Opcodes.H_GETFIELD || tag > Opcodes.H_INVOKEINTERFACE) {
throw new IllegalArgumentException("invalid handle tag " + tag);
}
checkInternalName(this.version, handle.getOwner(), "handle owner");
if (tag <= Opcodes.H_PUTSTATIC) {
checkDescriptor(this.version, handle.getDesc(), false);
} else {
checkMethodDescriptor(this.version, handle.getDesc());
}
String handleName = handle.getName();
if (!("<init>".equals(handleName) && tag == Opcodes.H_NEWINVOKESPECIAL)) {
checkMethodIdentifier(this.version, handleName, "handle name");
}
} else if (value instanceof ConstantDynamic) {
if ((version & 0xFFFF) < Opcodes.V11) {
throw new IllegalArgumentException("ldc of a ConstantDynamic requires at least version 11");
}
ConstantDynamic constantDynamic = (ConstantDynamic) value;
checkMethodIdentifier(this.version, constantDynamic.getName(), "constant dynamic name");
checkDescriptor(this.version, constantDynamic.getDescriptor(), false);
checkLdcConstant(constantDynamic.getBootstrapMethod());
int bootstrapMethodArgumentCount = constantDynamic.getBootstrapMethodArgumentCount();
for (int i = 0; i < bootstrapMethodArgumentCount; ++i) {
checkLdcConstant(constantDynamic.getBootstrapMethodArgument(i));
}
} else {
checkConstant(value);
}
}
static void checkUnqualifiedName(final int version, final String name, final String message) {
checkIdentifier(version, name, 0, -1, message);
}
static void checkIdentifier(
final int version,
final String name,
final int startPos,
final int endPos,
final String message) {
if (name == null || (endPos == -1 ? name.length() <= startPos : endPos <= startPos)) {
throw new IllegalArgumentException(INVALID + message + MUST_NOT_BE_NULL_OR_EMPTY);
}
int max = endPos == -1 ? name.length() : endPos;
if ((version & 0xFFFF) >= Opcodes.V1_5) {
for (int i = startPos; i < max; i = name.offsetByCodePoints(i, 1)) {
if (".;[/".indexOf(name.codePointAt(i)) != -1) {
throw new IllegalArgumentException(
INVALID + message + " (must not contain . ; [ or /): " + name);
}
}
return;
}
for (int i = startPos; i < max; i = name.offsetByCodePoints(i, 1)) {
if (i == startPos
? !Character.isJavaIdentifierStart(name.codePointAt(i))
: !Character.isJavaIdentifierPart(name.codePointAt(i))) {
throw new IllegalArgumentException(
INVALID + message + " (must be a valid Java identifier): " + name);
}
}
}
static void checkMethodIdentifier(final int version, final String name, final String message) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException(INVALID + message + MUST_NOT_BE_NULL_OR_EMPTY);
}
if ((version & 0xFFFF) >= Opcodes.V1_5) {
for (int i = 0; i < name.length(); i = name.offsetByCodePoints(i, 1)) {
if (".;[/<>".indexOf(name.codePointAt(i)) != -1) {
throw new IllegalArgumentException(
INVALID + message + " (must be a valid unqualified name): " + name);
}
}
return;
}
for (int i = 0; i < name.length(); i = name.offsetByCodePoints(i, 1)) {
if (i == 0
? !Character.isJavaIdentifierStart(name.codePointAt(i))
: !Character.isJavaIdentifierPart(name.codePointAt(i))) {
throw new IllegalArgumentException(
INVALID
+ message
+ " (must be a '<init>', '<clinit>' or a valid Java identifier): "
+ name);
}
}
}
static void checkInternalName(final int version, final String name, final String message) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException(INVALID + message + MUST_NOT_BE_NULL_OR_EMPTY);
}
if (name.charAt(0) == '[') {
checkDescriptor(version, name, false);
} else {
checkInternalClassName(version, name, message);
}
}
private static void checkInternalClassName(
final int version, final String name, final String message) {
try {
int startIndex = 0;
int slashIndex;
while ((slashIndex = name.indexOf('/', startIndex + 1)) != -1) {
checkIdentifier(version, name, startIndex, slashIndex, null);
startIndex = slashIndex + 1;
}
checkIdentifier(version, name, startIndex, name.length(), null);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
INVALID + message + " (must be an internal class name): " + name, e);
}
}
static void checkDescriptor(final int version, final String descriptor, final boolean canBeVoid) {
int endPos = checkDescriptor(version, descriptor, 0, canBeVoid);
if (endPos != descriptor.length()) {
throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
}
}
private static int checkDescriptor(
final int version, final String descriptor, final int startPos, final boolean canBeVoid) {
if (descriptor == null || startPos >= descriptor.length()) {
throw new IllegalArgumentException("Invalid type descriptor (must not be null or empty)");
}
switch (descriptor.charAt(startPos)) {
case 'V':
if (canBeVoid) {
return startPos + 1;
} else {
throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
}
case 'Z':
case 'C':
case 'B':
case 'S':
case 'I':
case 'F':
case 'J':
case 'D':
return startPos + 1;
case '[':
int pos = startPos + 1;
while (pos < descriptor.length() && descriptor.charAt(pos) == '[') {
++pos;
}
if (pos < descriptor.length()) {
return checkDescriptor(version, descriptor, pos, false);
} else {
throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
}
case 'L':
int endPos = descriptor.indexOf(';', startPos);
if (startPos == -1 || endPos - startPos < 2) {
throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
}
try {
checkInternalClassName(version, descriptor.substring(startPos + 1, endPos), null);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor, e);
}
return endPos + 1;
default:
throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
}
}
static void checkMethodDescriptor(final int version, final String descriptor) {
if (descriptor == null || descriptor.length() == 0) {
throw new IllegalArgumentException("Invalid method descriptor (must not be null or empty)");
}
if (descriptor.charAt(0) != '(' || descriptor.length() < 3) {
throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
}
int pos = 1;
if (descriptor.charAt(pos) != ')') {
do {
if (descriptor.charAt(pos) == 'V') {
throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
}
pos = checkDescriptor(version, descriptor, pos, false);
} while (pos < descriptor.length() && descriptor.charAt(pos) != ')');
}
pos = checkDescriptor(version, descriptor, pos + 1, true);
if (pos != descriptor.length()) {
throw new IllegalArgumentException(INVALID_DESCRIPTOR + descriptor);
}
}
private void checkLabel(final Label label, final boolean checkVisited, final String message) {
if (label == null) {
throw new IllegalArgumentException(INVALID + message + " (must not be null)");
}
if (checkVisited && labelInsnIndices.get(label) == null) {
throw new IllegalArgumentException(INVALID + message + " (must be visited first)");
}
}
}