@Override
  public void visitTryStatement(@NotNull PsiTryStatement statement) {
    startElement(statement);

    PsiCodeBlock[] catchBlocks = statement.getCatchBlocks();
    PsiParameter[] catchBlockParameters = statement.getCatchBlockParameters();
    int catchNum = Math.min(catchBlocks.length, catchBlockParameters.length);
    myUnhandledExceptionCatchBlocks.push(null);
    for (int i = catchNum - 1; i >= 0; i--) {
      ProgressManager.checkCanceled();
      myCatchParameters.push(catchBlockParameters[i]);
      myCatchBlocks.push(catchBlocks[i]);

      final PsiType type = catchBlockParameters[i].getType();
      // todo cast param
      if (type instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)type)) {
        myUnhandledExceptionCatchBlocks.push(catchBlocks[i]);
      }
      else if (type instanceof PsiDisjunctionType) {
        final PsiType lub = ((PsiDisjunctionType)type).getLeastUpperBound();
        if (lub instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)lub)) {
          myUnhandledExceptionCatchBlocks.push(catchBlocks[i]);
        }
        else if (lub instanceof PsiIntersectionType) {
          for (PsiType conjunct : ((PsiIntersectionType)lub).getConjuncts()) {
            if (conjunct instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)conjunct)) {
              myUnhandledExceptionCatchBlocks.push(catchBlocks[i]);
              break;
            }
          }
        }
      }
    }

    PsiCodeBlock finallyBlock = statement.getFinallyBlock();

    FinallyBlockSubroutine finallyBlockSubroutine = null;
    if (finallyBlock != null) {
      finallyBlockSubroutine = new FinallyBlockSubroutine(finallyBlock);
      myFinallyBlocks.push(finallyBlockSubroutine);
    }

    PsiResourceList resourceList = statement.getResourceList();
    if (resourceList != null) {
      generateCheckedExceptionJumps(resourceList);
      resourceList.accept(this);
    }
    PsiCodeBlock tryBlock = statement.getTryBlock();
    if (tryBlock != null) {
      // javac works as if all checked exceptions can occur at the top of the block
      generateCheckedExceptionJumps(tryBlock);
      tryBlock.accept(this);
    }

    //noinspection StatementWithEmptyBody
    while (myUnhandledExceptionCatchBlocks.pop() != null) ;

    myCurrentFlow.addInstruction(new GoToInstruction(finallyBlock == null ? 0 : -6));
    if (finallyBlock == null) {
      addElementOffsetLater(statement, false);
    }
    else {
      addElementOffsetLater(finallyBlock, true);
    }

    for (int i = 0; i < catchNum; i++) {
      myCatchParameters.pop();
      myCatchBlocks.pop();
    }

    for (int i = catchNum - 1; i >= 0; i--) {
      ProgressManager.checkCanceled();
      if (myPolicy.isParameterAccepted(catchBlockParameters[i])) {
        generateWriteInstruction(catchBlockParameters[i]);
      }
      PsiCodeBlock catchBlock = catchBlocks[i];
      if (catchBlock != null) {
        catchBlock.accept(this);
      }
      else {
        LOG.error("Catch body is null (" + i + ") " + statement.getText());
      }

      myCurrentFlow.addInstruction(new GoToInstruction(finallyBlock == null ? 0 : -6));
      if (finallyBlock == null) {
        addElementOffsetLater(statement, false);
      }
      else {
        addElementOffsetLater(finallyBlock, true);
      }
    }

    if (finallyBlock != null) {
      myFinallyBlocks.pop();
    }

    if (finallyBlock != null) {
      // normal completion, call finally block and proceed
      CallInstruction normalCompletion = new CallInstruction(0, 0);
      finallyBlockSubroutine.addCall(normalCompletion);
      myCurrentFlow.addInstruction(normalCompletion);
      addElementOffsetLater(finallyBlock, true);
      myCurrentFlow.addInstruction(new GoToInstruction(0));
      addElementOffsetLater(statement, false);
      // return completion, call finally block and return
      CallInstruction returnCompletion = new CallInstruction(0, 0);
      finallyBlockSubroutine.addCall(returnCompletion);
      myCurrentFlow.addInstruction(returnCompletion);
      addElementOffsetLater(finallyBlock, true);
      addReturnInstruction(statement);
      // throw exception completion, call finally block and rethrow
      CallInstruction throwExceptionCompletion = new CallInstruction(0, 0);
      finallyBlockSubroutine.addCall(throwExceptionCompletion);
      myCurrentFlow.addInstruction(throwExceptionCompletion);
      addElementOffsetLater(finallyBlock, true);
      final GoToInstruction gotoUncheckedRethrow = new GoToInstruction(0);
      myCurrentFlow.addInstruction(gotoUncheckedRethrow);
      addElementOffsetLater(finallyBlock, false);

      finallyBlock.accept(this);
      final int procStart = myCurrentFlow.getStartOffset(finallyBlock);
      final int procEnd = myCurrentFlow.getEndOffset(finallyBlock);
      for (CallInstruction callInstruction : finallyBlockSubroutine.getCalls()) {
        callInstruction.procBegin = procStart;
        callInstruction.procEnd = procEnd;
      }

      // generate return instructions
      // first three return instructions are for normal completion, return statement call completion and unchecked exception throwing completion resp.

      // normal completion
      myCurrentFlow.addInstruction(new ReturnInstruction(0, normalCompletion));

      // return statement call completion
      myCurrentFlow.addInstruction(new ReturnInstruction(procStart - 3, returnCompletion));

      // unchecked exception throwing completion
      myCurrentFlow.addInstruction(new ReturnInstruction(procStart - 1, throwExceptionCompletion));

      // checked exception throwing completion; need to dispatch to the correct catch clause
      final List<PsiElement> unhandledExceptionCatchBlocks = finallyBlockToUnhandledExceptions.remove(finallyBlock);
      for (int i = 0; unhandledExceptionCatchBlocks != null && i < unhandledExceptionCatchBlocks.size(); i++) {
        ProgressManager.checkCanceled();
        PsiElement catchBlock = unhandledExceptionCatchBlocks.get(i);

        final ReturnInstruction returnInstruction = new ReturnInstruction(0, throwExceptionCompletion);
        returnInstruction.setRethrowFromFinally();
        myCurrentFlow.addInstruction(returnInstruction);
        if (catchBlock == null) {
          // dispatch to rethrowing exception code
          returnInstruction.offset = procStart - 1;
        }
        else {
          // dispatch to catch clause
          returnInstruction.offset--; // -1 for catch block init parameter instruction
          addElementOffsetLater(catchBlock, true);
        }
      }

      // here generated rethrowing code for unchecked exceptions
      gotoUncheckedRethrow.offset = myCurrentFlow.getSize();
      generateUncheckedExceptionJumps(statement, false);
      // just in case
      myCurrentFlow.addInstruction(new ThrowToInstruction(0));
      addElementOffsetLater(myCodeFragment, false);
    }

    finishElement(statement);
  }
@Override
  public void visitTryStatement(@NotNull PsiTryStatement statement)