001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018package org.apache.bcel.verifier.structurals; 019 020import java.io.PrintWriter; 021import java.io.StringWriter; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Random; 025import java.util.Vector; 026 027import org.apache.bcel.Const; 028import org.apache.bcel.Repository; 029import org.apache.bcel.classfile.JavaClass; 030import org.apache.bcel.classfile.Method; 031import org.apache.bcel.generic.ConstantPoolGen; 032import org.apache.bcel.generic.InstructionHandle; 033import org.apache.bcel.generic.JsrInstruction; 034import org.apache.bcel.generic.MethodGen; 035import org.apache.bcel.generic.ObjectType; 036import org.apache.bcel.generic.RET; 037import org.apache.bcel.generic.ReferenceType; 038import org.apache.bcel.generic.ReturnInstruction; 039import org.apache.bcel.generic.ReturnaddressType; 040import org.apache.bcel.generic.Type; 041import org.apache.bcel.verifier.PassVerifier; 042import org.apache.bcel.verifier.VerificationResult; 043import org.apache.bcel.verifier.Verifier; 044import org.apache.bcel.verifier.exc.AssertionViolatedException; 045import org.apache.bcel.verifier.exc.StructuralCodeConstraintException; 046import org.apache.bcel.verifier.exc.VerifierConstraintViolatedException; 047 048/** 049 * This PassVerifier verifies a method of class file according to pass 3, so-called structural verification as described 050 * in The Java Virtual Machine Specification, 2nd edition. More detailed information is to be found at the do_verify() 051 * method's documentation. 052 * 053 * @see #do_verify() 054 */ 055 056public final class Pass3bVerifier extends PassVerifier { 057 /* 058 * TODO: Throughout pass 3b, upper halves of LONG and DOUBLE are represented by Type.UNKNOWN. This should be changed in 059 * favour of LONG_Upper and DOUBLE_Upper as in pass 2. 060 */ 061 062 /** 063 * An InstructionContextQueue is a utility class that holds (InstructionContext, ArrayList) pairs in a Queue data 064 * structure. This is used to hold information about InstructionContext objects externally --- i.e. that information is 065 * not saved inside the InstructionContext object itself. This is useful to save the execution path of the symbolic 066 * execution of the Pass3bVerifier - this is not information that belongs into the InstructionContext object itself. 067 * Only at "execute()"ing time, an InstructionContext object will get the current information we have about its symbolic 068 * execution predecessors. 069 */ 070 private static final class InstructionContextQueue { 071 // The following two fields together represent the queue. 072 /** The first elements from pairs in the queue. */ 073 private final List<InstructionContext> ics = new Vector<>(); 074 /** The second elements from pairs in the queue. */ 075 private final List<ArrayList<InstructionContext>> ecs = new Vector<>(); 076 077 /** 078 * Adds an (InstructionContext, ExecutionChain) pair to this queue. 079 * 080 * @param ic the InstructionContext 081 * @param executionChain the ExecutionChain 082 */ 083 public void add(final InstructionContext ic, final ArrayList<InstructionContext> executionChain) { 084 ics.add(ic); 085 ecs.add(executionChain); 086 } 087 088 /** 089 * Gets a specific ExecutionChain from the queue. 090 * 091 * @param i the index of the item to be fetched 092 * @return the indicated ExecutionChain 093 */ 094 public ArrayList<InstructionContext> getEC(final int i) { 095 return ecs.get(i); 096 } 097 098 /** 099 * Gets a specific InstructionContext from the queue. 100 * 101 * @param i the index of the item to be fetched 102 * @return the indicated InstructionContext 103 */ 104 public InstructionContext getIC(final int i) { 105 return ics.get(i); 106 } 107 108 /** 109 * Tests if InstructionContext queue is empty. 110 * 111 * @return true if the InstructionContext queue is empty. 112 */ 113 public boolean isEmpty() { 114 return ics.isEmpty(); 115 } 116 117 /** 118 * Removes a specific (InstructionContext, ExecutionChain) pair from their respective queues. 119 * 120 * @param i the index of the items to be removed 121 */ 122 public void remove(final int i) { 123 ics.remove(i); 124 ecs.remove(i); 125 } 126 127 /** 128 * Gets the size of the InstructionContext queue. 129 * 130 * @return the size of the InstructionQueue 131 */ 132 public int size() { 133 return ics.size(); 134 } 135 } // end Inner Class InstructionContextQueue 136 137 /** In DEBUG mode, the verification algorithm is not randomized. */ 138 private static final boolean DEBUG = true; 139 140 /** The Verifier that created this. */ 141 private final Verifier myOwner; 142 143 /** The method number to verify. */ 144 private final int methodNo; 145 146 /** 147 * This class should only be instantiated by a Verifier. 148 * 149 * @see org.apache.bcel.verifier.Verifier 150 */ 151 public Pass3bVerifier(final Verifier owner, final int method_no) { 152 myOwner = owner; 153 this.methodNo = method_no; 154 } 155 156 /** 157 * Whenever the outgoing frame situation of an InstructionContext changes, all its successors are put [back] into the 158 * queue [as if they were unvisited]. The proof of termination is about the existence of a fix point of frame merging. 159 */ 160 private void circulationPump(final MethodGen m, final ControlFlowGraph cfg, final InstructionContext start, final Frame vanillaFrame, 161 final InstConstraintVisitor icv, final ExecutionVisitor ev) { 162 final Random random = new Random(); 163 final InstructionContextQueue icq = new InstructionContextQueue(); 164 165 start.execute(vanillaFrame, new ArrayList<>(), icv, ev); 166 // new ArrayList() <=> no Instruction was executed before 167 // => Top-Level routine (no jsr call before) 168 icq.add(start, new ArrayList<>()); 169 170 // LOOP! 171 while (!icq.isEmpty()) { 172 InstructionContext u; 173 ArrayList<InstructionContext> ec; 174 if (!DEBUG) { 175 final int r = random.nextInt(icq.size()); 176 u = icq.getIC(r); 177 ec = icq.getEC(r); 178 icq.remove(r); 179 } else { 180 u = icq.getIC(0); 181 ec = icq.getEC(0); 182 icq.remove(0); 183 } 184 185 @SuppressWarnings("unchecked") // ec is of type ArrayList<InstructionContext> 186 final ArrayList<InstructionContext> oldchain = (ArrayList<InstructionContext>) ec.clone(); 187 @SuppressWarnings("unchecked") // ec is of type ArrayList<InstructionContext> 188 final ArrayList<InstructionContext> newchain = (ArrayList<InstructionContext>) ec.clone(); 189 newchain.add(u); 190 191 if (u.getInstruction().getInstruction() instanceof RET) { 192//System.err.println(u); 193 // We can only follow _one_ successor, the one after the 194 // JSR that was recently executed. 195 final RET ret = (RET) u.getInstruction().getInstruction(); 196 final ReturnaddressType t = (ReturnaddressType) u.getOutFrame(oldchain).getLocals().get(ret.getIndex()); 197 final InstructionContext theSuccessor = cfg.contextOf(t.getTarget()); 198 199 // Sanity check 200 InstructionContext lastJSR = null; 201 int skip_jsr = 0; 202 for (int ss = oldchain.size() - 1; ss >= 0; ss--) { 203 if (skip_jsr < 0) { 204 throw new AssertionViolatedException("More RET than JSR in execution chain?!"); 205 } 206//System.err.println("+"+oldchain.get(ss)); 207 if (oldchain.get(ss).getInstruction().getInstruction() instanceof JsrInstruction) { 208 if (skip_jsr == 0) { 209 lastJSR = oldchain.get(ss); 210 break; 211 } 212 skip_jsr--; 213 } 214 if (oldchain.get(ss).getInstruction().getInstruction() instanceof RET) { 215 skip_jsr++; 216 } 217 } 218 if (lastJSR == null) { 219 throw new AssertionViolatedException("RET without a JSR before in ExecutionChain?! EC: '" + oldchain + "'."); 220 } 221 final JsrInstruction jsr = (JsrInstruction) lastJSR.getInstruction().getInstruction(); 222 if (theSuccessor != cfg.contextOf(jsr.physicalSuccessor())) { 223 throw new AssertionViolatedException("RET '" + u.getInstruction() + "' info inconsistent: jump back to '" + theSuccessor + "' or '" 224 + cfg.contextOf(jsr.physicalSuccessor()) + "'?"); 225 } 226 227 if (theSuccessor.execute(u.getOutFrame(oldchain), newchain, icv, ev)) { 228 @SuppressWarnings("unchecked") // newchain is already of type ArrayList<InstructionContext> 229 final ArrayList<InstructionContext> newchainClone = (ArrayList<InstructionContext>) newchain.clone(); 230 icq.add(theSuccessor, newchainClone); 231 } 232 } else {// "not a ret" 233 234 // Normal successors. Add them to the queue of successors. 235 final InstructionContext[] succs = u.getSuccessors(); 236 for (final InstructionContext v : succs) { 237 if (v.execute(u.getOutFrame(oldchain), newchain, icv, ev)) { 238 @SuppressWarnings("unchecked") // newchain is already of type ArrayList<InstructionContext> 239 final ArrayList<InstructionContext> newchainClone = (ArrayList<InstructionContext>) newchain.clone(); 240 icq.add(v, newchainClone); 241 } 242 } 243 } // end "not a ret" 244 245 // Exception Handlers. Add them to the queue of successors. 246 // [subroutines are never protected; mandated by JustIce] 247 final ExceptionHandler[] exc_hds = u.getExceptionHandlers(); 248 for (final ExceptionHandler exc_hd : exc_hds) { 249 final InstructionContext v = cfg.contextOf(exc_hd.getHandlerStart()); 250 // TODO: the "oldchain" and "newchain" is used to determine the subroutine 251 // we're in (by searching for the last JSR) by the InstructionContext 252 // implementation. Therefore, we should not use this chain mechanism 253 // when dealing with exception handlers. 254 // Example: a JSR with an exception handler as its successor does not 255 // mean we're in a subroutine if we go to the exception handler. 256 // We should address this problem later; by now we simply "cut" the chain 257 // by using an empty chain for the exception handlers. 258 // if (v.execute(new Frame(u.getOutFrame(oldchain).getLocals(), 259 // new OperandStack (u.getOutFrame().getStack().maxStack(), 260 // (exc_hds[s].getExceptionType()==null? Type.THROWABLE : exc_hds[s].getExceptionType())) ), newchain), icv, ev) { 261 // icq.add(v, (ArrayList) newchain.clone()); 262 if (v.execute(new Frame(u.getOutFrame(oldchain).getLocals(), new OperandStack(u.getOutFrame(oldchain).getStack().maxStack(), 263 exc_hd.getExceptionType() == null ? Type.THROWABLE : exc_hd.getExceptionType())), new ArrayList<>(), icv, ev)) { 264 icq.add(v, new ArrayList<>()); 265 } 266 } 267 268 } // while (!icq.isEmpty()) END 269 270 InstructionHandle ih = start.getInstruction(); 271 do { 272 if (ih.getInstruction() instanceof ReturnInstruction && !cfg.isDead(ih)) { 273 final InstructionContext ic = cfg.contextOf(ih); 274 // TODO: This is buggy, we check only the top-level return instructions this way. 275 // Maybe some maniac returns from a method when in a subroutine? 276 final Frame f = ic.getOutFrame(new ArrayList<>()); 277 final LocalVariables lvs = f.getLocals(); 278 for (int i = 0; i < lvs.maxLocals(); i++) { 279 if (lvs.get(i) instanceof UninitializedObjectType) { 280 this.addMessage("Warning: ReturnInstruction '" + ic + "' may leave method with an uninitialized object in the local variables array '" 281 + lvs + "'."); 282 } 283 } 284 final OperandStack os = f.getStack(); 285 for (int i = 0; i < os.size(); i++) { 286 if (os.peek(i) instanceof UninitializedObjectType) { 287 this.addMessage( 288 "Warning: ReturnInstruction '" + ic + "' may leave method with an uninitialized object on the operand stack '" + os + "'."); 289 } 290 } 291 // see JVM $4.8.2 292 Type returnedType = null; 293 final OperandStack inStack = ic.getInFrame().getStack(); 294 if (inStack.size() >= 1) { 295 returnedType = inStack.peek(); 296 } else { 297 returnedType = Type.VOID; 298 } 299 300 if (returnedType != null) { 301 if (returnedType instanceof ReferenceType) { 302 try { 303 if (!((ReferenceType) returnedType).isCastableTo(m.getReturnType())) { 304 invalidReturnTypeError(returnedType, m); 305 } 306 } catch (final ClassNotFoundException e) { 307 // Don't know what to do now, so raise RuntimeException 308 throw new IllegalArgumentException(e); 309 } 310 } else if (!returnedType.equals(m.getReturnType().normalizeForStackOrLocal())) { 311 invalidReturnTypeError(returnedType, m); 312 } 313 } 314 } 315 } while ((ih = ih.getNext()) != null); 316 317 } 318 319 /** 320 * Pass 3b implements the data flow analysis as described in the Java Virtual Machine Specification, Second Edition. 321 * Later versions will use LocalVariablesInfo objects to verify if the verifier-inferred types and the class file's 322 * debug information (LocalVariables attributes) match [TODO]. 323 * 324 * @see org.apache.bcel.verifier.statics.LocalVariablesInfo 325 * @see org.apache.bcel.verifier.statics.Pass2Verifier#getLocalVariablesInfo(int) 326 */ 327 @Override 328 public VerificationResult do_verify() { 329 if (!myOwner.doPass3a(methodNo).equals(VerificationResult.VR_OK)) { 330 return VerificationResult.VR_NOTYET; 331 } 332 333 // Pass 3a ran before, so it's safe to assume the JavaClass object is 334 // in the BCEL repository. 335 JavaClass jc; 336 try { 337 jc = Repository.lookupClass(myOwner.getClassName()); 338 } catch (final ClassNotFoundException e) { 339 // FIXME: maybe not the best way to handle this 340 throw new AssertionViolatedException("Missing class: " + e, e); 341 } 342 343 final ConstantPoolGen constantPoolGen = new ConstantPoolGen(jc.getConstantPool()); 344 // Init Visitors 345 final InstConstraintVisitor icv = new InstConstraintVisitor(); 346 icv.setConstantPoolGen(constantPoolGen); 347 348 final ExecutionVisitor ev = new ExecutionVisitor(); 349 ev.setConstantPoolGen(constantPoolGen); 350 351 final Method[] methods = jc.getMethods(); // Method no "methodNo" exists, we ran Pass3a before on it! 352 353 try { 354 355 final MethodGen mg = new MethodGen(methods[methodNo], myOwner.getClassName(), constantPoolGen); 356 357 icv.setMethodGen(mg); 358 359 ////////////// DFA BEGINS HERE //////////////// 360 if (!(mg.isAbstract() || mg.isNative())) { // IF mg HAS CODE (See pass 2) 361 362 final ControlFlowGraph cfg = new ControlFlowGraph(mg); 363 364 // Build the initial frame situation for this method. 365 final Frame f = new Frame(mg.getMaxLocals(), mg.getMaxStack()); 366 if (!mg.isStatic()) { 367 if (mg.getName().equals(Const.CONSTRUCTOR_NAME)) { 368 Frame.setThis(new UninitializedObjectType(ObjectType.getInstance(jc.getClassName()))); 369 f.getLocals().set(0, Frame.getThis()); 370 } else { 371 Frame.setThis(null); 372 f.getLocals().set(0, ObjectType.getInstance(jc.getClassName())); 373 } 374 } 375 final Type[] argtypes = mg.getArgumentTypes(); 376 int twoslotoffset = 0; 377 for (int j = 0; j < argtypes.length; j++) { 378 if (argtypes[j] == Type.SHORT || argtypes[j] == Type.BYTE || argtypes[j] == Type.CHAR || argtypes[j] == Type.BOOLEAN) { 379 argtypes[j] = Type.INT; 380 } 381 f.getLocals().set(twoslotoffset + j + (mg.isStatic() ? 0 : 1), argtypes[j]); 382 if (argtypes[j].getSize() == 2) { 383 twoslotoffset++; 384 f.getLocals().set(twoslotoffset + j + (mg.isStatic() ? 0 : 1), Type.UNKNOWN); 385 } 386 } 387 circulationPump(mg, cfg, cfg.contextOf(mg.getInstructionList().getStart()), f, icv, ev); 388 } 389 } catch (final VerifierConstraintViolatedException ce) { 390 ce.extendMessage("Constraint violated in method '" + methods[methodNo] + "':\n", ""); 391 return new VerificationResult(VerificationResult.VERIFIED_REJECTED, ce.getMessage()); 392 } catch (final RuntimeException re) { 393 // These are internal errors 394 395 final StringWriter sw = new StringWriter(); 396 final PrintWriter pw = new PrintWriter(sw); 397 re.printStackTrace(pw); 398 399 throw new AssertionViolatedException("Some RuntimeException occurred while verify()ing class '" + jc.getClassName() + "', method '" 400 + methods[methodNo] + "'. Original RuntimeException's stack trace:\n---\n" + sw + "---\n", re); 401 } 402 return VerificationResult.VR_OK; 403 } 404 405 /** Returns the method number as supplied when instantiating. */ 406 public int getMethodNo() { 407 return methodNo; 408 } 409 410 /** 411 * Throws an exception indicating the returned type is not compatible with the return type of the given method. 412 * 413 * @param returnedType the type of the returned expression 414 * @param m the method we are processing 415 * @throws StructuralCodeConstraintException always 416 * @since 6.0 417 */ 418 public void invalidReturnTypeError(final Type returnedType, final MethodGen m) { 419 throw new StructuralCodeConstraintException("Returned type " + returnedType + " does not match Method's return type " + m.getReturnType()); 420 } 421}