public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { // Consider the try block and catch block so as to compute the intersection of initializations // and // the minimum exit relative depth amongst all of them. Then consider the subroutine, and append // its // initialization to the try/catch ones, if the subroutine completes normally. If the subroutine // does not // complete, then only keep this result for the rest of the analysis // process the finally block (subroutine) - create a context for the subroutine preTryInitStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo); if (anyExceptionVariable != null) { anyExceptionVariable.useFlag = LocalVariableBinding.USED; } if (returnAddressVariable != null) { // TODO (philippe) if subroutine is escaping, unused returnAddressVariable.useFlag = LocalVariableBinding.USED; } InsideSubRoutineFlowContext insideSubContext; FinallyFlowContext finallyContext; UnconditionalFlowInfo subInfo; if (subRoutineStartLabel == null) { // no finally block insideSubContext = null; finallyContext = null; subInfo = null; } else { // analyse finally block first insideSubContext = new InsideSubRoutineFlowContext(flowContext, this); subInfo = finallyBlock .analyseCode( currentScope, finallyContext = new FinallyFlowContext(flowContext, finallyBlock), flowInfo.copy().unconditionalInits().discardNullRelatedInitializations()) .unconditionalInits(); if (subInfo == FlowInfo.DEAD_END) { isSubRoutineEscaping = true; scope.problemReporter().finallyMustCompleteNormally(finallyBlock); } this.subRoutineInits = subInfo; } // process the try block in a context handling the local exceptions. ExceptionHandlingFlowContext handlingContext = new ExceptionHandlingFlowContext( insideSubContext == null ? flowContext : insideSubContext, tryBlock, caughtExceptionTypes, scope, flowInfo.unconditionalInits()); FlowInfo tryInfo; if (tryBlock.isEmptyBlock()) { tryInfo = flowInfo; tryBlockExit = false; } else { tryInfo = tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy()); tryBlockExit = !tryInfo.isReachable(); } // check unreachable catch blocks handlingContext.complainIfUnusedExceptionHandlers(scope, this); // process the catch blocks - computing the minimal exit depth amongst try/catch if (catchArguments != null) { int catchCount; catchExits = new boolean[catchCount = catchBlocks.length]; for (int i = 0; i < catchCount; i++) { // keep track of the inits that could potentially have led to this exception handler (for // final assignments diagnosis) FlowInfo catchInfo = flowInfo .copy() .unconditionalInits() .addPotentialInitializationsFrom( handlingContext.initsOnException(caughtExceptionTypes[i]).unconditionalInits()) .addPotentialInitializationsFrom(tryInfo.unconditionalInits()) .addPotentialInitializationsFrom(handlingContext.initsOnReturn); // catch var is always set LocalVariableBinding catchArg = catchArguments[i].binding; FlowContext catchContext = insideSubContext == null ? flowContext : insideSubContext; catchInfo.markAsDefinitelyAssigned(catchArg); catchInfo.markAsDefinitelyNonNull(catchArg); /* "If we are about to consider an unchecked exception handler, potential inits may have occured inside the try block that need to be detected , e.g. try { x = 1; throwSomething();} catch(Exception e){ x = 2} " "(uncheckedExceptionTypes notNil and: [uncheckedExceptionTypes at: index]) ifTrue: [catchInits addPotentialInitializationsFrom: tryInits]." */ if (tryBlock.statements == null) { catchInfo.setReachMode(FlowInfo.UNREACHABLE); } catchInfo = catchBlocks[i].analyseCode(currentScope, catchContext, catchInfo); catchExits[i] = !catchInfo.isReachable(); tryInfo = tryInfo.mergedWith(catchInfo.unconditionalInits()); } } if (subRoutineStartLabel == null) { mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(tryInfo); return tryInfo; } // we also need to check potential multiple assignments of final variables inside the finally // block // need to include potential inits from returns inside the try/catch parts - 1GK2AOF finallyContext.complainOnDeferredChecks( tryInfo.isReachable() ? (tryInfo.addPotentialInitializationsFrom(insideSubContext.initsOnReturn)) : insideSubContext.initsOnReturn, currentScope); if (subInfo == FlowInfo.DEAD_END) { mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(subInfo); return subInfo; } else { FlowInfo mergedInfo = tryInfo.addInitializationsFrom(subInfo); mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo); return mergedInfo; } }
public void resolve(BlockScope upperScope) { // special scope for secret locals optimization. this.scope = new BlockScope(upperScope); BlockScope tryScope = new BlockScope(scope); BlockScope finallyScope = null; if (finallyBlock != null) { if (finallyBlock.isEmptyBlock()) { if ((finallyBlock.bits & UndocumentedEmptyBlockMASK) != 0) { scope .problemReporter() .undocumentedEmptyBlock(finallyBlock.sourceStart, finallyBlock.sourceEnd); } } else { finallyScope = new BlockScope(scope, false); // don't add it yet to parent scope // provision for returning and forcing the finally block to run MethodScope methodScope = scope.methodScope(); // the type does not matter as long as it is not a base type if (!upperScope.compilerOptions().inlineJsrBytecode) { this.returnAddressVariable = new LocalVariableBinding( SecretReturnName, upperScope.getJavaLangObject(), AccDefault, false); finallyScope.addLocalVariable(returnAddressVariable); this.returnAddressVariable.setConstant(NotAConstant); // not inlinable } this.subRoutineStartLabel = new Label(); this.anyExceptionVariable = new LocalVariableBinding( SecretAnyHandlerName, scope.getJavaLangThrowable(), AccDefault, false); finallyScope.addLocalVariable(this.anyExceptionVariable); this.anyExceptionVariable.setConstant(NotAConstant); // not inlinable if (!methodScope.isInsideInitializer()) { MethodBinding methodBinding = ((AbstractMethodDeclaration) methodScope.referenceContext).binding; if (methodBinding != null) { TypeBinding methodReturnType = methodBinding.returnType; if (methodReturnType.id != T_void) { this.secretReturnValue = new LocalVariableBinding( SecretLocalDeclarationName, methodReturnType, AccDefault, false); finallyScope.addLocalVariable(this.secretReturnValue); this.secretReturnValue.setConstant(NotAConstant); // not inlinable } } } finallyBlock.resolveUsing(finallyScope); // force the finally scope to have variable positions shifted after its try scope and catch // ones finallyScope.shiftScopes = new BlockScope[catchArguments == null ? 1 : catchArguments.length + 1]; finallyScope.shiftScopes[0] = tryScope; } } this.tryBlock.resolveUsing(tryScope); // arguments type are checked against JavaLangThrowable in resolveForCatch(..) if (this.catchBlocks != null) { int length = this.catchArguments.length; TypeBinding[] argumentTypes = new TypeBinding[length]; boolean catchHasError = false; for (int i = 0; i < length; i++) { BlockScope catchScope = new BlockScope(scope); if (finallyScope != null) { finallyScope.shiftScopes[i + 1] = catchScope; } // side effect on catchScope in resolveForCatch(..) if ((argumentTypes[i] = catchArguments[i].resolveForCatch(catchScope)) == null) { catchHasError = true; } catchBlocks[i].resolveUsing(catchScope); } if (catchHasError) { return; } // Verify that the catch clause are ordered in the right way: // more specialized first. this.caughtExceptionTypes = new ReferenceBinding[length]; for (int i = 0; i < length; i++) { caughtExceptionTypes[i] = (ReferenceBinding) argumentTypes[i]; for (int j = 0; j < i; j++) { if (caughtExceptionTypes[i].isCompatibleWith(argumentTypes[j])) { scope .problemReporter() .wrongSequenceOfExceptionTypesError( this, caughtExceptionTypes[i], i, argumentTypes[j]); } } } } else { caughtExceptionTypes = new ReferenceBinding[0]; } if (finallyScope != null) { // add finallyScope as last subscope, so it can be shifted behind try/catch subscopes. // the shifting is necessary to achieve no overlay in between the finally scope and its // sibling in term of local variable positions. this.scope.addSubscope(finallyScope); } }