1 /******************************************************************************* 2 * Copyright (c) 2000, 2004 IBM Corporation and others. 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Common Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/cpl-v10.html 7 * 8 * Contributors: 9 * IBM Corporation - initial API and implementation 10 *******************************************************************************/ 11 package org.eclipse.jdt.internal.compiler.ast; 12 13 import org.eclipse.jdt.internal.compiler.ASTVisitor; 14 import org.eclipse.jdt.internal.compiler.codegen.*; 15 import org.eclipse.jdt.internal.compiler.flow.*; 16 import org.eclipse.jdt.internal.compiler.lookup.*; 17 18 public class TryStatement extends SubRoutineStatement { 19 20 public Block tryBlock; 21 public Block[] catchBlocks; 22 public Argument[] catchArguments; 23 public Block finallyBlock; 24 BlockScope scope; 25 26 private boolean isSubRoutineEscaping = false; 27 public UnconditionalFlowInfo subRoutineInits; 28 29 // should rename into subRoutineComplete to be set to false by default 30 31 ReferenceBinding[] caughtExceptionTypes; 32 boolean tryBlockExit; 33 boolean[] catchExits; 34 public int[] preserveExceptionHandler; 35 36 Label subRoutineStartLabel; 37 public LocalVariableBinding anyExceptionVariable, 38 returnAddressVariable, 39 secretReturnValue; 40 41 public final static char[] SecretReturnName = " returnAddress".toCharArray(); //$NON-NLS-1$ 42 public final static char[] SecretAnyHandlerName = " anyExceptionHandler".toCharArray(); //$NON-NLS-1$ 43 public static final char[] SecretLocalDeclarationName = " returnValue".toCharArray(); //$NON-NLS-1$ 44 45 // for local variables table attributes 46 int preTryInitStateIndex = -1; 47 int mergedInitStateIndex = -1; 48 analyseCode( BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo)49 public FlowInfo analyseCode( 50 BlockScope currentScope, 51 FlowContext flowContext, 52 FlowInfo flowInfo) { 53 54 // Consider the try block and catch block so as to compute the intersection of initializations and 55 // the minimum exit relative depth amongst all of them. Then consider the subroutine, and append its 56 // initialization to the try/catch ones, if the subroutine completes normally. If the subroutine does not 57 // complete, then only keep this result for the rest of the analysis 58 59 // process the finally block (subroutine) - create a context for the subroutine 60 61 preTryInitStateIndex = 62 currentScope.methodScope().recordInitializationStates(flowInfo); 63 64 if (anyExceptionVariable != null) { 65 anyExceptionVariable.useFlag = LocalVariableBinding.USED; 66 } 67 if (returnAddressVariable != null) { // TODO (philippe) if subroutine is escaping, unused 68 returnAddressVariable.useFlag = LocalVariableBinding.USED; 69 } 70 InsideSubRoutineFlowContext insideSubContext; 71 FinallyFlowContext finallyContext; 72 UnconditionalFlowInfo subInfo; 73 if (subRoutineStartLabel == null) { 74 // no finally block 75 insideSubContext = null; 76 finallyContext = null; 77 subInfo = null; 78 } else { 79 // analyse finally block first 80 insideSubContext = new InsideSubRoutineFlowContext(flowContext, this); 81 subInfo = 82 finallyBlock 83 .analyseCode( 84 currentScope, 85 finallyContext = new FinallyFlowContext(flowContext, finallyBlock), 86 flowInfo.copy()) 87 .unconditionalInits(); 88 if (subInfo == FlowInfo.DEAD_END) { 89 isSubRoutineEscaping = true; 90 scope.problemReporter().finallyMustCompleteNormally(finallyBlock); 91 } 92 this.subRoutineInits = subInfo; 93 } 94 // process the try block in a context handling the local exceptions. 95 ExceptionHandlingFlowContext handlingContext = 96 new ExceptionHandlingFlowContext( 97 insideSubContext == null ? flowContext : insideSubContext, 98 tryBlock, 99 caughtExceptionTypes, 100 scope, 101 flowInfo.unconditionalInits()); 102 103 FlowInfo tryInfo; 104 if (tryBlock.isEmptyBlock()) { 105 tryInfo = flowInfo; 106 tryBlockExit = false; 107 } else { 108 tryInfo = tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy()); 109 tryBlockExit = !tryInfo.isReachable(); 110 } 111 112 // check unreachable catch blocks 113 handlingContext.complainIfUnusedExceptionHandlers(scope, this); 114 115 // process the catch blocks - computing the minimal exit depth amongst try/catch 116 if (catchArguments != null) { 117 int catchCount; 118 catchExits = new boolean[catchCount = catchBlocks.length]; 119 for (int i = 0; i < catchCount; i++) { 120 // keep track of the inits that could potentially have led to this exception handler (for final assignments diagnosis) 121 FlowInfo catchInfo = 122 flowInfo 123 .copy() 124 .unconditionalInits() 125 .addPotentialInitializationsFrom( 126 handlingContext.initsOnException(caughtExceptionTypes[i]).unconditionalInits()) 127 .addPotentialInitializationsFrom(tryInfo.unconditionalInits()) 128 .addPotentialInitializationsFrom(handlingContext.initsOnReturn); 129 130 // catch var is always set 131 catchInfo.markAsDefinitelyAssigned(catchArguments[i].binding); 132 /* 133 "If we are about to consider an unchecked exception handler, potential inits may have occured inside 134 the try block that need to be detected , e.g. 135 try { x = 1; throwSomething();} catch(Exception e){ x = 2} " 136 "(uncheckedExceptionTypes notNil and: [uncheckedExceptionTypes at: index]) 137 ifTrue: [catchInits addPotentialInitializationsFrom: tryInits]." 138 */ 139 // TODO (philippe) should only tag as unreachable if the catchblock cannot be reached? 140 //??? if (!handlingContext.initsOnException(caughtExceptionTypes[i]).isReachable()){ 141 if (tryBlock.statements == null) { 142 catchInfo.setReachMode(FlowInfo.UNREACHABLE); 143 } 144 catchInfo = 145 catchBlocks[i].analyseCode( 146 currentScope, 147 insideSubContext == null ? flowContext : insideSubContext, 148 catchInfo); 149 catchExits[i] = !catchInfo.isReachable(); 150 tryInfo = tryInfo.mergedWith(catchInfo.unconditionalInits()); 151 } 152 } 153 if (subRoutineStartLabel == null) { 154 mergedInitStateIndex = 155 currentScope.methodScope().recordInitializationStates(tryInfo); 156 return tryInfo; 157 } 158 159 160 // we also need to check potential multiple assignments of final variables inside the finally block 161 // need to include potential inits from returns inside the try/catch parts - 1GK2AOF 162 finallyContext.complainOnRedundantFinalAssignments( 163 tryInfo.isReachable() 164 ? (tryInfo.addPotentialInitializationsFrom(insideSubContext.initsOnReturn)) 165 : insideSubContext.initsOnReturn, 166 currentScope); 167 if (subInfo == FlowInfo.DEAD_END) { 168 mergedInitStateIndex = 169 currentScope.methodScope().recordInitializationStates(subInfo); 170 return subInfo; 171 } else { 172 FlowInfo mergedInfo = tryInfo.addInitializationsFrom(subInfo); 173 mergedInitStateIndex = 174 currentScope.methodScope().recordInitializationStates(mergedInfo); 175 return mergedInfo; 176 } 177 } 178 isSubRoutineEscaping()179 public boolean isSubRoutineEscaping() { 180 181 return isSubRoutineEscaping; 182 } 183 184 /** 185 * Try statement code generation with or without jsr bytecode use 186 * post 1.5 target level, cannot use jsr bytecode, must instead inline finally block 187 * returnAddress is only allocated if jsr is allowed 188 */ generateCode(BlockScope currentScope, CodeStream codeStream)189 public void generateCode(BlockScope currentScope, CodeStream codeStream) { 190 if ((bits & IsReachableMASK) == 0) { 191 return; 192 } 193 // in case the labels needs to be reinitialized 194 // when the code generation is restarted in wide mode 195 if (this.anyExceptionLabelsCount > 0) { 196 this.anyExceptionLabels = NO_EXCEPTION_HANDLER; 197 this.anyExceptionLabelsCount = 0; 198 } 199 int pc = codeStream.position; 200 final int NO_FINALLY = 0; // no finally block 201 final int FINALLY_SUBROUTINE = 1; // finally is generated as a subroutine (using jsr/ret bytecodes) 202 final int FINALLY_DOES_NOT_COMPLETE = 2; // non returning finally is optimized with only one instance of finally block 203 final int FINALLY_MUST_BE_INLINED = 3; // finally block must be inlined since cannot use jsr/ret bytecodes >1.5 204 int finallyMode; 205 if (subRoutineStartLabel == null) { 206 finallyMode = NO_FINALLY; 207 } else { 208 if (this.isSubRoutineEscaping) { 209 finallyMode = FINALLY_DOES_NOT_COMPLETE; 210 } else if (scope.environment().options.inlineJsrBytecode) { 211 finallyMode = FINALLY_MUST_BE_INLINED; 212 } else { 213 finallyMode = FINALLY_SUBROUTINE; 214 } 215 } 216 boolean requiresNaturalExit = false; 217 // preparing exception labels 218 int maxCatches; 219 ExceptionLabel[] exceptionLabels = 220 new ExceptionLabel[maxCatches = 221 catchArguments == null ? 0 : catchArguments.length]; 222 for (int i = 0; i < maxCatches; i++) { 223 exceptionLabels[i] = new ExceptionLabel(codeStream, catchArguments[i].binding.type); 224 } 225 if (subRoutineStartLabel != null) { 226 subRoutineStartLabel.initialize(codeStream); 227 this.enterAnyExceptionHandler(codeStream); 228 } 229 // generate the try block 230 tryBlock.generateCode(scope, codeStream); 231 boolean tryBlockHasSomeCode = codeStream.position != pc; 232 // flag telling if some bytecodes were issued inside the try block 233 234 // place end positions of user-defined exception labels 235 if (tryBlockHasSomeCode) { 236 // natural exit may require subroutine invocation (if finally != null) 237 Label naturalExitLabel = new Label(codeStream); 238 if (!tryBlockExit) { 239 int position = codeStream.position; 240 switch(finallyMode) { 241 case FINALLY_SUBROUTINE : 242 case FINALLY_MUST_BE_INLINED : 243 requiresNaturalExit = true; 244 // fall through 245 case NO_FINALLY : 246 codeStream.goto_(naturalExitLabel); 247 break; 248 case FINALLY_DOES_NOT_COMPLETE : 249 codeStream.goto_(subRoutineStartLabel); 250 break; 251 } 252 codeStream.updateLastRecordedEndPC(position); 253 //goto is tagged as part of the try block 254 } 255 for (int i = 0; i < maxCatches; i++) { 256 exceptionLabels[i].placeEnd(); 257 } 258 /* generate sequence of handler, all starting by storing the TOS (exception 259 thrown) into their own catch variables, the one specified in the source 260 that must denote the handled exception. 261 */ 262 if (catchArguments != null) { 263 for (int i = 0; i < maxCatches; i++) { 264 // May loose some local variable initializations : affecting the local variable attributes 265 if (preTryInitStateIndex != -1) { 266 codeStream.removeNotDefinitelyAssignedVariables(currentScope, preTryInitStateIndex); 267 } 268 exceptionLabels[i].place(); 269 codeStream.incrStackSize(1); 270 // optimizing the case where the exception variable is not actually used 271 LocalVariableBinding catchVar; 272 int varPC = codeStream.position; 273 if ((catchVar = catchArguments[i].binding).resolvedPosition != -1) { 274 codeStream.store(catchVar, false); 275 catchVar.recordInitializationStartPC(codeStream.position); 276 codeStream.addVisibleLocalVariable(catchVar); 277 } else { 278 codeStream.pop(); 279 } 280 codeStream.recordPositionsFrom(varPC, catchArguments[i].sourceStart); 281 // Keep track of the pcs at diverging point for computing the local attribute 282 // since not passing the catchScope, the block generation will exitUserScope(catchScope) 283 catchBlocks[i].generateCode(scope, codeStream); 284 if (!catchExits[i]) { 285 switch(finallyMode) { 286 case FINALLY_SUBROUTINE : 287 case FINALLY_MUST_BE_INLINED : 288 requiresNaturalExit = true; 289 // fall through 290 case NO_FINALLY : 291 codeStream.goto_(naturalExitLabel); 292 break; 293 case FINALLY_DOES_NOT_COMPLETE : 294 codeStream.goto_(subRoutineStartLabel); 295 break; 296 } 297 } 298 } 299 } 300 this.exitAnyExceptionHandler(); 301 // extra handler for trailing natural exit (will be fixed up later on when natural exit is generated below) 302 ExceptionLabel naturalExitExceptionHandler = 303 finallyMode == FINALLY_SUBROUTINE && requiresNaturalExit ? new ExceptionLabel(codeStream, null) : null; 304 305 // addition of a special handler so as to ensure that any uncaught exception (or exception thrown 306 // inside catch blocks) will run the finally block 307 int finallySequenceStartPC = codeStream.position; 308 if (subRoutineStartLabel != null) { 309 this.placeAllAnyExceptionHandlers(); 310 if (naturalExitExceptionHandler != null) naturalExitExceptionHandler.place(); 311 312 if (preTryInitStateIndex != -1) { 313 // reset initialization state, as for a normal catch block 314 codeStream.removeNotDefinitelyAssignedVariables(currentScope, preTryInitStateIndex); 315 } 316 317 codeStream.incrStackSize(1); 318 switch(finallyMode) { 319 case FINALLY_SUBROUTINE : 320 codeStream.store(anyExceptionVariable, false); 321 codeStream.jsr(subRoutineStartLabel); 322 codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart); 323 int position = codeStream.position; 324 codeStream.load(anyExceptionVariable); 325 codeStream.athrow(); 326 codeStream.recordPositionsFrom(position, finallyBlock.sourceEnd); 327 subRoutineStartLabel.place(); 328 codeStream.incrStackSize(1); 329 position = codeStream.position; 330 codeStream.store(returnAddressVariable, false); 331 codeStream.recordPositionsFrom(position, finallyBlock.sourceStart); 332 finallyBlock.generateCode(scope, codeStream); 333 position = codeStream.position; 334 codeStream.ret(returnAddressVariable.resolvedPosition); 335 // codeStream.updateLastRecordedEndPC(position); 336 codeStream.recordPositionsFrom( 337 position, 338 finallyBlock.sourceEnd); 339 // the ret bytecode is part of the subroutine 340 break; 341 case FINALLY_MUST_BE_INLINED : 342 codeStream.store(anyExceptionVariable, false); 343 codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart); 344 this.finallyBlock.generateCode(currentScope, codeStream); 345 position = codeStream.position; 346 codeStream.load(anyExceptionVariable); 347 codeStream.athrow(); 348 subRoutineStartLabel.place(); 349 codeStream.recordPositionsFrom(position, finallyBlock.sourceEnd); 350 break; 351 case FINALLY_DOES_NOT_COMPLETE : 352 codeStream.pop(); 353 subRoutineStartLabel.place(); 354 codeStream.recordPositionsFrom(finallySequenceStartPC, finallyBlock.sourceStart); 355 finallyBlock.generateCode(scope, codeStream); 356 break; 357 } 358 // will naturally fall into subsequent code after subroutine invocation 359 naturalExitLabel.place(); 360 if (requiresNaturalExit) { 361 switch(finallyMode) { 362 case FINALLY_SUBROUTINE : 363 int position = codeStream.position; 364 // fix up natural exit handler 365 naturalExitExceptionHandler.placeStart(); 366 codeStream.jsr(subRoutineStartLabel); 367 naturalExitExceptionHandler.placeEnd(); 368 codeStream.recordPositionsFrom( 369 position, 370 finallyBlock.sourceEnd); 371 break; 372 case FINALLY_MUST_BE_INLINED : 373 // May loose some local variable initializations : affecting the local variable attributes 374 // needed since any exception handler got inlined subroutine 375 if (preTryInitStateIndex != -1) { 376 codeStream.removeNotDefinitelyAssignedVariables(currentScope, preTryInitStateIndex); 377 } 378 // entire sequence for finally is associated to finally block 379 finallyBlock.generateCode(scope, codeStream); 380 break; 381 case FINALLY_DOES_NOT_COMPLETE : 382 break; 383 } 384 } 385 } else { 386 // no subroutine, simply position end label (natural exit == end) 387 naturalExitLabel.place(); 388 } 389 } else { 390 // try block had no effect, only generate the body of the finally block if any 391 if (subRoutineStartLabel != null) { 392 finallyBlock.generateCode(scope, codeStream); 393 } 394 } 395 // May loose some local variable initializations : affecting the local variable attributes 396 if (mergedInitStateIndex != -1) { 397 codeStream.removeNotDefinitelyAssignedVariables(currentScope, mergedInitStateIndex); 398 codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex); 399 } 400 codeStream.recordPositionsFrom(pc, this.sourceStart); 401 } 402 403 /* (non-Javadoc) 404 * @see org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement#generateSubRoutineInvocation(org.eclipse.jdt.internal.compiler.lookup.BlockScope, org.eclipse.jdt.internal.compiler.codegen.CodeStream) 405 */ generateSubRoutineInvocation( BlockScope currentScope, CodeStream codeStream)406 public void generateSubRoutineInvocation( 407 BlockScope currentScope, 408 CodeStream codeStream) { 409 410 if (this.isSubRoutineEscaping) { 411 codeStream.goto_(this.subRoutineStartLabel); 412 } else { 413 if (currentScope.environment().options.inlineJsrBytecode) { 414 // cannot use jsr bytecode, then simply inline the subroutine 415 this.finallyBlock.generateCode(currentScope, codeStream); 416 } else { 417 // classic subroutine invocation, distinguish case of non-returning subroutine 418 codeStream.jsr(this.subRoutineStartLabel); 419 } 420 } 421 } 422 printStatement(int indent, StringBuffer output)423 public StringBuffer printStatement(int indent, StringBuffer output) { 424 printIndent(indent, output).append("try \n"); //$NON-NLS-1$ 425 tryBlock.printStatement(indent + 1, output); //$NON-NLS-1$ 426 427 //catches 428 if (catchBlocks != null) 429 for (int i = 0; i < catchBlocks.length; i++) { 430 output.append('\n'); 431 printIndent(indent, output).append("catch ("); //$NON-NLS-1$ 432 catchArguments[i].print(0, output).append(") "); //$NON-NLS-1$ 433 catchBlocks[i].printStatement(indent + 1, output); 434 } 435 //finally 436 if (finallyBlock != null) { 437 output.append('\n'); 438 printIndent(indent, output).append("finally\n"); //$NON-NLS-1$ 439 finallyBlock.printStatement(indent + 1, output); 440 } 441 442 return output; 443 } 444 resolve(BlockScope upperScope)445 public void resolve(BlockScope upperScope) { 446 447 // special scope for secret locals optimization. 448 this.scope = new BlockScope(upperScope); 449 450 BlockScope tryScope = new BlockScope(scope); 451 BlockScope finallyScope = null; 452 453 if (finallyBlock != null) { 454 if (finallyBlock.isEmptyBlock()) { 455 if ((finallyBlock.bits & UndocumentedEmptyBlockMASK) != 0) { 456 scope.problemReporter().undocumentedEmptyBlock(finallyBlock.sourceStart, finallyBlock.sourceEnd); 457 } 458 } else { 459 finallyScope = new BlockScope(scope, false); // don't add it yet to parent scope 460 461 // provision for returning and forcing the finally block to run 462 MethodScope methodScope = scope.methodScope(); 463 464 // the type does not matter as long as it is not a base type 465 if (!upperScope.environment().options.inlineJsrBytecode) { 466 this.returnAddressVariable = 467 new LocalVariableBinding(SecretReturnName, upperScope.getJavaLangObject(), AccDefault, false); 468 finallyScope.addLocalVariable(returnAddressVariable); 469 this.returnAddressVariable.setConstant(NotAConstant); // not inlinable 470 } 471 this.subRoutineStartLabel = new Label(); 472 473 this.anyExceptionVariable = 474 new LocalVariableBinding(SecretAnyHandlerName, scope.getJavaLangThrowable(), AccDefault, false); 475 finallyScope.addLocalVariable(this.anyExceptionVariable); 476 this.anyExceptionVariable.setConstant(NotAConstant); // not inlinable 477 478 if (!methodScope.isInsideInitializer()) { 479 MethodBinding methodBinding = 480 ((AbstractMethodDeclaration) methodScope.referenceContext).binding; 481 if (methodBinding != null) { 482 TypeBinding methodReturnType = methodBinding.returnType; 483 if (methodReturnType.id != T_void) { 484 this.secretReturnValue = 485 new LocalVariableBinding( 486 SecretLocalDeclarationName, 487 methodReturnType, 488 AccDefault, 489 false); 490 finallyScope.addLocalVariable(this.secretReturnValue); 491 this.secretReturnValue.setConstant(NotAConstant); // not inlinable 492 } 493 } 494 } 495 finallyBlock.resolveUsing(finallyScope); 496 // force the finally scope to have variable positions shifted after its try scope and catch ones 497 finallyScope.shiftScopes = new BlockScope[catchArguments == null ? 1 : catchArguments.length+1]; 498 finallyScope.shiftScopes[0] = tryScope; 499 } 500 } 501 this.tryBlock.resolveUsing(tryScope); 502 503 // arguments type are checked against JavaLangThrowable in resolveForCatch(..) 504 if (this.catchBlocks != null) { 505 int length = this.catchArguments.length; 506 TypeBinding[] argumentTypes = new TypeBinding[length]; 507 boolean catchHasError = false; 508 for (int i = 0; i < length; i++) { 509 BlockScope catchScope = new BlockScope(scope); 510 if (finallyScope != null){ 511 finallyScope.shiftScopes[i+1] = catchScope; 512 } 513 // side effect on catchScope in resolveForCatch(..) 514 if ((argumentTypes[i] = catchArguments[i].resolveForCatch(catchScope)) == null) { 515 catchHasError = true; 516 } 517 catchBlocks[i].resolveUsing(catchScope); 518 } 519 if (catchHasError) { 520 return; 521 } 522 // Verify that the catch clause are ordered in the right way: 523 // more specialized first. 524 this.caughtExceptionTypes = new ReferenceBinding[length]; 525 for (int i = 0; i < length; i++) { 526 caughtExceptionTypes[i] = (ReferenceBinding) argumentTypes[i]; 527 for (int j = 0; j < i; j++) { 528 if (caughtExceptionTypes[i].isCompatibleWith(argumentTypes[j])) { 529 scope.problemReporter().wrongSequenceOfExceptionTypesError(this, caughtExceptionTypes[i], i, argumentTypes[j]); 530 } 531 } 532 } 533 } else { 534 caughtExceptionTypes = new ReferenceBinding[0]; 535 } 536 537 if (finallyScope != null){ 538 // add finallyScope as last subscope, so it can be shifted behind try/catch subscopes. 539 // the shifting is necessary to achieve no overlay in between the finally scope and its 540 // sibling in term of local variable positions. 541 this.scope.addSubscope(finallyScope); 542 } 543 } 544 traverse( ASTVisitor visitor, BlockScope blockScope)545 public void traverse( 546 ASTVisitor visitor, 547 BlockScope blockScope) { 548 549 if (visitor.visit(this, blockScope)) { 550 tryBlock.traverse(visitor, scope); 551 if (catchArguments != null) { 552 for (int i = 0, max = catchBlocks.length; i < max; i++) { 553 catchArguments[i].traverse(visitor, scope); 554 catchBlocks[i].traverse(visitor, scope); 555 } 556 } 557 if (finallyBlock != null) 558 finallyBlock.traverse(visitor, scope); 559 } 560 visitor.endVisit(this, blockScope); 561 } 562 } 563