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.util.ArrayList;
22 import java.util.BitSet;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28
29 import org.apache.bcel.generic.ASTORE;
30 import org.apache.bcel.generic.ATHROW;
31 import org.apache.bcel.generic.BranchInstruction;
32 import org.apache.bcel.generic.CodeExceptionGen;
33 import org.apache.bcel.generic.GotoInstruction;
34 import org.apache.bcel.generic.IndexedInstruction;
35 import org.apache.bcel.generic.Instruction;
36 import org.apache.bcel.generic.InstructionHandle;
37 import org.apache.bcel.generic.JsrInstruction;
38 import org.apache.bcel.generic.LocalVariableInstruction;
39 import org.apache.bcel.generic.MethodGen;
40 import org.apache.bcel.generic.RET;
41 import org.apache.bcel.generic.ReturnInstruction;
42 import org.apache.bcel.generic.Select;
43 import org.apache.bcel.verifier.exc.AssertionViolatedException;
44 import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
45
46 /**
47 * Instances of this class contain information about the subroutines found in a code array of a method. This
48 * implementation considers the top-level (the instructions reachable without a JSR or JSR_W starting off from the first
49 * instruction in a code array of a method) being a special subroutine; see getTopLevel() for that. Please note that the
50 * definition of subroutines in the Java Virtual Machine Specification, Second Edition is somewhat incomplete.
51 * Therefore, JustIce uses an own, more rigid notion. Basically, a subroutine is a piece of code that starts at the
52 * target of a JSR of JSR_W instruction and ends at a corresponding RET instruction. Note also that the control flow of
53 * a subroutine may be complex and non-linear; and that subroutines may be nested. JustIce also mandates subroutines not
54 * to be protected by exception handling code (for the sake of control flow predictability). To understand JustIce's
55 * notion of subroutines, please read
56 *
57 * TODO: refer to the paper.
58 *
59 * @see #getTopLevel()
60 */
61 public class Subroutines {
62 // Node coloring constants
63 private enum ColourConstants {
64 WHITE, GRAY, BLACK
65 }
66
67 /**
68 * This inner class implements the Subroutine interface.
69 */
70 private final class SubroutineImpl implements Subroutine {
71
72 /**
73 * UNSET, a symbol for an uninitialized localVariable field. This is used for the "top-level" Subroutine; for example no
74 * subroutine.
75 */
76 private static final int UNSET = -1;
77
78 private final SubroutineImpl[] EMPTY_ARRAY = {};
79
80 /**
81 * The Local Variable slot where the first instruction of this subroutine (an ASTORE) stores the JsrInstruction's
82 * ReturnAddress in and the RET of this subroutine operates on.
83 */
84 private int localVariable = UNSET;
85
86 /** The instructions that belong to this subroutine. */
87 private final Set<InstructionHandle> instructions = new HashSet<>(); // Elements: InstructionHandle
88
89 /**
90 * The JSR or JSR_W instructions that define this subroutine by targeting it.
91 */
92 private final Set<InstructionHandle> theJSRs = new HashSet<>();
93
94 /**
95 * The RET instruction that leaves this subroutine.
96 */
97 private InstructionHandle theRET;
98
99 /**
100 * Constructs a new instance.
101 */
102 SubroutineImpl() {
103 // empty
104 }
105
106 /**
107 * Adds a new JSR or JSR_W that has this subroutine as its target.
108 */
109 public void addEnteringJsrInstruction(final InstructionHandle jsrInst) {
110 if (jsrInst == null || !(jsrInst.getInstruction() instanceof JsrInstruction)) {
111 throw new AssertionViolatedException("Expecting JsrInstruction InstructionHandle.");
112 }
113 if (localVariable == UNSET) {
114 throw new AssertionViolatedException("Set the localVariable first.");
115 }
116 // Something is wrong when an ASTORE is targeted that does not operate on the same local variable than the rest of the
117 // JsrInstruction-targets and the RET.
118 // (We don't know out leader here so we cannot check if we're really targeted!)
119 if (localVariable != ((ASTORE) ((JsrInstruction) jsrInst.getInstruction()).getTarget().getInstruction()).getIndex()) {
120 throw new AssertionViolatedException("Setting a wrong JsrInstruction.");
121 }
122 theJSRs.add(jsrInst);
123 }
124
125 /*
126 * Adds an instruction to this subroutine. All instructions must have been added before invoking setLeavingRET().
127 *
128 * @see #setLeavingRET
129 */
130 void addInstruction(final InstructionHandle ih) {
131 if (theRET != null) {
132 throw new AssertionViolatedException("All instructions must have been added before invoking setLeavingRET().");
133 }
134 instructions.add(ih);
135 }
136
137 /*
138 * Refer to the Subroutine interface for documentation.
139 */
140 @Override
141 public boolean contains(final InstructionHandle inst) {
142 return instructions.contains(inst);
143 }
144
145 /*
146 * Satisfies Subroutine.getAccessedLocalIndices().
147 */
148 @Override
149 public int[] getAccessedLocalsIndices() {
150 // TODO: Implement caching.
151 final Set<Integer> acc = new HashSet<>();
152 if (theRET == null && this != getTopLevel()) {
153 throw new AssertionViolatedException("This subroutine object must be built up completely before calculating accessed locals.");
154 }
155 {
156 for (final InstructionHandle ih : instructions) {
157 // RET is not a LocalVariableInstruction in the current version of BCEL.
158 if (ih.getInstruction() instanceof LocalVariableInstruction || ih.getInstruction() instanceof RET) {
159 final int idx = ((IndexedInstruction) ih.getInstruction()).getIndex();
160 acc.add(Integer.valueOf(idx));
161 // LONG? DOUBLE?.
162 try {
163 // LocalVariableInstruction instances are typed without the need to look into
164 // the constant pool.
165 if (ih.getInstruction() instanceof LocalVariableInstruction) {
166 final int s = ((LocalVariableInstruction) ih.getInstruction()).getType(null).getSize();
167 if (s == 2) {
168 acc.add(Integer.valueOf(idx + 1));
169 }
170 }
171 } catch (final RuntimeException re) {
172 throw new AssertionViolatedException("BCEL did not like NULL as a ConstantPoolGen object.", re);
173 }
174 }
175 }
176 }
177
178 {
179 final int[] ret = new int[acc.size()];
180 int j = -1;
181 for (final Integer accessedLocal : acc) {
182 j++;
183 ret[j] = accessedLocal.intValue();
184 }
185 return ret;
186 }
187 }
188
189 /*
190 * Refer to the Subroutine interface for documentation.
191 */
192 @Override
193 public InstructionHandle[] getEnteringJsrInstructions() {
194 if (this == getTopLevel()) {
195 throw new AssertionViolatedException("getLeavingRET() called on top level pseudo-subroutine.");
196 }
197 return theJSRs.toArray(InstructionHandle.EMPTY_ARRAY);
198 }
199
200 /*
201 * Refer to the Subroutine interface for documentation.
202 */
203 @Override
204 public InstructionHandle[] getInstructions() {
205 return instructions.toArray(InstructionHandle.EMPTY_ARRAY);
206 }
207
208 /*
209 * Refer to the Subroutine interface for documentation.
210 */
211 @Override
212 public InstructionHandle getLeavingRET() {
213 if (this == getTopLevel()) {
214 throw new AssertionViolatedException("getLeavingRET() called on top level pseudo-subroutine.");
215 }
216 return theRET;
217 }
218
219 /* Satisfies Subroutine.getRecursivelyAccessedLocalsIndices(). */
220 @Override
221 public int[] getRecursivelyAccessedLocalsIndices() {
222 final Set<Integer> s = new HashSet<>();
223 final int[] lvs = getAccessedLocalsIndices();
224 for (final int lv : lvs) {
225 s.add(Integer.valueOf(lv));
226 }
227 getRecursivelyAccessedLocalsIndicesHelper(s, subSubs(), new HashSet<>());
228 final int[] ret = new int[s.size()];
229 int j = -1;
230 for (final Integer index : s) {
231 j++;
232 ret[j] = index.intValue();
233 }
234 return ret;
235 }
236
237 /**
238 * A recursive helper method for getRecursivelyAccessedLocalsIndices(). Every subroutine is visited at most
239 * once: since the computed set is a plain union, re-exploring an already visited subroutine cannot add
240 * anything, but doing so once per call path made this helper exponential in the depth of the JSR call graph
241 * (and made it recurse forever on a cyclic one).
242 *
243 * @see #getRecursivelyAccessedLocalsIndices()
244 */
245 private void getRecursivelyAccessedLocalsIndicesHelper(final Set<Integer> set, final Subroutine[] subs, final Set<Subroutine> visited) {
246 for (final Subroutine sub : subs) {
247 if (!visited.add(sub)) {
248 continue;
249 }
250 final int[] lvs = sub.getAccessedLocalsIndices();
251 for (final int lv : lvs) {
252 set.add(Integer.valueOf(lv));
253 }
254 if (sub.subSubs().length != 0) {
255 getRecursivelyAccessedLocalsIndicesHelper(set, sub.subSubs(), visited);
256 }
257 }
258 }
259
260 /**
261 * Sets the leaving RET instruction. Must be invoked after all instructions are added. Must not be invoked for top-level
262 * 'subroutine'.
263 */
264 void setLeavingRET() {
265 if (localVariable == UNSET) {
266 throw new AssertionViolatedException("setLeavingRET() called for top-level 'subroutine' or forgot to set local variable first.");
267 }
268 InstructionHandle ret = null;
269 for (final InstructionHandle actual : instructions) {
270 if (actual.getInstruction() instanceof RET) {
271 if (ret != null) {
272 throw new StructuralCodeConstraintException("Subroutine with more then one RET detected: '" + ret + "' and '" + actual + "'.");
273 }
274 ret = actual;
275 }
276 }
277 if (ret == null) {
278 throw new StructuralCodeConstraintException("Subroutine without a RET detected.");
279 }
280 if (((RET) ret.getInstruction()).getIndex() != localVariable) {
281 throw new StructuralCodeConstraintException(
282 "Subroutine uses '" + ret + "' which does not match the correct local variable '" + localVariable + "'.");
283 }
284 theRET = ret;
285 }
286
287 /*
288 * Sets the local variable slot the ASTORE that is targeted by the JsrInstructions of this subroutine operates on. This
289 * subroutine's RET operates on that same local variable slot, of course.
290 */
291 void setLocalVariable(final int i) {
292 if (localVariable != UNSET) {
293 throw new AssertionViolatedException("localVariable set twice.");
294 }
295 localVariable = i;
296 }
297
298 /*
299 * Satisfies Subroutine.subSubs().
300 */
301 @Override
302 public Subroutine[] subSubs() {
303 final Set<Subroutine> h = new HashSet<>();
304
305 for (final InstructionHandle ih : instructions) {
306 final Instruction inst = ih.getInstruction();
307 if (inst instanceof JsrInstruction) {
308 final InstructionHandle targ = ((JsrInstruction) inst).getTarget();
309 h.add(getSubroutine(targ));
310 }
311 }
312 return h.toArray(EMPTY_ARRAY);
313 }
314
315 /**
316 * Returns a String representation of this object, merely for debugging purposes. (Internal) Warning: Verbosity on a
317 * problematic subroutine may cause stack overflow errors due to recursive subSubs() calls. Don't use this, then.
318 */
319 @Override
320 public String toString() {
321 final StringBuilder ret = new StringBuilder();
322 ret.append("Subroutine: Local variable is '").append(localVariable);
323 ret.append("', JSRs are '").append(theJSRs);
324 ret.append("', RET is '").append(theRET);
325 ret.append("', Instructions: '").append(instructions).append("'.");
326
327 ret.append(" Accessed local variable slots: '");
328 int[] alv = getAccessedLocalsIndices();
329 for (final int element : alv) {
330 ret.append(element);
331 ret.append(" ");
332 }
333 ret.append("'.");
334
335 ret.append(" Recursively (via subsub...routines) accessed local variable slots: '");
336 alv = getRecursivelyAccessedLocalsIndices();
337 for (final int element : alv) {
338 ret.append(element);
339 ret.append(" ");
340 }
341 ret.append("'.");
342
343 return ret.toString();
344 }
345
346 } // end Inner Class SubrouteImpl
347
348 /**
349 * A utility method that calculates the successors of a given InstructionHandle <strong>in the same subroutine</strong>. That
350 * means, a RET does not have any successors as defined here. A JsrInstruction has its physical successor as its
351 * successor (opposed to its target) as defined here.
352 */
353 private static InstructionHandle[] getSuccessors(final InstructionHandle instruction) {
354 final InstructionHandle[] single = new InstructionHandle[1];
355
356 final Instruction inst = instruction.getInstruction();
357
358 // Terminates method normally.
359 // Terminates method abnormally, because JustIce mandates
360 // subroutines not to be protected by exception handlers.
361 if (inst instanceof RET || inst instanceof ReturnInstruction || inst instanceof ATHROW) {
362 return InstructionHandle.EMPTY_ARRAY;
363 }
364
365 // See method comment.
366 if (inst instanceof JsrInstruction) {
367 single[0] = instruction.getNext();
368 return single;
369 }
370
371 if (inst instanceof GotoInstruction) {
372 single[0] = ((GotoInstruction) inst).getTarget();
373 return single;
374 }
375
376 if (inst instanceof BranchInstruction) {
377 if (inst instanceof Select) {
378 // BCEL's getTargets() returns only the non-default targets,
379 // thanks to Eli Tilevich for reporting.
380 final InstructionHandle[] matchTargets = ((Select) inst).getTargets();
381 final InstructionHandle[] ret = new InstructionHandle[matchTargets.length + 1];
382 ret[0] = ((Select) inst).getTarget();
383 System.arraycopy(matchTargets, 0, ret, 1, matchTargets.length);
384 return ret;
385 }
386 final InstructionHandle[] pair = new InstructionHandle[2];
387 pair[0] = instruction.getNext();
388 pair[1] = ((BranchInstruction) inst).getTarget();
389 return pair;
390 }
391
392 // default case: Fall through.
393 single[0] = instruction.getNext();
394 return single;
395 }
396
397 private static StructuralCodeConstraintException recursiveCallException(final Subroutine sub2) {
398 // Don't use toString() here because of possibly infinite recursive subSubs() calls then.
399 final SubroutineImpl si = (SubroutineImpl) sub2;
400 return new StructuralCodeConstraintException("Subroutine with local variable '" + si.localVariable + "', JSRs '" + si.theJSRs + "', RET '"
401 + si.theRET + "' is called by a subroutine which uses the same local variable index as itself; maybe even a recursive call?"
402 + " JustIce's clean definition of a subroutine forbids both.");
403 }
404
405 /**
406 * The map containing the subroutines found. Key: InstructionHandle of the leader of the subroutine. Elements:
407 * SubroutineImpl objects.
408 */
409 private final Map<InstructionHandle, Subroutine> subroutines = new HashMap<>();
410
411 /**
412 * This is referring to a special subroutine, namely the top level. This is not really a subroutine but we use it to
413 * distinguish between top level instructions and unreachable instructions.
414 */
415 // CHECKSTYLE:OFF
416 public final Subroutine TOPLEVEL; // TODO can this be made private?
417 // CHECKSTYLE:ON
418
419 /**
420 * Constructs a new instance.
421 *
422 * @param mg A MethodGen object representing method to create the Subroutine objects of. Assumes that JustIce strict
423 * checks are needed.
424 */
425 public Subroutines(final MethodGen mg) {
426 this(mg, true);
427 }
428
429 /**
430 * Constructs a new instance.
431 *
432 * @param mg A MethodGen object representing method to create the Subroutine objects of.
433 * @param enableJustIceCheck whether to enable additional JustIce checks.
434 * @since 6.0
435 */
436 public Subroutines(final MethodGen mg, final boolean enableJustIceCheck) {
437 final InstructionHandle[] all = mg.getInstructionList().getInstructionHandles();
438 final CodeExceptionGen[] handlers = mg.getExceptionHandlers();
439
440 // Define our "Toplevel" fake subroutine.
441 TOPLEVEL = new SubroutineImpl();
442
443 // Calculate "real" subroutines.
444 final Set<InstructionHandle> subLeaders = new HashSet<>(); // Elements: InstructionHandle
445 for (final InstructionHandle element : all) {
446 final Instruction inst = element.getInstruction();
447 if (inst instanceof JsrInstruction) {
448 subLeaders.add(((JsrInstruction) inst).getTarget());
449 }
450 }
451
452 // Build up the database.
453 for (final InstructionHandle astore : subLeaders) {
454 final SubroutineImpl sr = new SubroutineImpl();
455 sr.setLocalVariable(((ASTORE) astore.getInstruction()).getIndex());
456 subroutines.put(astore, sr);
457 }
458
459 // Fake it a bit. We want a virtual "TopLevel" subroutine.
460 subroutines.put(all[0], TOPLEVEL);
461 subLeaders.add(all[0]);
462
463 // Tell the subroutines about their JsrInstructions.
464 // Note that there cannot be a JSR targeting the top-level
465 // since "Jsr 0" is disallowed in Pass 3a.
466 // Instructions shared by a subroutine and the toplevel are
467 // disallowed and checked below, after the BFS.
468 for (final InstructionHandle element : all) {
469 final Instruction inst = element.getInstruction();
470 if (inst instanceof JsrInstruction) {
471 final InstructionHandle leader = ((JsrInstruction) inst).getTarget();
472 ((SubroutineImpl) getSubroutine(leader)).addEnteringJsrInstruction(element);
473 }
474 }
475
476 // Now do a BFS from every subroutine leader to find all the
477 // instructions that belong to a subroutine.
478 // we don't want to assign an instruction to two or more Subroutine objects.
479 final Set<InstructionHandle> instructionsAssigned = new HashSet<>();
480
481 // Graph coloring. Key: InstructionHandle, Value: ColourConstants enum.
482 final Map<InstructionHandle, ColourConstants> colors = new HashMap<>();
483
484 final List<InstructionHandle> qList = new ArrayList<>();
485 for (final InstructionHandle actual : subLeaders) {
486 // Do some BFS with "actual" as the root of the graph.
487 // Init colors: an instruction absent from the map is WHITE. Explicitly coloring every
488 // instruction WHITE on every round would make this initialization quadratic in the
489 // method size.
490 colors.clear();
491 colors.put(actual, ColourConstants.GRAY);
492 // Init Queue
493
494 qList.clear();
495 qList.add(actual); // add(Obj) adds to the end, remove(0) removes from the start.
496
497 /*
498 * BFS ALGORITHM MODIFICATION: Start out with multiple "root" nodes, as exception handlers are starting points of
499 * top-level code, too. [why top-level? TODO: Refer to the special JustIce notion of subroutines.]
500 */
501 if (actual == all[0]) {
502 for (final CodeExceptionGen handler : handlers) {
503 colors.put(handler.getHandlerPC(), ColourConstants.GRAY);
504 qList.add(handler.getHandlerPC());
505 }
506 }
507 /* CONTINUE NORMAL BFS ALGORITHM */
508
509 // Loop until Queue is empty
510 while (!qList.isEmpty()) {
511 final InstructionHandle u = qList.remove(0);
512 final InstructionHandle[] successors = getSuccessors(u);
513 for (final InstructionHandle successor : successors) {
514 if (successor != null && colors.get(successor) == null) { // absent from the map means WHITE
515 colors.put(successor, ColourConstants.GRAY);
516 qList.add(successor);
517 }
518 }
519 colors.put(u, ColourConstants.BLACK);
520 }
521 // BFS ended above. Only instructions visited by this BFS round are in the color map,
522 // so this scan is proportional to the round, not to the whole method.
523 for (final Map.Entry<InstructionHandle, ColourConstants> entry : colors.entrySet()) {
524 if (entry.getValue() == ColourConstants.BLACK) {
525 final InstructionHandle element = entry.getKey();
526 ((SubroutineImpl) (actual == all[0] ? getTopLevel() : getSubroutine(actual))).addInstruction(element);
527 if (instructionsAssigned.contains(element)) {
528 throw new StructuralCodeConstraintException(
529 "Instruction '" + element + "' is part of more than one subroutine (or of the top level and a subroutine).");
530 }
531 instructionsAssigned.add(element);
532 }
533 }
534 if (actual != all[0]) { // If we don't deal with the top-level 'subroutine'
535 ((SubroutineImpl) getSubroutine(actual)).setLeavingRET();
536 }
537 }
538
539 if (enableJustIceCheck && handlers.length > 0) {
540 // Now make sure no instruction of a Subroutine is protected by exception handling code
541 // as is mandated by JustIces notion of subroutines.
542 // The handler coverage of every instruction is computed once, with a difference array
543 // over instruction list indices. Walking every handler's protected range and, per
544 // protected instruction, every subroutine would let a crafted method (thousands of
545 // handlers over large ranges) keep this constructor busy nearly forever.
546 final Map<InstructionHandle, Integer> instructionIndexes = new HashMap<>();
547 for (int i = 0; i < all.length; i++) {
548 instructionIndexes.put(all[i], Integer.valueOf(i));
549 }
550 final int[] coverageDelta = new int[all.length + 1];
551 for (final CodeExceptionGen handler : handlers) {
552 // Note the inclusive/inclusive notation of "generic API" exception handlers!
553 final Integer startIndex = instructionIndexes.get(handler.getStartPC());
554 final Integer endIndex = instructionIndexes.get(handler.getEndPC());
555 if (startIndex == null || endIndex == null || startIndex.intValue() > endIndex.intValue()) {
556 throw new StructuralCodeConstraintException("Exception handler '" + handler + "' does not protect a valid instruction range.");
557 }
558 coverageDelta[startIndex.intValue()]++;
559 coverageDelta[endIndex.intValue() + 1]--;
560 }
561 final boolean[] isProtected = new boolean[all.length];
562 int covered = 0;
563 for (int i = 0; i < all.length; i++) {
564 covered += coverageDelta[i];
565 isProtected[i] = covered > 0;
566 }
567 for (final Subroutine sub : subroutines.values()) {
568 if (sub == subroutines.get(all[0])) {
569 continue;
570 }
571 for (final InstructionHandle protectedIh : sub.getInstructions()) {
572 final Integer index = instructionIndexes.get(protectedIh);
573 if (index != null && isProtected[index.intValue()]) {
574 // Only the error message needs the offending handler; this scan runs at most once.
575 for (final CodeExceptionGen handler : handlers) {
576 final int startIndex = instructionIndexes.get(handler.getStartPC()).intValue();
577 final int endIndex = instructionIndexes.get(handler.getEndPC()).intValue();
578 if (startIndex <= index.intValue() && index.intValue() <= endIndex) {
579 throw new StructuralCodeConstraintException("Subroutine instruction '" + protectedIh
580 + "' is protected by an exception handler, '" + handler
581 + "'. This is forbidden by the JustIce verifier due to its clear definition of subroutines.");
582 }
583 }
584 }
585 }
586 }
587 }
588
589 // Now make sure no subroutine is calling a subroutine
590 // that uses the same local variable for the RET as themselves
591 // (recursively).
592 // This includes that subroutines may not call themselves
593 // recursively, even not through intermediate calls to other
594 // subroutines.
595 noRecursiveCalls(getTopLevel());
596
597 }
598
599 /**
600 * Returns the Subroutine object associated with the given leader (that is, the first instruction of the subroutine).
601 * You must not use this to get the top-level instructions modeled as a Subroutine object.
602 *
603 * @param leader The leader instruction handle.
604 * @return The Subroutine object.
605 * @see #getTopLevel()
606 */
607 public Subroutine getSubroutine(final InstructionHandle leader) {
608 final Subroutine ret = subroutines.get(leader);
609
610 if (ret == null) {
611 throw new AssertionViolatedException("Subroutine requested for an InstructionHandle that is not a leader of a subroutine.");
612 }
613
614 if (ret == TOPLEVEL) {
615 throw new AssertionViolatedException("TOPLEVEL special subroutine requested; use getTopLevel().");
616 }
617
618 return ret;
619 }
620
621 /**
622 * For easy handling, the piece of code that is <strong>not</strong> a subroutine, the top-level, is also modeled as a Subroutine
623 * object. It is a special Subroutine object where <B>you must not invoke getEnteringJsrInstructions() or
624 * getLeavingRET()</B>.
625 *
626 * @return The top-level Subroutine.
627 * @see Subroutine#getEnteringJsrInstructions()
628 * @see Subroutine#getLeavingRET()
629 */
630 public Subroutine getTopLevel() {
631 return TOPLEVEL;
632 }
633
634 /**
635 * This utility method makes sure that no subroutine is calling a subroutine that uses the same local
636 * variable for the RET as themselves (recursively). This includes that subroutines may not call themselves recursively,
637 * even not through intermediate calls to other subroutines.
638 *
639 * Every subroutine is fully validated exactly once, memoizing the RET local variable indices used anywhere in its
640 * call subtree. The former implementation re-explored a subroutine once per call path, which is exponential in the
641 * number of subroutines for a layered JSR call graph.
642 *
643 * @throws StructuralCodeConstraintException Thrown if the above constraint is not satisfied.
644 */
645 private void noRecursiveCalls(final Subroutine sub) {
646 noRecursiveCalls(sub, new BitSet(), new HashMap<>(), new HashMap<>());
647 }
648
649 /**
650 * The recursive helper for {@link #noRecursiveCalls(Subroutine)}.
651 *
652 * @param sub the subroutine whose callees are validated.
653 * @param pathLocals compact ids (see {@code localIds}) of the RET local variables used by the subroutines on the current call path.
654 * @param validated maps every fully validated subroutine to the compact ids of the RET local variables used by it and its entire call subtree.
655 * @param localIds maps a RET local variable index to a compact id so the bit sets stay small.
656 * @return the compact ids of the RET local variables used by {@code sub}'s callees and their call subtrees.
657 * @throws StructuralCodeConstraintException Thrown if a subroutine calls a subroutine using the same RET local variable.
658 */
659 private BitSet noRecursiveCalls(final Subroutine sub, final BitSet pathLocals, final Map<Subroutine, BitSet> validated,
660 final Map<Integer, Integer> localIds) {
661 final BitSet subtreeLocals = new BitSet();
662
663 for (final Subroutine sub2 : sub.subSubs()) {
664 final Integer index = Integer.valueOf(((RET) sub2.getLeavingRET().getInstruction()).getIndex());
665 final int localId = localIds.computeIfAbsent(index, k -> Integer.valueOf(localIds.size())).intValue();
666
667 BitSet childLocals = validated.get(sub2);
668 if (childLocals == null) {
669 if (pathLocals.get(localId)) {
670 // sub2 uses a RET local variable also used by a subroutine on the current call path;
671 // this also covers (possibly indirect) recursive calls.
672 throw recursiveCallException(sub2);
673 }
674 pathLocals.set(localId);
675 childLocals = noRecursiveCalls(sub2, pathLocals, validated, localIds);
676 pathLocals.clear(localId);
677 childLocals.set(localId);
678 validated.put(sub2, childLocals);
679 } else if (childLocals.intersects(pathLocals)) {
680 // A subroutine in sub2's (already validated) call subtree uses a RET local variable also
681 // used by a subroutine on the current call path.
682 throw recursiveCallException(sub2);
683 }
684 subtreeLocals.or(childLocals);
685 }
686 return subtreeLocals;
687 }
688
689 /**
690 * Returns the subroutine object associated with the given instruction. This is a costly operation, you should consider
691 * using getSubroutine(InstructionHandle). Returns 'null' if the given InstructionHandle lies in so-called 'dead code',
692 * for example code that can never be executed.
693 *
694 * @param any The instruction handle.
695 * @return The Subroutine object or null.
696 * @see #getSubroutine(InstructionHandle)
697 * @see #getTopLevel()
698 */
699 public Subroutine subroutineOf(final InstructionHandle any) {
700 for (final Subroutine s : subroutines.values()) {
701 if (s.contains(any)) {
702 return s;
703 }
704 }
705 System.err.println("DEBUG: Please verify '" + any.toString(true) + "' lies in dead code.");
706 return null;
707 // throw new AssertionViolatedException("No subroutine for InstructionHandle found (DEAD CODE?).");
708 }
709
710 /**
711 * Returns a String representation of this object; merely for debugging puposes.
712 */
713 @Override
714 public String toString() {
715 return "---\n" + subroutines + "\n---\n";
716 }
717 }