Example usage for org.objectweb.asm.tree AbstractInsnNode getNext

List of usage examples for org.objectweb.asm.tree AbstractInsnNode getNext

Introduction

In this page you can find the example usage for org.objectweb.asm.tree AbstractInsnNode getNext.

Prototype

public AbstractInsnNode getNext() 

Source Link

Document

Returns the next instruction in the list to which this instruction belongs, if any.

Usage

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);
                }
            }
        }
    }
}