Pass3bVerifier.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.bcel.verifier.structurals;

  18. import java.io.PrintWriter;
  19. import java.io.StringWriter;
  20. import java.util.ArrayList;
  21. import java.util.List;
  22. import java.util.Random;
  23. import java.util.Vector;

  24. import org.apache.bcel.Const;
  25. import org.apache.bcel.Repository;
  26. import org.apache.bcel.classfile.JavaClass;
  27. import org.apache.bcel.classfile.Method;
  28. import org.apache.bcel.generic.ConstantPoolGen;
  29. import org.apache.bcel.generic.InstructionHandle;
  30. import org.apache.bcel.generic.JsrInstruction;
  31. import org.apache.bcel.generic.MethodGen;
  32. import org.apache.bcel.generic.ObjectType;
  33. import org.apache.bcel.generic.RET;
  34. import org.apache.bcel.generic.ReferenceType;
  35. import org.apache.bcel.generic.ReturnInstruction;
  36. import org.apache.bcel.generic.ReturnaddressType;
  37. import org.apache.bcel.generic.Type;
  38. import org.apache.bcel.verifier.PassVerifier;
  39. import org.apache.bcel.verifier.VerificationResult;
  40. import org.apache.bcel.verifier.Verifier;
  41. import org.apache.bcel.verifier.exc.AssertionViolatedException;
  42. import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
  43. import org.apache.bcel.verifier.exc.VerifierConstraintViolatedException;

  44. /**
  45.  * This PassVerifier verifies a method of class file according to pass 3, so-called structural verification as described
  46.  * in The Java Virtual Machine Specification, 2nd edition. More detailed information is to be found at the do_verify()
  47.  * method's documentation.
  48.  *
  49.  * @see #do_verify()
  50.  */

  51. public final class Pass3bVerifier extends PassVerifier {
  52.     /*
  53.      * TODO: Throughout pass 3b, upper halves of LONG and DOUBLE are represented by Type.UNKNOWN. This should be changed in
  54.      * favour of LONG_Upper and DOUBLE_Upper as in pass 2.
  55.      */

  56.     /**
  57.      * An InstructionContextQueue is a utility class that holds (InstructionContext, ArrayList) pairs in a Queue data
  58.      * structure. This is used to hold information about InstructionContext objects externally --- i.e. that information is
  59.      * not saved inside the InstructionContext object itself. This is useful to save the execution path of the symbolic
  60.      * execution of the Pass3bVerifier - this is not information that belongs into the InstructionContext object itself.
  61.      * Only at "execute()"ing time, an InstructionContext object will get the current information we have about its symbolic
  62.      * execution predecessors.
  63.      */
  64.     private static final class InstructionContextQueue {
  65.         // The following two fields together represent the queue.
  66.         /** The first elements from pairs in the queue. */
  67.         private final List<InstructionContext> ics = new Vector<>();
  68.         /** The second elements from pairs in the queue. */
  69.         private final List<ArrayList<InstructionContext>> ecs = new Vector<>();

  70.         /**
  71.          * Adds an (InstructionContext, ExecutionChain) pair to this queue.
  72.          *
  73.          * @param ic the InstructionContext
  74.          * @param executionChain the ExecutionChain
  75.          */
  76.         public void add(final InstructionContext ic, final ArrayList<InstructionContext> executionChain) {
  77.             ics.add(ic);
  78.             ecs.add(executionChain);
  79.         }

  80.         /**
  81.          * Gets a specific ExecutionChain from the queue.
  82.          *
  83.          * @param i the index of the item to be fetched
  84.          * @return the indicated ExecutionChain
  85.          */
  86.         public ArrayList<InstructionContext> getEC(final int i) {
  87.             return ecs.get(i);
  88.         }

  89.         /**
  90.          * Gets a specific InstructionContext from the queue.
  91.          *
  92.          * @param i the index of the item to be fetched
  93.          * @return the indicated InstructionContext
  94.          */
  95.         public InstructionContext getIC(final int i) {
  96.             return ics.get(i);
  97.         }

  98.         /**
  99.          * Tests if InstructionContext queue is empty.
  100.          *
  101.          * @return true if the InstructionContext queue is empty.
  102.          */
  103.         public boolean isEmpty() {
  104.             return ics.isEmpty();
  105.         }

  106.         /**
  107.          * Removes a specific (InstructionContext, ExecutionChain) pair from their respective queues.
  108.          *
  109.          * @param i the index of the items to be removed
  110.          */
  111.         public void remove(final int i) {
  112.             ics.remove(i);
  113.             ecs.remove(i);
  114.         }

  115.         /**
  116.          * Gets the size of the InstructionContext queue.
  117.          *
  118.          * @return the size of the InstructionQueue
  119.          */
  120.         public int size() {
  121.             return ics.size();
  122.         }
  123.     } // end Inner Class InstructionContextQueue

  124.     /** In DEBUG mode, the verification algorithm is not randomized. */
  125.     private static final boolean DEBUG = true;

  126.     /** The Verifier that created this. */
  127.     private final Verifier myOwner;

  128.     /** The method number to verify. */
  129.     private final int methodNo;

  130.     /**
  131.      * This class should only be instantiated by a Verifier.
  132.      *
  133.      * @see org.apache.bcel.verifier.Verifier
  134.      */
  135.     public Pass3bVerifier(final Verifier myOwner, final int methodNo) {
  136.         this.myOwner = myOwner;
  137.         this.methodNo = methodNo;
  138.     }

  139.     /**
  140.      * Whenever the outgoing frame situation of an InstructionContext changes, all its successors are put [back] into the
  141.      * queue [as if they were unvisited]. The proof of termination is about the existence of a fix point of frame merging.
  142.      */
  143.     private void circulationPump(final MethodGen m, final ControlFlowGraph cfg, final InstructionContext start, final Frame vanillaFrame,
  144.         final InstConstraintVisitor icv, final ExecutionVisitor ev) {
  145.         final Random random = new Random();
  146.         final InstructionContextQueue icq = new InstructionContextQueue();

  147.         start.execute(vanillaFrame, new ArrayList<>(), icv, ev);
  148.         // new ArrayList() <=> no Instruction was executed before
  149.         // => Top-Level routine (no jsr call before)
  150.         icq.add(start, new ArrayList<>());

  151.         // LOOP!
  152.         while (!icq.isEmpty()) {
  153.             InstructionContext u;
  154.             ArrayList<InstructionContext> ec;
  155.             if (!DEBUG) {
  156.                 final int r = random.nextInt(icq.size());
  157.                 u = icq.getIC(r);
  158.                 ec = icq.getEC(r);
  159.                 icq.remove(r);
  160.             } else {
  161.                 u = icq.getIC(0);
  162.                 ec = icq.getEC(0);
  163.                 icq.remove(0);
  164.             }

  165.             @SuppressWarnings("unchecked") // ec is of type ArrayList<InstructionContext>
  166.             final ArrayList<InstructionContext> oldchain = (ArrayList<InstructionContext>) ec.clone();
  167.             @SuppressWarnings("unchecked") // ec is of type ArrayList<InstructionContext>
  168.             final ArrayList<InstructionContext> newchain = (ArrayList<InstructionContext>) ec.clone();
  169.             newchain.add(u);

  170.             if (u.getInstruction().getInstruction() instanceof RET) {
  171. //System.err.println(u);
  172.                 // We can only follow _one_ successor, the one after the
  173.                 // JSR that was recently executed.
  174.                 final RET ret = (RET) u.getInstruction().getInstruction();
  175.                 final ReturnaddressType t = (ReturnaddressType) u.getOutFrame(oldchain).getLocals().get(ret.getIndex());
  176.                 final InstructionContext theSuccessor = cfg.contextOf(t.getTarget());

  177.                 // Sanity check
  178.                 InstructionContext lastJSR = null;
  179.                 int skipJsr = 0;
  180.                 for (int ss = oldchain.size() - 1; ss >= 0; ss--) {
  181.                     if (skipJsr < 0) {
  182.                         throw new AssertionViolatedException("More RET than JSR in execution chain?!");
  183.                     }
  184. //System.err.println("+"+oldchain.get(ss));
  185.                     if (oldchain.get(ss).getInstruction().getInstruction() instanceof JsrInstruction) {
  186.                         if (skipJsr == 0) {
  187.                             lastJSR = oldchain.get(ss);
  188.                             break;
  189.                         }
  190.                         skipJsr--;
  191.                     }
  192.                     if (oldchain.get(ss).getInstruction().getInstruction() instanceof RET) {
  193.                         skipJsr++;
  194.                     }
  195.                 }
  196.                 if (lastJSR == null) {
  197.                     throw new AssertionViolatedException("RET without a JSR before in ExecutionChain?! EC: '" + oldchain + "'.");
  198.                 }
  199.                 final JsrInstruction jsr = (JsrInstruction) lastJSR.getInstruction().getInstruction();
  200.                 if (theSuccessor != cfg.contextOf(jsr.physicalSuccessor())) {
  201.                     throw new AssertionViolatedException("RET '" + u.getInstruction() + "' info inconsistent: jump back to '" + theSuccessor + "' or '"
  202.                         + cfg.contextOf(jsr.physicalSuccessor()) + "'?");
  203.                 }

  204.                 if (theSuccessor.execute(u.getOutFrame(oldchain), newchain, icv, ev)) {
  205.                     @SuppressWarnings("unchecked") // newchain is already of type ArrayList<InstructionContext>
  206.                     final ArrayList<InstructionContext> newchainClone = (ArrayList<InstructionContext>) newchain.clone();
  207.                     icq.add(theSuccessor, newchainClone);
  208.                 }
  209.             } else { // "not a ret"

  210.                 // Normal successors. Add them to the queue of successors.
  211.                 final InstructionContext[] succs = u.getSuccessors();
  212.                 for (final InstructionContext v : succs) {
  213.                     if (v.execute(u.getOutFrame(oldchain), newchain, icv, ev)) {
  214.                         @SuppressWarnings("unchecked") // newchain is already of type ArrayList<InstructionContext>
  215.                         final ArrayList<InstructionContext> newchainClone = (ArrayList<InstructionContext>) newchain.clone();
  216.                         icq.add(v, newchainClone);
  217.                     }
  218.                 }
  219.             } // end "not a ret"

  220.             // Exception Handlers. Add them to the queue of successors.
  221.             // [subroutines are never protected; mandated by JustIce]
  222.             final ExceptionHandler[] excHds = u.getExceptionHandlers();
  223.             for (final ExceptionHandler excHd : excHds) {
  224.                 final InstructionContext v = cfg.contextOf(excHd.getHandlerStart());
  225.                 // TODO: the "oldchain" and "newchain" is used to determine the subroutine
  226.                 // we're in (by searching for the last JSR) by the InstructionContext
  227.                 // implementation. Therefore, we should not use this chain mechanism
  228.                 // when dealing with exception handlers.
  229.                 // Example: a JSR with an exception handler as its successor does not
  230.                 // mean we're in a subroutine if we go to the exception handler.
  231.                 // We should address this problem later; by now we simply "cut" the chain
  232.                 // by using an empty chain for the exception handlers.
  233.                 // if (v.execute(new Frame(u.getOutFrame(oldchain).getLocals(),
  234.                 // new OperandStack (u.getOutFrame().getStack().maxStack(),
  235.                 // (exc_hds[s].getExceptionType() == null ? Type.THROWABLE : exc_hds[s].getExceptionType())) ), newchain), icv, ev) {
  236.                 // icq.add(v, (ArrayList) newchain.clone());
  237.                 if (v.execute(new Frame(u.getOutFrame(oldchain).getLocals(), new OperandStack(u.getOutFrame(oldchain).getStack().maxStack(),
  238.                     excHd.getExceptionType() == null ? Type.THROWABLE : excHd.getExceptionType())), new ArrayList<>(), icv, ev)) {
  239.                     icq.add(v, new ArrayList<>());
  240.                 }
  241.             }

  242.         } // while (!icq.isEmpty()) END

  243.         InstructionHandle ih = start.getInstruction();
  244.         do {
  245.             if (ih.getInstruction() instanceof ReturnInstruction && !cfg.isDead(ih)) {
  246.                 final InstructionContext ic = cfg.contextOf(ih);
  247.                 // TODO: This is buggy, we check only the top-level return instructions this way.
  248.                 // Maybe some maniac returns from a method when in a subroutine?
  249.                 final Frame f = ic.getOutFrame(new ArrayList<>());
  250.                 final LocalVariables lvs = f.getLocals();
  251.                 for (int i = 0; i < lvs.maxLocals(); i++) {
  252.                     if (lvs.get(i) instanceof UninitializedObjectType) {
  253.                         addMessage("Warning: ReturnInstruction '" + ic + "' may leave method with an uninitialized object in the local variables array '"
  254.                             + lvs + "'.");
  255.                     }
  256.                 }
  257.                 final OperandStack os = f.getStack();
  258.                 for (int i = 0; i < os.size(); i++) {
  259.                     if (os.peek(i) instanceof UninitializedObjectType) {
  260.                         addMessage(
  261.                             "Warning: ReturnInstruction '" + ic + "' may leave method with an uninitialized object on the operand stack '" + os + "'.");
  262.                     }
  263.                 }
  264.                 // see JVM $4.8.2
  265.                 Type returnedType = null;
  266.                 final OperandStack inStack = ic.getInFrame().getStack();
  267.                 if (inStack.size() >= 1) {
  268.                     returnedType = inStack.peek();
  269.                 } else {
  270.                     returnedType = Type.VOID;
  271.                 }

  272.                 if (returnedType != null) {
  273.                     if (returnedType instanceof ReferenceType) {
  274.                         try {
  275.                             if (!((ReferenceType) returnedType).isCastableTo(m.getReturnType())) {
  276.                                 invalidReturnTypeError(returnedType, m);
  277.                             }
  278.                         } catch (final ClassNotFoundException e) {
  279.                             // Don't know what to do now, so raise RuntimeException
  280.                             throw new IllegalArgumentException(e);
  281.                         }
  282.                     } else if (!returnedType.equals(m.getReturnType().normalizeForStackOrLocal())) {
  283.                         invalidReturnTypeError(returnedType, m);
  284.                     }
  285.                 }
  286.             }
  287.         } while ((ih = ih.getNext()) != null);

  288.     }

  289.     /**
  290.      * Pass 3b implements the data flow analysis as described in the Java Virtual Machine Specification, Second Edition.
  291.      * Later versions will use LocalVariablesInfo objects to verify if the verifier-inferred types and the class file's
  292.      * debug information (LocalVariables attributes) match [TODO].
  293.      *
  294.      * @see org.apache.bcel.verifier.statics.LocalVariablesInfo
  295.      * @see org.apache.bcel.verifier.statics.Pass2Verifier#getLocalVariablesInfo(int)
  296.      */
  297.     @Override
  298.     public VerificationResult do_verify() {
  299.         if (!myOwner.doPass3a(methodNo).equals(VerificationResult.VR_OK)) {
  300.             return VerificationResult.VR_NOTYET;
  301.         }

  302.         // Pass 3a ran before, so it's safe to assume the JavaClass object is
  303.         // in the BCEL repository.
  304.         JavaClass jc;
  305.         try {
  306.             jc = Repository.lookupClass(myOwner.getClassName());
  307.         } catch (final ClassNotFoundException e) {
  308.             // FIXME: maybe not the best way to handle this
  309.             throw new AssertionViolatedException("Missing class: " + e, e);
  310.         }

  311.         final ConstantPoolGen constantPoolGen = new ConstantPoolGen(jc.getConstantPool());
  312.         // Init Visitors
  313.         final InstConstraintVisitor icv = new InstConstraintVisitor();
  314.         icv.setConstantPoolGen(constantPoolGen);

  315.         final ExecutionVisitor ev = new ExecutionVisitor();
  316.         ev.setConstantPoolGen(constantPoolGen);

  317.         final Method[] methods = jc.getMethods(); // Method no "methodNo" exists, we ran Pass3a before on it!

  318.         try {

  319.             final MethodGen mg = new MethodGen(methods[methodNo], myOwner.getClassName(), constantPoolGen);

  320.             icv.setMethodGen(mg);

  321.             ////////////// DFA BEGINS HERE ////////////////
  322.             if (!(mg.isAbstract() || mg.isNative())) { // IF mg HAS CODE (See pass 2)

  323.                 final ControlFlowGraph cfg = new ControlFlowGraph(mg);

  324.                 // Build the initial frame situation for this method.
  325.                 final Frame f = new Frame(mg.getMaxLocals(), mg.getMaxStack());
  326.                 if (!mg.isStatic()) {
  327.                     if (mg.getName().equals(Const.CONSTRUCTOR_NAME)) {
  328.                         Frame.setThis(new UninitializedObjectType(ObjectType.getInstance(jc.getClassName())));
  329.                         f.getLocals().set(0, Frame.getThis());
  330.                     } else {
  331.                         Frame.setThis(null);
  332.                         f.getLocals().set(0, ObjectType.getInstance(jc.getClassName()));
  333.                     }
  334.                 }
  335.                 final Type[] argtypes = mg.getArgumentTypes();
  336.                 int twoslotoffset = 0;
  337.                 for (int j = 0; j < argtypes.length; j++) {
  338.                     if (argtypes[j] == Type.SHORT || argtypes[j] == Type.BYTE || argtypes[j] == Type.CHAR || argtypes[j] == Type.BOOLEAN) {
  339.                         argtypes[j] = Type.INT;
  340.                     }
  341.                     f.getLocals().set(twoslotoffset + j + (mg.isStatic() ? 0 : 1), argtypes[j]);
  342.                     if (argtypes[j].getSize() == 2) {
  343.                         twoslotoffset++;
  344.                         f.getLocals().set(twoslotoffset + j + (mg.isStatic() ? 0 : 1), Type.UNKNOWN);
  345.                     }
  346.                 }
  347.                 circulationPump(mg, cfg, cfg.contextOf(mg.getInstructionList().getStart()), f, icv, ev);
  348.             }
  349.         } catch (final VerifierConstraintViolatedException ce) {
  350.             ce.extendMessage("Constraint violated in method '" + methods[methodNo] + "':\n", "");
  351.             return new VerificationResult(VerificationResult.VERIFIED_REJECTED, ce.getMessage());
  352.         } catch (final RuntimeException re) {
  353.             // These are internal errors

  354.             final StringWriter sw = new StringWriter();
  355.             final PrintWriter pw = new PrintWriter(sw);
  356.             re.printStackTrace(pw);

  357.             throw new AssertionViolatedException("Some RuntimeException occurred while verify()ing class '" + jc.getClassName() + "', method '"
  358.                 + methods[methodNo] + "'. Original RuntimeException's stack trace:\n---\n" + sw + "---\n", re);
  359.         }
  360.         return VerificationResult.VR_OK;
  361.     }

  362.     /** Returns the method number as supplied when instantiating. */
  363.     public int getMethodNo() {
  364.         return methodNo;
  365.     }

  366.     /**
  367.      * Throws an exception indicating the returned type is not compatible with the return type of the given method.
  368.      *
  369.      * @param returnedType the type of the returned expression
  370.      * @param m the method we are processing
  371.      * @throws StructuralCodeConstraintException always
  372.      * @since 6.0
  373.      */
  374.     public void invalidReturnTypeError(final Type returnedType, final MethodGen m) {
  375.         throw new StructuralCodeConstraintException("Returned type " + returnedType + " does not match Method's return type " + m.getReturnType());
  376.     }
  377. }