1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * https://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.bcel.verifier.structurals;
20
21 import java.io.PrintWriter;
22 import java.io.StringWriter;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Random;
26 import java.util.Vector;
27
28 import org.apache.bcel.Const;
29 import org.apache.bcel.Repository;
30 import org.apache.bcel.classfile.JavaClass;
31 import org.apache.bcel.classfile.Method;
32 import org.apache.bcel.generic.ConstantPoolGen;
33 import org.apache.bcel.generic.InstructionHandle;
34 import org.apache.bcel.generic.JsrInstruction;
35 import org.apache.bcel.generic.MethodGen;
36 import org.apache.bcel.generic.ObjectType;
37 import org.apache.bcel.generic.RET;
38 import org.apache.bcel.generic.ReferenceType;
39 import org.apache.bcel.generic.ReturnInstruction;
40 import org.apache.bcel.generic.ReturnaddressType;
41 import org.apache.bcel.generic.Type;
42 import org.apache.bcel.verifier.PassVerifier;
43 import org.apache.bcel.verifier.VerificationResult;
44 import org.apache.bcel.verifier.Verifier;
45 import org.apache.bcel.verifier.exc.AssertionViolatedException;
46 import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
47 import org.apache.bcel.verifier.exc.VerifierConstraintViolatedException;
48
49 /**
50 * This PassVerifier verifies a method of class file according to pass 3, so-called structural verification as described in The Java Virtual Machine
51 * Specification, 2nd edition. More detailed information is to be found at the do_verify() method's documentation.
52 * <p>
53 * The system property {@code org.apache.bcel.verifier.maxFrameSlots} bounds the size of the pass 3b data flow analysis, measured in frame slots:
54 * {@code (max_locals + max_stack) * instruction count} of the method under verification. Methods above the bound are rejected instead of analyzed. The default
55 * is 100,000,000; a value of zero or less disables the bound.
56 * </p>
57 *
58 * @see #do_verify()
59 */
60 public final class Pass3bVerifier extends PassVerifier {
61 /*
62 * TODO: Throughout pass 3b, upper halves of LONG and DOUBLE are represented by Type.UNKNOWN. This should be changed in
63 * favor of LONG_Upper and DOUBLE_Upper as in pass 2.
64 */
65
66 /**
67 * An InstructionContextQueue is a utility class that holds (InstructionContext, ArrayList) pairs in a Queue data
68 * structure. This is used to hold information about InstructionContext objects externally --- for example that information is
69 * not saved inside the InstructionContext object itself. This is useful to save the execution path of the symbolic
70 * execution of the Pass3bVerifier - this is not information that belongs into the InstructionContext object itself.
71 * Only at "execute()"ing time, an InstructionContext object will get the current information we have about its symbolic
72 * execution predecessors.
73 */
74 private static final class InstructionContextQueue {
75 // The following two fields together represent the queue.
76
77 /** The first elements from pairs in the queue. */
78 private final List<InstructionContext> ics = new Vector<>();
79
80 /** The second elements from pairs in the queue. */
81 private final List<ArrayList<InstructionContext>> ecs = new Vector<>();
82
83 /**
84 * Adds an (InstructionContext, ExecutionChain) pair to this queue.
85 *
86 * @param ic The InstructionContext.
87 * @param executionChain The ExecutionChain.
88 */
89 public void add(final InstructionContext ic, final ArrayList<InstructionContext> executionChain) {
90 ics.add(ic);
91 ecs.add(executionChain);
92 }
93
94 /**
95 * Gets a specific ExecutionChain from the queue.
96 *
97 * @param i The index of the item to be fetched.
98 * @return The indicated ExecutionChain.
99 */
100 public ArrayList<InstructionContext> getEC(final int i) {
101 return ecs.get(i);
102 }
103
104 /**
105 * Gets a specific InstructionContext from the queue.
106 *
107 * @param i The index of the item to be fetched.
108 * @return The indicated InstructionContext.
109 */
110 public InstructionContext getIC(final int i) {
111 return ics.get(i);
112 }
113
114 /**
115 * Tests if InstructionContext queue is empty.
116 *
117 * @return true if the InstructionContext queue is empty.
118 */
119 public boolean isEmpty() {
120 return ics.isEmpty();
121 }
122
123 /**
124 * Removes a specific (InstructionContext, ExecutionChain) pair from their respective queues.
125 *
126 * @param i The index of the items to be removed.
127 */
128 public void remove(final int i) {
129 ics.remove(i);
130 ecs.remove(i);
131 }
132
133 /**
134 * Gets the size of the InstructionContext queue.
135 *
136 * @return The size of the InstructionQueue.
137 */
138 public int size() {
139 return ics.size();
140 }
141 } // end Inner Class InstructionContextQueue
142
143 /** In DEBUG mode, the verification algorithm is not randomized. */
144 private static final boolean DEBUG = true;
145
146 /**
147 * The name of the system property bounding the size of the pass 3b data flow analysis, measured in frame slots:
148 * {@code (max_locals + max_stack) * instruction count} of the method under verification. Methods above the bound
149 * are rejected instead of analyzed. The default is 100,000,000; a value of zero or less disables the bound.
150 */
151 private static final String MAX_FRAME_SLOTS_PROPERTY = "org.apache.bcel.verifier.maxFrameSlots";
152
153 private static final long MAX_FRAME_SLOTS = Long.getLong(MAX_FRAME_SLOTS_PROPERTY, 100_000_000L).longValue();
154
155 /** The Verifier that created this. */
156 private final Verifier myOwner;
157
158 /** The method number to verify. */
159 private final int methodNo;
160
161 /**
162 * This class should only be instantiated by a Verifier.
163 *
164 * @param myOwner The verifier that owns this Pass3bVerifier.
165 * @param methodNo The method number.
166 * @see org.apache.bcel.verifier.Verifier
167 */
168 public Pass3bVerifier(final Verifier myOwner, final int methodNo) {
169 this.myOwner = myOwner;
170 this.methodNo = methodNo;
171 }
172
173 /**
174 * Whenever the outgoing frame situation of an InstructionContext changes, all its successors are put [back] into the
175 * queue [as if they were unvisited]. The proof of termination is about the existence of a fix point of frame merging.
176 */
177 private void circulationPump(final MethodGen m, final ControlFlowGraph cfg, final InstructionContext start, final Frame vanillaFrame,
178 final InstConstraintVisitor icv, final ExecutionVisitor ev) {
179 final Random random = new Random();
180 final InstructionContextQueue icq = new InstructionContextQueue();
181
182 start.execute(vanillaFrame, new ArrayList<>(), icv, ev);
183 // new ArrayList() <=> no Instruction was executed before
184 // => Top-Level routine (no jsr call before)
185 icq.add(start, new ArrayList<>());
186
187 // LOOP!
188 while (!icq.isEmpty()) {
189 final InstructionContext u;
190 final ArrayList<InstructionContext> ec;
191 if (!DEBUG) {
192 final int r = random.nextInt(icq.size());
193 u = icq.getIC(r);
194 ec = icq.getEC(r);
195 icq.remove(r);
196 } else {
197 u = icq.getIC(0);
198 ec = icq.getEC(0);
199 icq.remove(0);
200 }
201
202 @SuppressWarnings("unchecked") // ec is of type ArrayList<InstructionContext>
203 final ArrayList<InstructionContext> oldchain = (ArrayList<InstructionContext>) ec.clone();
204 @SuppressWarnings("unchecked") // ec is of type ArrayList<InstructionContext>
205 final ArrayList<InstructionContext> newchain = (ArrayList<InstructionContext>) ec.clone();
206 newchain.add(u);
207
208 if (u.getInstruction().getInstruction() instanceof RET) {
209 //System.err.println(u);
210 // We can only follow _one_ successor, the one after the
211 // JSR that was recently executed.
212 final RET ret = (RET) u.getInstruction().getInstruction();
213 final ReturnaddressType t = (ReturnaddressType) u.getOutFrame(oldchain).getLocals().get(ret.getIndex());
214 final InstructionContext theSuccessor = cfg.contextOf(t.getTarget());
215
216 // Sanity check
217 InstructionContext lastJSR = null;
218 int skipJsr = 0;
219 for (int ss = oldchain.size() - 1; ss >= 0; ss--) {
220 if (skipJsr < 0) {
221 throw new AssertionViolatedException("More RET than JSR in execution chain?.");
222 }
223 //System.err.println("+"+oldchain.get(ss));
224 if (oldchain.get(ss).getInstruction().getInstruction() instanceof JsrInstruction) {
225 if (skipJsr == 0) {
226 lastJSR = oldchain.get(ss);
227 break;
228 }
229 skipJsr--;
230 }
231 if (oldchain.get(ss).getInstruction().getInstruction() instanceof RET) {
232 skipJsr++;
233 }
234 }
235 if (lastJSR == null) {
236 throw new AssertionViolatedException("RET without a JSR before in ExecutionChain?! EC: '" + oldchain + "'.");
237 }
238 final JsrInstruction jsr = (JsrInstruction) lastJSR.getInstruction().getInstruction();
239 if (theSuccessor != cfg.contextOf(jsr.physicalSuccessor())) {
240 throw new AssertionViolatedException("RET '" + u.getInstruction() + "' info inconsistent: jump back to '" + theSuccessor + "' or '"
241 + cfg.contextOf(jsr.physicalSuccessor()) + "'?");
242 }
243
244 if (theSuccessor.execute(u.getOutFrame(oldchain), newchain, icv, ev)) {
245 @SuppressWarnings("unchecked") // newchain is already of type ArrayList<InstructionContext>
246 final ArrayList<InstructionContext> newchainClone = (ArrayList<InstructionContext>) newchain.clone();
247 icq.add(theSuccessor, newchainClone);
248 }
249 } else { // "not a ret"
250
251 // Normal successors. Add them to the queue of successors.
252 final InstructionContext[] succs = u.getSuccessors();
253 for (final InstructionContext v : succs) {
254 if (v.execute(u.getOutFrame(oldchain), newchain, icv, ev)) {
255 @SuppressWarnings("unchecked") // newchain is already of type ArrayList<InstructionContext>
256 final ArrayList<InstructionContext> newchainClone = (ArrayList<InstructionContext>) newchain.clone();
257 icq.add(v, newchainClone);
258 }
259 }
260 } // end "not a ret"
261
262 // Exception Handlers. Add them to the queue of successors.
263 // [subroutines are never protected; mandated by JustIce]
264 final ExceptionHandler[] excHds = u.getExceptionHandlers();
265 for (final ExceptionHandler excHd : excHds) {
266 final InstructionContext v = cfg.contextOf(excHd.getHandlerStart());
267 // TODO: the "oldchain" and "newchain" is used to determine the subroutine
268 // we're in (by searching for the last JSR) by the InstructionContext
269 // implementation. Therefore, we should not use this chain mechanism
270 // when dealing with exception handlers.
271 // Example: a JSR with an exception handler as its successor does not
272 // mean we're in a subroutine if we go to the exception handler.
273 // We should address this problem later; by now we simply "cut" the chain
274 // by using an empty chain for the exception handlers.
275 // if (v.execute(new Frame(u.getOutFrame(oldchain).getLocals(),
276 // new OperandStack (u.getOutFrame().getStack().maxStack(),
277 // (exc_hds[s].getExceptionType() == null ? Type.THROWABLE : exc_hds[s].getExceptionType())) ), newchain), icv, ev) {
278 // icq.add(v, (ArrayList) newchain.clone());
279 if (v.execute(new Frame(u.getOutFrame(oldchain).getLocals(), new OperandStack(u.getOutFrame(oldchain).getStack().maxStack(),
280 excHd.getExceptionType() == null ? Type.THROWABLE : excHd.getExceptionType())), new ArrayList<>(), icv, ev)) {
281 icq.add(v, new ArrayList<>());
282 }
283 }
284
285 } // while (!icq.isEmpty()) END
286
287 InstructionHandle ih = start.getInstruction();
288 do {
289 if (ih.getInstruction() instanceof ReturnInstruction && !cfg.isDead(ih)) {
290 final InstructionContext ic = cfg.contextOf(ih);
291 // TODO: This is buggy, we check only the top-level return instructions this way.
292 // Maybe some maniac returns from a method when in a subroutine?
293 final Frame f = ic.getOutFrame(new ArrayList<>());
294 final LocalVariables lvs = f.getLocals();
295 for (int i = 0; i < lvs.maxLocals(); i++) {
296 if (lvs.get(i) instanceof UninitializedObjectType) {
297 addMessage("Warning: ReturnInstruction '" + ic + "' may leave method with an uninitialized object in the local variables array '"
298 + lvs + "'.");
299 }
300 }
301 final OperandStack os = f.getStack();
302 for (int i = 0; i < os.size(); i++) {
303 if (os.peek(i) instanceof UninitializedObjectType) {
304 addMessage(
305 "Warning: ReturnInstruction '" + ic + "' may leave method with an uninitialized object on the operand stack '" + os + "'.");
306 }
307 }
308 // see JVM $4.8.2
309 Type returnedType = null;
310 final OperandStack inStack = ic.getInFrame().getStack();
311 if (inStack.size() >= 1) {
312 returnedType = inStack.peek();
313 } else {
314 returnedType = Type.VOID;
315 }
316
317 if (returnedType != null) {
318 if (returnedType instanceof ReferenceType) {
319 try {
320 if (!((ReferenceType) returnedType).isCastableTo(m.getReturnType())) {
321 invalidReturnTypeError(returnedType, m);
322 }
323 } catch (final ClassNotFoundException e) {
324 // Don't know what to do now, so raise RuntimeException
325 throw new IllegalArgumentException(e);
326 }
327 } else if (!returnedType.equals(m.getReturnType().normalizeForStackOrLocal())) {
328 invalidReturnTypeError(returnedType, m);
329 }
330 }
331 }
332 } while ((ih = ih.getNext()) != null);
333
334 }
335
336 /**
337 * Pass 3b implements the data flow analysis as described in the Java Virtual Machine Specification, Second Edition.
338 * Later versions will use LocalVariablesInfo objects to verify if the verifier-inferred types and the class file's
339 * debug information (LocalVariables attributes) match [TODO].
340 *
341 * @see org.apache.bcel.verifier.statics.LocalVariablesInfo
342 * @see org.apache.bcel.verifier.statics.Pass2Verifier#getLocalVariablesInfo(int)
343 */
344 @Override
345 public VerificationResult do_verify() {
346 if (!myOwner.doPass3a(methodNo).equals(VerificationResult.VR_OK)) {
347 return VerificationResult.VR_NOTYET;
348 }
349
350 // Pass 3a ran before, so it's safe to assume the JavaClass object is
351 // in the BCEL repository.
352 final JavaClass jc;
353 try {
354 jc = Repository.lookupClass(myOwner.getClassName());
355 } catch (final ClassNotFoundException e) {
356 // FIXME: maybe not the best way to handle this
357 throw new AssertionViolatedException("Missing class: " + e, e);
358 }
359
360 final ConstantPoolGen constantPoolGen = new ConstantPoolGen(jc.getConstantPool());
361 // Init Visitors
362 final InstConstraintVisitor icv = new InstConstraintVisitor();
363 icv.setConstantPoolGen(constantPoolGen);
364
365 final ExecutionVisitor ev = new ExecutionVisitor();
366 ev.setConstantPoolGen(constantPoolGen);
367
368 final Method[] methods = jc.getMethods(); // Method no "methodNo" exists, we ran Pass3a before on it!
369
370 try {
371
372 final MethodGen mg = new MethodGen(methods[methodNo], myOwner.getClassName(), constantPoolGen);
373
374 icv.setMethodGen(mg);
375
376 ////////////// DFA BEGINS HERE ////////////////
377 if (!(mg.isAbstract() || mg.isNative())) { // IF mg HAS CODE (See pass 2)
378
379 // Reject pathological resource claims before running the data flow analysis: an 'in' and an 'out'
380 // Frame sized max_locals + max_stack is stored for every reachable instruction (and per calling
381 // subroutine), so a small crafted method declaring max_locals = max_stack = 65535 over tens of
382 // thousands of instructions would force tens of gigabytes of allocations before any constraint
383 // could fail. The limit can be changed (or disabled with a value <= 0) via the
384 // MAX_FRAME_SLOTS_PROPERTY system property.
385 final int instructionCount = mg.getInstructionList().getLength();
386 final long frameSlots = ((long) mg.getMaxLocals() + mg.getMaxStack()) * instructionCount;
387 if (MAX_FRAME_SLOTS > 0 && frameSlots > MAX_FRAME_SLOTS) {
388 throw new StructuralCodeConstraintException("Data flow analysis of this method would require more than " + MAX_FRAME_SLOTS
389 + " frame slots: max_locals '" + mg.getMaxLocals() + "' plus max_stack '" + mg.getMaxStack() + "' over '" + instructionCount
390 + "' instructions. Adjust the '" + MAX_FRAME_SLOTS_PROPERTY + "' system property to change this limit.");
391 }
392
393 final ControlFlowGraph cfg = new ControlFlowGraph(mg);
394
395 // Build the initial frame situation for this method.
396 final Frame f = new Frame(mg.getMaxLocals(), mg.getMaxStack());
397 if (!mg.isStatic()) {
398 if (mg.getName().equals(Const.CONSTRUCTOR_NAME)) {
399 Frame.setThis(new UninitializedObjectType(ObjectType.getInstance(jc.getClassName())));
400 f.getLocals().set(0, Frame.getThis());
401 } else {
402 Frame.setThis(null);
403 f.getLocals().set(0, ObjectType.getInstance(jc.getClassName()));
404 }
405 }
406 final Type[] argtypes = mg.getArgumentTypes();
407 int twoslotoffset = 0;
408 for (int j = 0; j < argtypes.length; j++) {
409 if (argtypes[j] == Type.SHORT || argtypes[j] == Type.BYTE || argtypes[j] == Type.CHAR || argtypes[j] == Type.BOOLEAN) {
410 argtypes[j] = Type.INT;
411 }
412 f.getLocals().set(twoslotoffset + j + (mg.isStatic() ? 0 : 1), argtypes[j]);
413 if (argtypes[j].getSize() == 2) {
414 twoslotoffset++;
415 f.getLocals().set(twoslotoffset + j + (mg.isStatic() ? 0 : 1), Type.UNKNOWN);
416 }
417 }
418 circulationPump(mg, cfg, cfg.contextOf(mg.getInstructionList().getStart()), f, icv, ev);
419 }
420 } catch (final VerifierConstraintViolatedException ce) {
421 ce.extendMessage("Constraint violated in method '" + methods[methodNo] + "':\n", "");
422 return new VerificationResult(VerificationResult.VERIFIED_REJECTED, ce.getMessage());
423 } catch (final RuntimeException re) {
424 // These are internal errors
425
426 final StringWriter sw = new StringWriter();
427 final PrintWriter pw = new PrintWriter(sw);
428 re.printStackTrace(pw);
429
430 throw new AssertionViolatedException("Some RuntimeException occurred while verify()ing class '" + jc.getClassName() + "', method '"
431 + methods[methodNo] + "'. Original RuntimeException's stack trace:\n---\n" + sw + "---\n", re);
432 }
433 return VerificationResult.VR_OK;
434 }
435
436 /**
437 * Returns the method number as supplied when instantiating.
438 *
439 * @return The method number.
440 */
441 public int getMethodNo() {
442 return methodNo;
443 }
444
445 /**
446 * Throws an exception indicating the returned type is not compatible with the return type of the given method.
447 *
448 * @param returnedType The type of the returned expression.
449 * @param m The method we are processing.
450 * @throws StructuralCodeConstraintException Always thrown.
451 * @since 6.0
452 */
453 public void invalidReturnTypeError(final Type returnedType, final MethodGen m) {
454 throw new StructuralCodeConstraintException("Returned type " + returnedType + " does not match Method's return type " + m.getReturnType());
455 }
456 }