View Javadoc
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 }