List of usage examples for org.objectweb.asm.tree AbstractInsnNode getNext
public AbstractInsnNode getNext()
From source file:com.android.build.gradle.internal2.incremental.ConstructorBuilder.java
License:Apache License
/** * Splits the constructor in two methods, the "set up" and the "body" parts (see above). *///from www. j a v a 2s . com @NonNull private static Constructor split(@NonNull String owner, @NonNull MethodNode method, @NonNull VarInsnNode loadThis, @NonNull MethodInsnNode delegation, int loadThisLine, @NonNull List<LocalVariable> variables, int localsAtLoadThis) { String[] exceptions = ((List<String>) method.exceptions).toArray(new String[method.exceptions.size()]); // Do not add the local array yet, as we treat it as a new variable. String newDesc = method.desc.replace(")V", ")Ljava/lang/Object;"); newDesc = newDesc.replace("(", "([L" + owner + ";"); Type[] argumentTypes = Type.getArgumentTypes(newDesc); // Store the non hotswappable part of the constructor List<AbstractInsnNode> fixed = Lists.newLinkedList(); AbstractInsnNode insn = method.instructions.getFirst(); while (insn != loadThis) { fixed.add(insn); insn = insn.getNext(); } fixed.add(loadThis); MethodNode initArgs = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "init$args", newDesc, null, exceptions); GeneratorAdapter mv = new GeneratorAdapter(initArgs, initArgs.access, initArgs.name, initArgs.desc); int newArgument = mv.newLocal(Type.getType("[Ljava/lang/Object;")); mv.loadLocal(newArgument); ByteCodeUtils.restoreVariables(mv, variables.subList(0, localsAtLoadThis)); // Now insert the original method insn = loadThis.getNext(); while (insn != delegation) { insn.accept(mv); insn = insn.getNext(); } LabelNode labelBefore = new LabelNode(); labelBefore.accept(mv); // Create the args array with the local variables and the values to send to the delegated constructor Type[] returnTypes = Type.getArgumentTypes(delegation.desc); // The extra elements for the local variables and the qualified name of the constructor. mv.push(returnTypes.length + 2); mv.newArray(Type.getType(Object.class)); int args = mv.newLocal(Type.getType("[Ljava/lang/Object;")); mv.storeLocal(args); for (int i = returnTypes.length - 1; i >= 0; i--) { Type type = returnTypes[i]; mv.loadLocal(args); mv.swap(type, Type.getType(Object.class)); mv.push(i + 2); mv.swap(type, Type.INT_TYPE); mv.box(type); mv.arrayStore(Type.getType(Object.class)); } // Store the qualified name of the constructor in the second element of the array. mv.loadLocal(args); mv.push(1); mv.push(delegation.owner + "." + delegation.desc); // Name of the constructor to be called. mv.arrayStore(Type.getType(Object.class)); // Create the locals array and place it in the first element of the return array mv.loadLocal(args); mv.push(0); mv.push(argumentTypes.length + 1); mv.newArray(Type.getType(Object.class)); ByteCodeUtils.loadVariableArray(mv, ByteCodeUtils.toLocalVariables(Arrays.asList(argumentTypes)), 0); mv.dup(); mv.push(argumentTypes.length); ByteCodeUtils.newVariableArray(mv, variables); mv.arrayStore(Type.getType(Object.class)); mv.arrayStore(Type.getType(Object.class)); mv.loadLocal(args); mv.returnValue(); // Move the first variable up to be an argument initArgs.desc = initArgs.desc.replace(")", "[Ljava/lang/Object;)"); newDesc = method.desc.replace("(", "(L" + owner + ";"); MethodNode body = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "init$body", newDesc, null, exceptions); mv = new GeneratorAdapter(body, body.access, body.name, body.desc); newArgument = mv.newLocal(Type.getType("[Ljava/lang/Object;")); LabelNode labelAfter = new LabelNode(); labelAfter.accept(body); Set<LabelNode> bodyLabels = new HashSet<LabelNode>(); mv.loadLocal(newArgument); ByteCodeUtils.restoreVariables(mv, variables); insn = delegation.getNext(); while (insn != null) { if (insn instanceof LabelNode) { bodyLabels.add((LabelNode) insn); } insn.accept(mv); insn = insn.getNext(); } // manually transfer the exception table from the existing constructor to the new // "init$body" method. The labels were transferred just above so we can reuse them. //noinspection unchecked for (TryCatchBlockNode tryCatch : (List<TryCatchBlockNode>) method.tryCatchBlocks) { tryCatch.accept(mv); } //noinspection unchecked for (LocalVariableNode variable : (List<LocalVariableNode>) method.localVariables) { boolean startsInBody = bodyLabels.contains(variable.start); boolean endsInBody = bodyLabels.contains(variable.end); if (!startsInBody && !endsInBody) { if (variable.index != 0) { // '#0' on init$args is not 'this' variable.accept(initArgs); } } else if (startsInBody && endsInBody) { variable.accept(body); } else if (!startsInBody && endsInBody) { // The variable spans from the args to the end of the method, create two: if (variable.index != 0) { // '#0' on init$args is not 'this' LocalVariableNode var0 = new LocalVariableNode(variable.name, variable.desc, variable.signature, variable.start, labelBefore, variable.index); var0.accept(initArgs); } LocalVariableNode var1 = new LocalVariableNode(variable.name, variable.desc, variable.signature, labelAfter, variable.end, variable.index); var1.accept(body); } else { throw new IllegalStateException("Local variable starts after it ends."); } } // Move the first variable up to be an argument body.desc = body.desc.replace(")", "[Ljava/lang/Object;)"); return new Constructor(owner, method, fixed, loadThis, loadThisLine, initArgs, delegation, body, variables, localsAtLoadThis); }
From source file:com.android.ide.eclipse.apt.internal.analysis.InternalGetSetAnalyzer.java
License:Apache License
/** * Checks if a method is a getter/*www . j av a 2 s.co m*/ * @param methodTest The method to test * @return True if the method is a getter, false otherwise */ private boolean isGetter(final MethodNode methodTest) { boolean getter = false; final String desc = methodTest.desc; final Type[] arguments = Type.getArgumentTypes(desc); final Type returnType = Type.getReturnType(desc); if (arguments.length == 0 && returnType.getSort() != Type.VOID) { final InsnList instructions = methodTest.instructions; //three next to skip label and line number instructions final AbstractInsnNode first = instructions.getFirst().getNext().getNext(); final int returnOp = returnType.getOpcode(Opcodes.IRETURN); final int firstOp = first.getOpcode(); //check for static getter if ((Opcodes.ACC_STATIC & methodTest.access) == 0) { if (firstOp == Opcodes.ALOAD) { final AbstractInsnNode second = first.getNext(); if (second.getOpcode() == Opcodes.GETFIELD) { final AbstractInsnNode third = second.getNext(); if (third.getOpcode() == returnOp) { getter = true; } } } } else { if (firstOp == Opcodes.GETSTATIC) { final AbstractInsnNode second = first.getNext(); if (second.getOpcode() == returnOp) { getter = true; } } } } return getter; }
From source file:com.android.ide.eclipse.apt.internal.analysis.InternalGetSetAnalyzer.java
License:Apache License
/** * Checks if a method is a setter// www . j a va 2 s .c om * @param methodTest The method to be checked * @return True if the method is a setter, false otherwise */ private boolean isSetter(final MethodNode methodTest) { boolean setter = false; final String desc = methodTest.desc; final Type[] arguments = Type.getArgumentTypes(desc); final Type returnType = Type.getReturnType(desc); if (arguments.length == 1 && returnType.getSort() == Type.VOID) { final InsnList instructions = methodTest.instructions; //skip label and line number instructions final AbstractInsnNode first = instructions.getFirst().getNext().getNext(); final int loadOp = arguments[0].getOpcode(Opcodes.ILOAD); final int firstOp = first.getOpcode(); //check for static setter if ((Opcodes.ACC_STATIC & methodTest.access) == 0) { if (firstOp == Opcodes.ALOAD) { final AbstractInsnNode second = first.getNext(); if (second.getOpcode() == loadOp) { final AbstractInsnNode third = second.getNext(); if (third.getOpcode() == Opcodes.PUTFIELD) { //three next to skip label and line number instructions final AbstractInsnNode fourth = third.getNext().getNext().getNext(); if (fourth.getOpcode() == Opcodes.RETURN) { setter = true; } } } } } else { if (firstOp == loadOp) { final AbstractInsnNode second = first.getNext(); if (second.getOpcode() == Opcodes.PUTSTATIC) { final AbstractInsnNode third = second.getNext().getNext().getNext(); if (third.getOpcode() == Opcodes.RETURN) { setter = true; } } } } } return setter; }
From source file:com.android.tools.klint.checks.ControlFlowGraph.java
License:Apache License
/** Adds an exception try block node to this graph */ protected void exception(@NonNull AbstractInsnNode from, @NonNull TryCatchBlockNode tcb) { // Add tcb's to all instructions in the range LabelNode start = tcb.start;/*w ww. j av a 2s .c o m*/ LabelNode end = tcb.end; // exclusive // Add exception edges for all method calls in the range AbstractInsnNode curr = start; Node handlerNode = getNode(tcb.handler); while (curr != end && curr != null) { if (curr.getType() == AbstractInsnNode.METHOD_INSN) { // Method call; add exception edge to handler if (tcb.type == null) { // finally block: not an exception path getNode(curr).addSuccessor(handlerNode); } getNode(curr).addExceptionPath(handlerNode); } curr = curr.getNext(); } }
From source file:com.android.tools.klint.checks.ControlFlowGraph.java
License:Apache License
/** * Creates a human readable version of the graph * * @param start the starting instruction, or null if not known or to use the * first instruction//www .java2s . co m * @return a string version of the graph */ @NonNull public String toString(@Nullable Node start) { StringBuilder sb = new StringBuilder(400); AbstractInsnNode curr; if (start != null) { curr = start.instruction; } else { if (mNodeMap.isEmpty()) { return "<empty>"; } else { curr = mNodeMap.keySet().iterator().next(); while (curr.getPrevious() != null) { curr = curr.getPrevious(); } } } while (curr != null) { Node node = mNodeMap.get(curr); if (node != null) { sb.append(node.toString(true)); } curr = curr.getNext(); } return sb.toString(); }
From source file:com.android.tools.klint.checks.ControlFlowGraph.java
License:Apache License
/** * Generates dot output of the graph. This can be used with * graphwiz to visualize the graph. For example, if you * save the output as graph1.gv you can run * <pre>// w w w. j a va 2 s. com * $ dot -Tps graph1.gv -o graph1.ps * </pre> * to generate a postscript file, which you can then view * with "gv graph1.ps". * * (There are also some online web sites where you can * paste in dot graphs and see the visualization right * there in the browser.) * * @return a dot description of this control flow graph, * useful for debugging */ public String toDot(@Nullable Set<Node> highlight) { StringBuilder sb = new StringBuilder(); sb.append("digraph G {\n"); AbstractInsnNode instruction = mMethod.instructions.getFirst(); // Special start node sb.append(" start -> ").append(getId(mNodeMap.get(instruction))).append(";\n"); sb.append(" start [shape=plaintext];\n"); while (instruction != null) { Node node = mNodeMap.get(instruction); if (node != null) { if (node.successors != null) { for (Node to : node.successors) { sb.append(" ").append(getId(node)).append(" -> ").append(getId(to)); if (node.instruction instanceof JumpInsnNode) { sb.append(" [label=\""); if (((JumpInsnNode) node.instruction).label == to.instruction) { sb.append("yes"); } else { sb.append("no"); } sb.append("\"]"); } sb.append(";\n"); } } if (node.exceptions != null) { for (Node to : node.exceptions) { sb.append(getId(node)).append(" -> ").append(getId(to)); sb.append(" [label=\"exception\"];\n"); } } } instruction = instruction.getNext(); } // Labels sb.append("\n"); for (Node node : mNodeMap.values()) { instruction = node.instruction; sb.append(" ").append(getId(node)).append(" "); sb.append("[label=\"").append(dotDescribe(node)).append("\""); if (highlight != null && highlight.contains(node)) { sb.append(",shape=box,style=filled"); } else if (instruction instanceof LineNumberNode || instruction instanceof LabelNode || instruction instanceof FrameNode) { sb.append(",shape=oval,style=dotted"); } else { sb.append(",shape=box"); } sb.append("];\n"); } sb.append("}"); return sb.toString(); }
From source file:com.android.tools.klint.detector.api.ClassContext.java
License:Apache License
/** * Finds the line number closest to the given node * * @param node the instruction node to get a line number for * @return the closest line number, or -1 if not known *//*from w ww . j a v a 2s . c o m*/ public static int findLineNumber(@NonNull AbstractInsnNode node) { AbstractInsnNode curr = node; // First search backwards while (curr != null) { if (curr.getType() == AbstractInsnNode.LINE) { return ((LineNumberNode) curr).line; } curr = curr.getPrevious(); } // Then search forwards curr = node; while (curr != null) { if (curr.getType() == AbstractInsnNode.LINE) { return ((LineNumberNode) curr).line; } curr = curr.getNext(); } return -1; }
From source file:com.android.tools.klint.detector.api.LintUtils.java
License:Apache License
/** * Returns the next instruction after to the given node, ignoring label and * line number nodes.//from www.j a v a 2 s . c o m * * @param node the node to look up the next node for * @return the next instruction, or null if no next node was found */ @Nullable public static AbstractInsnNode getNextInstruction(@NonNull AbstractInsnNode node) { AbstractInsnNode next = node; while (true) { next = next.getNext(); if (next == null) { return null; } else { int type = next.getType(); if (type != AbstractInsnNode.LINE && type != AbstractInsnNode.LABEL && type != AbstractInsnNode.FRAME) { return next; } } } }
From source file:com.android.tools.lint.checks.ApiDetector.java
License:Apache License
@SuppressWarnings("rawtypes") // ASM API @Override/* ww w . j a v a 2 s. com*/ public void checkClass(@NonNull final ClassContext context, @NonNull ClassNode classNode) { if (mApiDatabase == null) { return; } if (AOSP_BUILD && classNode.name.startsWith("android/support/")) { //$NON-NLS-1$ return; } // Requires util package (add prebuilts/tools/common/asm-tools/asm-debug-all-4.0.jar) //classNode.accept(new TraceClassVisitor(new PrintWriter(System.out))); int classMinSdk = getClassMinSdk(context, classNode); if (classMinSdk == -1) { classMinSdk = getMinSdk(context); } List methodList = classNode.methods; if (methodList.isEmpty()) { return; } boolean checkCalls = context.isEnabled(UNSUPPORTED) || context.isEnabled(INLINED); boolean checkMethods = context.isEnabled(OVERRIDE) && context.getMainProject().getBuildSdk() >= 1; String frameworkParent = null; if (checkMethods) { LintDriver driver = context.getDriver(); String owner = classNode.superName; while (owner != null) { // For virtual dispatch, walk up the inheritance chain checking // each inherited method if ((owner.startsWith("android/") //$NON-NLS-1$ && !owner.startsWith("android/support/")) //$NON-NLS-1$ || owner.startsWith("java/") //$NON-NLS-1$ || owner.startsWith("javax/")) { //$NON-NLS-1$ frameworkParent = owner; break; } owner = driver.getSuperClass(owner); } if (frameworkParent == null) { checkMethods = false; } } if (checkCalls) { // Check implements/extends if (classNode.superName != null) { String signature = classNode.superName; checkExtendsClass(context, classNode, classMinSdk, signature); } if (classNode.interfaces != null) { @SuppressWarnings("unchecked") // ASM API List<String> interfaceList = classNode.interfaces; for (String signature : interfaceList) { checkExtendsClass(context, classNode, classMinSdk, signature); } } } for (Object m : methodList) { MethodNode method = (MethodNode) m; int minSdk = getLocalMinSdk(method.invisibleAnnotations); if (minSdk == -1) { minSdk = classMinSdk; } InsnList nodes = method.instructions; if (checkMethods && Character.isJavaIdentifierStart(method.name.charAt(0))) { int buildSdk = context.getMainProject().getBuildSdk(); String name = method.name; assert frameworkParent != null; int api = mApiDatabase.getCallVersion(frameworkParent, name, method.desc); if (api > buildSdk && buildSdk != -1) { // TODO: Don't complain if it's annotated with @Override; that means // somehow the build target isn't correct. String fqcn; String owner = classNode.name; if (CONSTRUCTOR_NAME.equals(name)) { fqcn = "new " + ClassContext.getFqcn(owner); //$NON-NLS-1$ } else { fqcn = ClassContext.getFqcn(owner) + '#' + name; } String message = String.format( "This method is not overriding anything with the current build " + "target, but will in API level %1$d (current target is %2$d): %3$s", api, buildSdk, fqcn); Location location = context.getLocation(method, classNode); context.report(OVERRIDE, method, null, location, message, null); } } if (!checkCalls) { continue; } if (CHECK_DECLARATIONS) { // Check types in parameter list and types of local variables List localVariables = method.localVariables; if (localVariables != null) { for (Object v : localVariables) { LocalVariableNode var = (LocalVariableNode) v; String desc = var.desc; if (desc.charAt(0) == 'L') { // "Lpackage/Class;" => "package/Bar" String className = desc.substring(1, desc.length() - 1); int api = mApiDatabase.getClassVersion(className); if (api > minSdk) { String fqcn = ClassContext.getFqcn(className); String message = String.format( "Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn); report(context, message, var.start, method, className.substring(className.lastIndexOf('/') + 1), null, SearchHints.create(NEAREST).matchJavaSymbol()); } } } } // Check return type // The parameter types are already handled as local variables so we can skip // right to the return type. // Check types in parameter list String signature = method.desc; if (signature != null) { int args = signature.indexOf(')'); if (args != -1 && signature.charAt(args + 1) == 'L') { String type = signature.substring(args + 2, signature.length() - 1); int api = mApiDatabase.getClassVersion(type); if (api > minSdk) { String fqcn = ClassContext.getFqcn(type); String message = String.format( "Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn); AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null; report(context, message, first, method, method.name, null, SearchHints.create(BACKWARD).matchJavaSymbol()); } } } } for (int i = 0, n = nodes.size(); i < n; i++) { AbstractInsnNode instruction = nodes.get(i); int type = instruction.getType(); if (type == AbstractInsnNode.METHOD_INSN) { MethodInsnNode node = (MethodInsnNode) instruction; String name = node.name; String owner = node.owner; String desc = node.desc; // No need to check methods in this local class; we know they // won't be an API match if (node.getOpcode() == Opcodes.INVOKEVIRTUAL && owner.equals(classNode.name)) { owner = classNode.superName; } boolean checkingSuperClass = false; while (owner != null) { int api = mApiDatabase.getCallVersion(owner, name, desc); if (api > minSdk) { if (method.name.startsWith(SWITCH_TABLE_PREFIX)) { // We're in a compiler-generated method to generate an // array indexed by enum ordinal values to enum values. The enum // itself must be requiring a higher API number than is // currently used, but the call site for the switch statement // will also be referencing it, so no need to report these // calls. break; } if (!checkingSuperClass && node.getOpcode() == Opcodes.INVOKEVIRTUAL && methodDefinedLocally(classNode, name, desc)) { break; } String fqcn; if (CONSTRUCTOR_NAME.equals(name)) { fqcn = "new " + ClassContext.getFqcn(owner); //$NON-NLS-1$ } else { fqcn = ClassContext.getFqcn(owner) + '#' + name; } String message = String.format( "Call requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn); if (name.equals(ORDINAL_METHOD) && instruction.getNext() != null && instruction.getNext().getNext() != null && instruction.getNext().getOpcode() == Opcodes.IALOAD && instruction.getNext().getNext().getOpcode() == Opcodes.TABLESWITCH) { message = String.format( "Enum for switch requires API level %1$d " + "(current min is %2$d): %3$s", api, minSdk, ClassContext.getFqcn(owner)); } report(context, message, node, method, name, null, SearchHints.create(FORWARD).matchJavaSymbol()); } // For virtual dispatch, walk up the inheritance chain checking // each inherited method if (owner.startsWith("android/") //$NON-NLS-1$ || owner.startsWith("javax/")) { //$NON-NLS-1$ // The API map has already inlined all inherited methods // so no need to keep checking up the chain // -- unless it's the support library which is also in // the android/ namespace: if (owner.startsWith("android/support/")) { //$NON-NLS-1$ owner = context.getDriver().getSuperClass(owner); } else { owner = null; } } else if (owner.startsWith("java/")) { //$NON-NLS-1$ if (owner.equals(LocaleDetector.DATE_FORMAT_OWNER)) { checkSimpleDateFormat(context, method, node, minSdk); } // Already inlined; see comment above owner = null; } else if (node.getOpcode() == Opcodes.INVOKEVIRTUAL) { owner = context.getDriver().getSuperClass(owner); } else if (node.getOpcode() == Opcodes.INVOKESTATIC && api == -1) { // Inherit through static classes as well owner = context.getDriver().getSuperClass(owner); } else { owner = null; } checkingSuperClass = true; } } else if (type == AbstractInsnNode.FIELD_INSN) { FieldInsnNode node = (FieldInsnNode) instruction; String name = node.name; String owner = node.owner; int api = mApiDatabase.getFieldVersion(owner, name); if (api > minSdk) { if (method.name.startsWith(SWITCH_TABLE_PREFIX)) { checkSwitchBlock(context, classNode, node, method, name, owner, api, minSdk); continue; } String fqcn = ClassContext.getFqcn(owner) + '#' + name; if (mPendingFields != null) { mPendingFields.remove(fqcn); } String message = String.format("Field requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn); report(context, message, node, method, name, null, SearchHints.create(FORWARD).matchJavaSymbol()); } } else if (type == AbstractInsnNode.LDC_INSN) { LdcInsnNode node = (LdcInsnNode) instruction; if (node.cst instanceof Type) { Type t = (Type) node.cst; String className = t.getInternalName(); int api = mApiDatabase.getClassVersion(className); if (api > minSdk) { String fqcn = ClassContext.getFqcn(className); String message = String.format( "Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn); report(context, message, node, method, className.substring(className.lastIndexOf('/') + 1), null, SearchHints.create(FORWARD).matchJavaSymbol()); } } } } } }
From source file:com.android.tools.lint.checks.ApiDetector.java
License:Apache License
@SuppressWarnings("rawtypes") // ASM API private static void checkSwitchBlock(ClassContext context, ClassNode classNode, FieldInsnNode field, MethodNode method, String name, String owner, int api, int minSdk) { // Switch statements on enums are tricky. The compiler will generate a method // which returns an array of the enum constants, indexed by their ordinal() values. // However, we only want to complain if the code is actually referencing one of // the non-available enum fields. //// w w w . j a v a 2s . co m // For the android.graphics.PorterDuff.Mode enum for example, the first few items // in the array are populated like this: // // L0 // ALOAD 0 // GETSTATIC android/graphics/PorterDuff$Mode.ADD : Landroid/graphics/PorterDuff$Mode; // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I // ICONST_1 // IASTORE // L1 // GOTO L3 // L2 // FRAME FULL [[I] [java/lang/NoSuchFieldError] // POP // L3 // FRAME SAME // ALOAD 0 // GETSTATIC android/graphics/PorterDuff$Mode.CLEAR : Landroid/graphics/PorterDuff$Mode; // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I // ICONST_2 // IASTORE // ... // So if we for example find that the "ADD" field isn't accessible, since it requires // API 11, we need to // (1) First find out what its ordinal number is. We can look at the following // instructions to discover this; it's the "ICONST_1" and "IASTORE" instructions. // (After ICONST_5 it moves on to BIPUSH 6, BIPUSH 7, etc.) // (2) Find the corresponding *usage* of this switch method. For the above enum, // the switch ordinal lookup method will be called // "$SWITCH_TABLE$android$graphics$PorterDuff$Mode" with desc "()[I". // This means we will be looking for an invocation in some other method which looks // like this: // INVOKESTATIC (current class).$SWITCH_TABLE$android$graphics$PorterDuff$Mode ()[I // (obviously, it can be invoked more than once) // Note that it can be used more than once in this class and all sites should be // checked! // (3) Look up the corresponding table switch, which should look something like this: // INVOKESTATIC (current class).$SWITCH_TABLE$android$graphics$PorterDuff$Mode ()[I // ALOAD 0 // INVOKEVIRTUAL android/graphics/PorterDuff$Mode.ordinal ()I // IALOAD // LOOKUPSWITCH // 2: L1 // 11: L2 // default: L3 // Here we need to see if the LOOKUPSWITCH instruction is referencing our target // case. Above we were looking for the "ADD" case which had ordinal 1. Since this // isn't explicitly referenced, we can ignore this field reference. AbstractInsnNode next = field.getNext(); if (next == null || next.getOpcode() != Opcodes.INVOKEVIRTUAL) { return; } next = next.getNext(); if (next == null) { return; } int ordinal; switch (next.getOpcode()) { case Opcodes.ICONST_0: ordinal = 0; break; case Opcodes.ICONST_1: ordinal = 1; break; case Opcodes.ICONST_2: ordinal = 2; break; case Opcodes.ICONST_3: ordinal = 3; break; case Opcodes.ICONST_4: ordinal = 4; break; case Opcodes.ICONST_5: ordinal = 5; break; case Opcodes.BIPUSH: { IntInsnNode iin = (IntInsnNode) next; ordinal = iin.operand; break; } default: return; } // Find usages of this call site List methodList = classNode.methods; for (Object m : methodList) { InsnList nodes = ((MethodNode) m).instructions; for (int i = 0, n = nodes.size(); i < n; i++) { AbstractInsnNode instruction = nodes.get(i); if (instruction.getOpcode() != Opcodes.INVOKESTATIC) { continue; } MethodInsnNode node = (MethodInsnNode) instruction; if (node.name.equals(method.name) && node.desc.equals(method.desc) && node.owner.equals(classNode.name)) { // Find lookup switch AbstractInsnNode target = getNextInstruction(node); while (target != null) { if (target.getOpcode() == Opcodes.LOOKUPSWITCH) { LookupSwitchInsnNode lookup = (LookupSwitchInsnNode) target; @SuppressWarnings("unchecked") // ASM API List<Integer> keys = lookup.keys; if (keys != null && keys.contains(ordinal)) { String fqcn = ClassContext.getFqcn(owner) + '#' + name; String message = String.format( "Enum value requires API level %1$d " + "(current min is %2$d): %3$s", api, minSdk, fqcn); report(context, message, lookup, (MethodNode) m, name, null, SearchHints.create(FORWARD).matchJavaSymbol()); // Break out of the inner target search only; the switch // statement could be used in other places in this class as // well and we want to report all problematic usages. break; } } target = getNextInstruction(target); } } } } }