001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.bcel.verifier.structurals; 020 021import java.util.ArrayList; 022import java.util.BitSet; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028 029import org.apache.bcel.generic.ASTORE; 030import org.apache.bcel.generic.ATHROW; 031import org.apache.bcel.generic.BranchInstruction; 032import org.apache.bcel.generic.CodeExceptionGen; 033import org.apache.bcel.generic.GotoInstruction; 034import org.apache.bcel.generic.IndexedInstruction; 035import org.apache.bcel.generic.Instruction; 036import org.apache.bcel.generic.InstructionHandle; 037import org.apache.bcel.generic.JsrInstruction; 038import org.apache.bcel.generic.LocalVariableInstruction; 039import org.apache.bcel.generic.MethodGen; 040import org.apache.bcel.generic.RET; 041import org.apache.bcel.generic.ReturnInstruction; 042import org.apache.bcel.generic.Select; 043import org.apache.bcel.verifier.exc.AssertionViolatedException; 044import org.apache.bcel.verifier.exc.StructuralCodeConstraintException; 045 046/** 047 * Instances of this class contain information about the subroutines found in a code array of a method. This 048 * implementation considers the top-level (the instructions reachable without a JSR or JSR_W starting off from the first 049 * instruction in a code array of a method) being a special subroutine; see getTopLevel() for that. Please note that the 050 * definition of subroutines in the Java Virtual Machine Specification, Second Edition is somewhat incomplete. 051 * Therefore, JustIce uses an own, more rigid notion. Basically, a subroutine is a piece of code that starts at the 052 * target of a JSR of JSR_W instruction and ends at a corresponding RET instruction. Note also that the control flow of 053 * a subroutine may be complex and non-linear; and that subroutines may be nested. JustIce also mandates subroutines not 054 * to be protected by exception handling code (for the sake of control flow predictability). To understand JustIce's 055 * notion of subroutines, please read 056 * 057 * TODO: refer to the paper. 058 * 059 * @see #getTopLevel() 060 */ 061public class Subroutines { 062 // Node coloring constants 063 private enum ColourConstants { 064 WHITE, GRAY, BLACK 065 } 066 067 /** 068 * This inner class implements the Subroutine interface. 069 */ 070 private final class SubroutineImpl implements Subroutine { 071 072 /** 073 * UNSET, a symbol for an uninitialized localVariable field. This is used for the "top-level" Subroutine; for example no 074 * subroutine. 075 */ 076 private static final int UNSET = -1; 077 078 private final SubroutineImpl[] EMPTY_ARRAY = {}; 079 080 /** 081 * The Local Variable slot where the first instruction of this subroutine (an ASTORE) stores the JsrInstruction's 082 * ReturnAddress in and the RET of this subroutine operates on. 083 */ 084 private int localVariable = UNSET; 085 086 /** The instructions that belong to this subroutine. */ 087 private final Set<InstructionHandle> instructions = new HashSet<>(); // Elements: InstructionHandle 088 089 /** 090 * The JSR or JSR_W instructions that define this subroutine by targeting it. 091 */ 092 private final Set<InstructionHandle> theJSRs = new HashSet<>(); 093 094 /** 095 * The RET instruction that leaves this subroutine. 096 */ 097 private InstructionHandle theRET; 098 099 /** 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}