// Report an error if necessary (if even more unreachable than previously reported
 // complaintLevel = 0 if was reachable up until now, 1 if fake reachable (deadcode), 2 if fatal
 // unreachable (error)
 public int complainIfUnreachable(
     FlowInfo flowInfo, BlockScope scope, int previousComplaintLevel, boolean endOfBlock) {
   if ((flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0) {
     if ((flowInfo.reachMode() & FlowInfo.UNREACHABLE_OR_DEAD) != 0)
       this.bits &= ~ASTNode.IsReachable;
     if (flowInfo == FlowInfo.DEAD_END) {
       if (previousComplaintLevel < COMPLAINED_UNREACHABLE) {
         scope.problemReporter().unreachableCode(this);
         if (endOfBlock) scope.checkUnclosedCloseables(flowInfo, null, null, null);
       }
       return COMPLAINED_UNREACHABLE;
     } else {
       if (previousComplaintLevel < COMPLAINED_FAKE_REACHABLE) {
         scope.problemReporter().fakeReachable(this);
         if (endOfBlock) scope.checkUnclosedCloseables(flowInfo, null, null, null);
       }
       return COMPLAINED_FAKE_REACHABLE;
     }
   }
   return previousComplaintLevel;
 }
Exemple #2
0
  // Report an error if necessary
  public boolean complainIfUnreachable(
      FlowInfo flowInfo, BlockScope scope, boolean didAlreadyComplain) {

    if ((flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0) {
      this.bits &= ~ASTNode.IsReachableMASK;
      boolean reported = flowInfo == FlowInfo.DEAD_END;
      if (!didAlreadyComplain && reported) {
        scope.problemReporter().unreachableCode(this);
      }
      return reported; // keep going for fake reachable
    }
    return false;
  }
 public void checkCapturedLocalInitializationIfNecessary(
     ReferenceBinding checkedType, BlockScope currentScope, FlowInfo flowInfo) {
   if (((checkedType.tagBits & (TagBits.AnonymousTypeMask | TagBits.LocalTypeMask))
           == TagBits.LocalTypeMask)
       && !currentScope.isDefinedInType(checkedType)) { // only check external allocations
     NestedTypeBinding nestedType = (NestedTypeBinding) checkedType;
     SyntheticArgumentBinding[] syntheticArguments = nestedType.syntheticOuterLocalVariables();
     if (syntheticArguments != null)
       for (int i = 0, count = syntheticArguments.length; i < count; i++) {
         SyntheticArgumentBinding syntheticArgument = syntheticArguments[i];
         LocalVariableBinding targetLocal;
         if ((targetLocal = syntheticArgument.actualOuterLocalVariable) == null) continue;
         if (targetLocal.declaration != null && !flowInfo.isDefinitelyAssigned(targetLocal)) {
           currentScope.problemReporter().uninitializedLocalVariable(targetLocal, this);
         }
       }
   }
 }
 public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
   LocalVariableBinding local = this.expression.localVariableBinding();
   if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) {
     flowInfo =
         this.expression.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
     FlowInfo initsWhenTrue = flowInfo.copy();
     initsWhenTrue.markAsComparedEqualToNonNull(local);
     if ((flowContext.tagBits & FlowContext.HIDE_NULL_COMPARISON_WARNING) != 0) {
       initsWhenTrue.markedAsNullOrNonNullInAssertExpression(local);
     }
     flowContext.recordUsingNullReference(
         currentScope,
         local,
         this.expression,
         FlowContext.CAN_ONLY_NULL | FlowContext.IN_INSTANCEOF,
         flowInfo);
     // no impact upon enclosing try context
     return FlowInfo.conditional(initsWhenTrue, flowInfo.copy());
   }
   return this.expression.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
 }
Exemple #5
0
  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 FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {

    Constant cst = this.left.optimizedBooleanConstant();
    boolean isLeftOptimizedTrue = cst != Constant.NotAConstant && cst.booleanValue() == true;
    boolean isLeftOptimizedFalse = cst != Constant.NotAConstant && cst.booleanValue() == false;

    if (isLeftOptimizedFalse) {
      // FALSE || anything
      // need to be careful of scenario:
      //		(x || y) || !z, if passing the left info to the right, it would be swapped by the !
      FlowInfo mergedInfo =
          this.left.analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
      flowContext.expireNullCheckedFieldInfo();
      mergedInfo = this.right.analyseCode(currentScope, flowContext, mergedInfo);
      flowContext.expireNullCheckedFieldInfo();
      this.mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo);
      return mergedInfo;
    }

    FlowInfo leftInfo = this.left.analyseCode(currentScope, flowContext, flowInfo);
    flowContext.expireNullCheckedFieldInfo();

    // need to be careful of scenario:
    //		(x || y) || !z, if passing the left info to the right, it would be swapped by the !
    FlowInfo rightInfo = leftInfo.initsWhenFalse().unconditionalCopy();
    this.rightInitStateIndex = currentScope.methodScope().recordInitializationStates(rightInfo);

    int previousMode = rightInfo.reachMode();
    if (isLeftOptimizedTrue) {
      if ((rightInfo.reachMode() & FlowInfo.UNREACHABLE) == 0) {
        currentScope.problemReporter().fakeReachable(this.right);
        rightInfo.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
      }
    }
    rightInfo = this.right.analyseCode(currentScope, flowContext, rightInfo);
    flowContext.expireNullCheckedFieldInfo();
    if ((this.left.implicitConversion & TypeIds.UNBOXING) != 0) {
      this.left.checkNPE(currentScope, flowContext, flowInfo);
    }
    if ((this.right.implicitConversion & TypeIds.UNBOXING) != 0) {
      this.right.checkNPE(currentScope, flowContext, flowInfo);
    }
    // The definitely null variables in right info when true should not be missed out while merging
    // https://bugs.eclipse.org/bugs/show_bug.cgi?id=299900
    FlowInfo leftInfoWhenTrueForMerging =
        leftInfo
            .initsWhenTrue()
            .unconditionalCopy()
            .addPotentialInitializationsFrom(rightInfo.unconditionalInitsWithoutSideEffect());
    FlowInfo mergedInfo =
        FlowInfo.conditional(
            // merging two true initInfos for such a negative case: if ((t && (b = t)) || f) r = b;
            // // b may not have been initialized
            leftInfoWhenTrueForMerging
                .unconditionalInits()
                .mergedWith(
                    rightInfo.safeInitsWhenTrue().setReachMode(previousMode).unconditionalInits()),
            rightInfo.initsWhenFalse());
    this.mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo);
    return mergedInfo;
  }
  public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
    this.preAssertInitStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo);

    Constant cst = this.assertExpression.optimizedBooleanConstant();
    if ((this.assertExpression.implicitConversion & TypeIds.UNBOXING) != 0) {
      this.assertExpression.checkNPE(currentScope, flowContext, flowInfo);
    }
    boolean isOptimizedTrueAssertion = cst != Constant.NotAConstant && cst.booleanValue() == true;
    boolean isOptimizedFalseAssertion = cst != Constant.NotAConstant && cst.booleanValue() == false;

    flowContext.tagBits |= FlowContext.HIDE_NULL_COMPARISON_WARNING;
    FlowInfo conditionFlowInfo =
        this.assertExpression.analyseCode(currentScope, flowContext, flowInfo.copy());
    flowContext.extendTimeToLiveForNullCheckedField(1); // survive this assert as a Statement
    flowContext.tagBits &= ~FlowContext.HIDE_NULL_COMPARISON_WARNING;
    UnconditionalFlowInfo assertWhenTrueInfo =
        conditionFlowInfo.initsWhenTrue().unconditionalInits();
    FlowInfo assertInfo = conditionFlowInfo.initsWhenFalse();
    if (isOptimizedTrueAssertion) {
      assertInfo.setReachMode(FlowInfo.UNREACHABLE_OR_DEAD);
    }

    if (this.exceptionArgument != null) {
      // only gets evaluated when escaping - results are not taken into account
      FlowInfo exceptionInfo =
          this.exceptionArgument.analyseCode(currentScope, flowContext, assertInfo.copy());

      if (isOptimizedTrueAssertion) {
        currentScope.problemReporter().fakeReachable(this.exceptionArgument);
      } else {
        flowContext.checkExceptionHandlers(
            currentScope.getJavaLangAssertionError(), this, exceptionInfo, currentScope);
      }
    }

    if (!isOptimizedTrueAssertion) {
      // add the assert support in the clinit
      manageSyntheticAccessIfNecessary(currentScope, flowInfo);
    }
    // account for potential AssertionError:
    flowContext.recordAbruptExit();
    if (isOptimizedFalseAssertion) {
      return flowInfo; // if assertions are enabled, the following code will be unreachable
      // change this if we need to carry null analysis results of the assert
      // expression downstream
    } else {
      CompilerOptions compilerOptions = currentScope.compilerOptions();
      if (!compilerOptions.includeNullInfoFromAsserts) {
        // keep just the initializations info, don't include assert's null info
        // merge initialization info's and then add back the null info from flowInfo to
        // make sure that the empty null info of assertInfo doesnt change flowInfo's null info.
        return ((flowInfo.nullInfoLessUnconditionalCopy())
                .mergedWith(assertInfo.nullInfoLessUnconditionalCopy()))
            .addNullInfoFrom(flowInfo);
      }
      return flowInfo
          .mergedWith(assertInfo.nullInfoLessUnconditionalCopy())
          .addInitializationsFrom(assertWhenTrueInfo.discardInitializationInfo());
      // keep the merge from the initial code for the definite assignment
      // analysis, tweak the null part to influence nulls downstream
    }
  }
  public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
    // check captured variables are initialized in current context (26134)
    checkCapturedLocalInitializationIfNecessary(
        (ReferenceBinding) this.binding.declaringClass.erasure(), currentScope, flowInfo);

    // process arguments
    if (this.arguments != null) {
      boolean analyseResources = currentScope.compilerOptions().analyseResourceLeaks;
      boolean hasResourceWrapperType =
          analyseResources
              && this.resolvedType instanceof ReferenceBinding
              && ((ReferenceBinding) this.resolvedType).hasTypeBit(TypeIds.BitWrapperCloseable);
      for (int i = 0, count = this.arguments.length; i < count; i++) {
        flowInfo =
            this.arguments[i].analyseCode(currentScope, flowContext, flowInfo).unconditionalInits();
        // if argument is an AutoCloseable insert info that it *may* be closed (by the target
        // method, i.e.)
        if (analyseResources
            && !hasResourceWrapperType) { // allocation of wrapped closeables is analyzed specially
          flowInfo =
              FakedTrackingVariable.markPassedToOutside(
                  currentScope, this.arguments[i], flowInfo, flowContext, false);
        }
        this.arguments[i].checkNPEbyUnboxing(currentScope, flowContext, flowInfo);
      }
      analyseArguments(currentScope, flowContext, flowInfo, this.binding, this.arguments);
    }

    // record some dependency information for exception types
    ReferenceBinding[] thrownExceptions;
    if (((thrownExceptions = this.binding.thrownExceptions).length) != 0) {
      if ((this.bits & ASTNode.Unchecked) != 0 && this.genericTypeArguments == null) {
        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=277643, align with javac on JLS 15.12.2.6
        thrownExceptions =
            currentScope.environment().convertToRawTypes(this.binding.thrownExceptions, true, true);
      }
      // check exception handling
      flowContext.checkExceptionHandlers(
          thrownExceptions, this, flowInfo.unconditionalCopy(), currentScope);
    }

    // after having analysed exceptions above start tracking newly allocated resource:
    if (currentScope.compilerOptions().analyseResourceLeaks
        && FakedTrackingVariable.isAnyCloseable(this.resolvedType))
      FakedTrackingVariable.analyseCloseableAllocation(currentScope, flowInfo, this);

    if (this.binding.declaringClass.isMemberType() && !this.binding.declaringClass.isStatic()) {
      // allocating a non-static member type without an enclosing instance of parent type
      // https://bugs.eclipse.org/bugs/show_bug.cgi?id=335845
      currentScope.resetDeclaringClassMethodStaticFlag(this.binding.declaringClass.enclosingType());
      // Reviewed for https://bugs.eclipse.org/bugs/show_bug.cgi?id=378674 :
      // The corresponding problem (when called from static) is not produced until during code
      // generation
    }
    manageEnclosingInstanceAccessIfNecessary(currentScope, flowInfo);
    manageSyntheticAccessIfNecessary(currentScope, flowInfo);

    // account for possible exceptions thrown by the constructor
    flowContext.recordAbruptExit(); // TODO whitelist of ctors that cannot throw any exc.??

    return flowInfo;
  }
  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

    this.preTryInitStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo);

    if (this.anyExceptionVariable != null) {
      this.anyExceptionVariable.useFlag = LocalVariableBinding.USED;
    }
    if (this.returnAddressVariable != null) { // TODO (philippe) if subroutine is escaping, unused
      this.returnAddressVariable.useFlag = LocalVariableBinding.USED;
    }
    if (this.subRoutineStartLabel == null) {
      // no finally block -- this is a simplified copy of the else part
      // process the try block in a context handling the local exceptions.
      ExceptionHandlingFlowContext handlingContext =
          new ExceptionHandlingFlowContext(
              flowContext,
              this,
              this.caughtExceptionTypes,
              null,
              this.scope,
              flowInfo.unconditionalInits());
      handlingContext.initsOnFinally = new NullInfoRegistry(flowInfo.unconditionalInits());
      // only try blocks initialize that member - may consider creating a
      // separate class if needed

      FlowInfo tryInfo;
      if (this.tryBlock.isEmptyBlock()) {
        tryInfo = flowInfo;
      } else {
        tryInfo = this.tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy());
        if ((tryInfo.tagBits & FlowInfo.UNREACHABLE) != 0) this.bits |= ASTNode.IsTryBlockExiting;
      }

      // check unreachable catch blocks
      handlingContext.complainIfUnusedExceptionHandlers(this.scope, this);

      // process the catch blocks - computing the minimal exit depth amongst try/catch
      if (this.catchArguments != null) {
        int catchCount;
        this.catchExits = new boolean[catchCount = this.catchBlocks.length];
        this.catchExitInitStateIndexes = new int[catchCount];
        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;
          if (this.caughtExceptionTypes[i].isUncheckedException(true)) {
            catchInfo =
                handlingContext.initsOnFinally.mitigateNullInfoOf(
                    flowInfo
                        .unconditionalCopy()
                        .addPotentialInitializationsFrom(
                            handlingContext.initsOnException(this.caughtExceptionTypes[i]))
                        .addPotentialInitializationsFrom(tryInfo)
                        .addPotentialInitializationsFrom(handlingContext.initsOnReturn));
          } else {
            FlowInfo initsOnException =
                handlingContext.initsOnException(this.caughtExceptionTypes[i]);
            catchInfo =
                flowInfo
                    .nullInfoLessUnconditionalCopy()
                    .addPotentialInitializationsFrom(initsOnException)
                    .addNullInfoFrom(
                        initsOnException) // null info only from here, this is the only way to enter
                                          // the catch block
                    .addPotentialInitializationsFrom(tryInfo.nullInfoLessUnconditionalCopy())
                    .addPotentialInitializationsFrom(
                        handlingContext.initsOnReturn.nullInfoLessUnconditionalCopy());
          }

          // catch var is always set
          LocalVariableBinding catchArg = this.catchArguments[i].binding;
          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 (this.tryBlock.statements == null) {
            catchInfo.setReachMode(FlowInfo.UNREACHABLE);
          }
          catchInfo = this.catchBlocks[i].analyseCode(currentScope, flowContext, catchInfo);
          this.catchExitInitStateIndexes[i] =
              currentScope.methodScope().recordInitializationStates(catchInfo);
          this.catchExits[i] = (catchInfo.tagBits & FlowInfo.UNREACHABLE) != 0;
          tryInfo = tryInfo.mergedWith(catchInfo.unconditionalInits());
        }
      }
      this.mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(tryInfo);

      // chain up null info registry
      if (flowContext.initsOnFinally != null) {
        flowContext.initsOnFinally.add(handlingContext.initsOnFinally);
      }

      return tryInfo;
    } else {
      InsideSubRoutineFlowContext insideSubContext;
      FinallyFlowContext finallyContext;
      UnconditionalFlowInfo subInfo;
      // analyse finally block first
      insideSubContext = new InsideSubRoutineFlowContext(flowContext, this);

      subInfo =
          this.finallyBlock
              .analyseCode(
                  currentScope,
                  finallyContext = new FinallyFlowContext(flowContext, this.finallyBlock),
                  flowInfo.nullInfoLessUnconditionalCopy())
              .unconditionalInits();
      if (subInfo == FlowInfo.DEAD_END) {
        this.bits |= ASTNode.IsSubRoutineEscaping;
        this.scope.problemReporter().finallyMustCompleteNormally(this.finallyBlock);
      }
      this.subRoutineInits = subInfo;
      // process the try block in a context handling the local exceptions.
      ExceptionHandlingFlowContext handlingContext =
          new ExceptionHandlingFlowContext(
              insideSubContext,
              this,
              this.caughtExceptionTypes,
              null,
              this.scope,
              flowInfo.unconditionalInits());
      handlingContext.initsOnFinally = new NullInfoRegistry(flowInfo.unconditionalInits());
      // only try blocks initialize that member - may consider creating a
      // separate class if needed

      FlowInfo tryInfo;
      if (this.tryBlock.isEmptyBlock()) {
        tryInfo = flowInfo;
      } else {
        tryInfo = this.tryBlock.analyseCode(currentScope, handlingContext, flowInfo.copy());
        if ((tryInfo.tagBits & FlowInfo.UNREACHABLE) != 0) this.bits |= ASTNode.IsTryBlockExiting;
      }

      // check unreachable catch blocks
      handlingContext.complainIfUnusedExceptionHandlers(this.scope, this);

      // process the catch blocks - computing the minimal exit depth amongst try/catch
      if (this.catchArguments != null) {
        int catchCount;
        this.catchExits = new boolean[catchCount = this.catchBlocks.length];
        this.catchExitInitStateIndexes = new int[catchCount];
        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;
          if (this.caughtExceptionTypes[i].isUncheckedException(true)) {
            catchInfo =
                handlingContext.initsOnFinally.mitigateNullInfoOf(
                    flowInfo
                        .unconditionalCopy()
                        .addPotentialInitializationsFrom(
                            handlingContext.initsOnException(this.caughtExceptionTypes[i]))
                        .addPotentialInitializationsFrom(tryInfo)
                        .addPotentialInitializationsFrom(handlingContext.initsOnReturn));
          } else {
            FlowInfo initsOnException =
                handlingContext.initsOnException(this.caughtExceptionTypes[i]);
            catchInfo =
                flowInfo
                    .nullInfoLessUnconditionalCopy()
                    .addPotentialInitializationsFrom(initsOnException)
                    .addNullInfoFrom(
                        initsOnException) // null info only from here, this is the only way to enter
                                          // the catch block
                    .addPotentialInitializationsFrom(tryInfo.nullInfoLessUnconditionalCopy())
                    .addPotentialInitializationsFrom(
                        handlingContext.initsOnReturn.nullInfoLessUnconditionalCopy());
          }

          // catch var is always set
          LocalVariableBinding catchArg = this.catchArguments[i].binding;
          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 (this.tryBlock.statements == null) {
            catchInfo.setReachMode(FlowInfo.UNREACHABLE);
          }
          catchInfo = this.catchBlocks[i].analyseCode(currentScope, insideSubContext, catchInfo);
          this.catchExitInitStateIndexes[i] =
              currentScope.methodScope().recordInitializationStates(catchInfo);
          this.catchExits[i] = (catchInfo.tagBits & FlowInfo.UNREACHABLE) != 0;
          tryInfo = tryInfo.mergedWith(catchInfo.unconditionalInits());
        }
      }
      // 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(
          handlingContext.initsOnFinally.mitigateNullInfoOf(
              (tryInfo.tagBits & FlowInfo.UNREACHABLE) == 0
                  ? flowInfo
                      .unconditionalCopy()
                      .addPotentialInitializationsFrom(tryInfo)
                      .
                      // lighten the influence of the try block, which may have
                      // exited at any point
                      addPotentialInitializationsFrom(insideSubContext.initsOnReturn)
                  : insideSubContext.initsOnReturn),
          currentScope);

      // chain up null info registry
      if (flowContext.initsOnFinally != null) {
        flowContext.initsOnFinally.add(handlingContext.initsOnFinally);
      }

      this.naturalExitMergeInitStateIndex =
          currentScope.methodScope().recordInitializationStates(tryInfo);
      if (subInfo == FlowInfo.DEAD_END) {
        this.mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(subInfo);
        return subInfo;
      } else {
        FlowInfo mergedInfo = tryInfo.addInitializationsFrom(subInfo);
        this.mergedInitStateIndex =
            currentScope.methodScope().recordInitializationStates(mergedInfo);
        return mergedInfo;
      }
    }
  }