public static boolean hasObservableThrowExitPoints(final @NotNull ControlFlow flow,
                                                     final int flowStart,
                                                     final int flowEnd,
                                                     PsiElement @NotNull [] elements,
                                                     @NotNull PsiElement enclosingCodeFragment) {
    final List<Instruction> instructions = flow.getInstructions();
    class Worker {
      private @NotNull Map<PsiVariable, IntList> getWritesOffsets() {
        final Map<PsiVariable, IntList> writeOffsets = new HashMap<>();
        for (int i = flowStart; i < flowEnd; i++) {
          Instruction instruction = instructions.get(i);
          if (instruction instanceof WriteVariableInstruction) {
            final PsiVariable variable = ((WriteVariableInstruction)instruction).variable;
            if (variable instanceof PsiLocalVariable || variable instanceof PsiParameter) {
              IntList offsets = writeOffsets.get(variable);
              if (offsets == null) writeOffsets.put(variable, offsets = new IntArrayList());
              offsets.add(i);
            }
          }
        }
        LOG.debug("writeOffsets:", writeOffsets);
        return writeOffsets;
      }

      private @NotNull Map<PsiVariable, IntList> getVisibleReadsOffsets(@NotNull Map<PsiVariable, IntList> writeOffsets, @NotNull PsiCodeBlock tryBlock) {
        final Map<PsiVariable, IntList> visibleReadOffsets = new HashMap<>();
        for (PsiVariable variable : writeOffsets.keySet()) {
          if (!PsiTreeUtil.isAncestor(tryBlock, variable, true)) {
            visibleReadOffsets.put(variable, new IntArrayList());
          }
        }
        if (visibleReadOffsets.isEmpty()) return visibleReadOffsets;

        for (int i = 0; i < instructions.size(); i++) {
          final Instruction instruction = instructions.get(i);
          if (instruction instanceof ReadVariableInstruction) {
            final PsiVariable variable = ((ReadVariableInstruction)instruction).variable;
            final IntList readOffsets = visibleReadOffsets.get(variable);
            if (readOffsets != null) {
              readOffsets.add(i);
            }
          }
        }
        LOG.debug("visibleReadOffsets:", visibleReadOffsets);
        return visibleReadOffsets;
      }

      private @NotNull Map<PsiVariable, Set<PsiElement>> getReachableAfterWrite(@NotNull Map<PsiVariable, IntList> writeOffsets,
                                                                                @NotNull Map<PsiVariable, IntList> visibleReadOffsets) {
        final Map<PsiVariable, Set<PsiElement>> afterWrite = new HashMap<>();
        final IntFunction<BitSet> calculator = getReachableInstructionsCalculator();
        for (PsiVariable variable : visibleReadOffsets.keySet()) {
          final BitSet collectedOffsets = new BitSet(flowEnd);
          for (int writeOffset : writeOffsets.get(variable).toIntArray()) {
            LOG.assertTrue(writeOffset >= flowStart, "writeOffset");
            final BitSet reachableOffsets = calculator.apply(writeOffset);
            //skip current assignment
            reachableOffsets.set(writeOffset, false);
            if (writeOffset + 1 < flow.getSize()) {
              final PsiElement nextOffsetElement = flow.getElement(writeOffset + 1);
              if (nextOffsetElement instanceof PsiExpressionStatement) {
                if (flow.getElement(writeOffset) == ((PsiExpressionStatement)nextOffsetElement).getExpression()) {
                  reachableOffsets.set(writeOffset + 1, false);
                }
              }
            }
            collectedOffsets.or(reachableOffsets);
          }
          Set<PsiElement> throwSources = afterWrite.getOrDefault(variable, new HashSet<>());
          for (int i = flowStart; i < flowEnd; i++) {
            if (collectedOffsets.get(i)) {
              throwSources.add(flow.getElement(i));
            }
          }
          final List<PsiElement> subordinates = new ArrayList<>();
          for (PsiElement element : throwSources) {
            if (throwSources.contains(element.getParent())) {
              subordinates.add(element);
            }
          }
          subordinates.forEach(throwSources::remove);
          if (throwSources.isEmpty()) {
            afterWrite.remove(variable);
          } else {
            afterWrite.put(variable, throwSources);
          }
        }
        LOG.debug("afterWrite:", afterWrite);
        return afterWrite;
      }

      private @NotNull IntList getCatchOrFinallyOffsets(@NotNull List<? extends PsiTryStatement> tryStatements, @NotNull List<? extends PsiClassType> thrownExceptions) {
        final IntList catchOrFinallyOffsets = new IntArrayList();
        for (PsiTryStatement tryStatement : tryStatements) {
          final PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock();
          if (finallyBlock != null) {
            int offset = flow.getStartOffset(finallyBlock);
            if (offset >= 0) {
              catchOrFinallyOffsets.add(offset - 2); // -2 is an adjustment for rethrow-after-finally
            }
          }
          for (PsiCatchSection catchSection : tryStatement.getCatchSections()) {
            final PsiCodeBlock catchBlock = catchSection.getCatchBlock();
            final PsiParameter parameter = catchSection.getParameter();
            if (catchBlock != null && parameter != null) {
              for (PsiClassType throwType : thrownExceptions) {
                if (isCaughtExceptionType(throwType, parameter.getType())) {
                  int offset = flow.getStartOffset(catchBlock);
                  if (offset >= 0) {
                    catchOrFinallyOffsets.add(offset - 1); // -1 is an adjustment for catch block initialization
                  }
                }
              }
            }
          }
        }
        return catchOrFinallyOffsets;
      }

      private boolean isAnyReadOffsetReachableFrom(@Nullable IntList readOffsets, @NotNull IntList fromOffsets) {
        if (readOffsets != null && !readOffsets.isEmpty()) {
          final int[] readOffsetsArray = readOffsets.toIntArray();
          for (int j = 0; j < fromOffsets.size(); j++) {
            int fromOffset = fromOffsets.getInt(j);
            if (areInstructionsReachable(flow, readOffsetsArray, fromOffset)) {
              LOG.debug("reachableFromOffset:", fromOffset);
              return true;
            }
          }
        }
        return false;
      }

      private @NotNull IntFunction<BitSet> getReachableInstructionsCalculator() {
        final ControlFlowGraph graph = new ControlFlowGraph(flow.getSize()) {
          @Override
          void addArc(int offset, int nextOffset) {
            nextOffset = promoteThroughGotoChain(flow, nextOffset);
            if (nextOffset >= flowStart && nextOffset < flowEnd) {
              super.addArc(offset, nextOffset);
            }
          }
        };
        graph.buildFrom(flow);

        return startOffset -> {
          BitSet visitedOffsets = new BitSet(flowEnd);
          graph.depthFirstSearch(startOffset, visitedOffsets);
          return visitedOffsets;
        };
      }
    }

    final Worker worker = new Worker();
    final Map<PsiVariable, IntList> writeOffsets = worker.getWritesOffsets();
    if (writeOffsets.isEmpty()) return false;

    final PsiElement commonParent = elements.length != 1 ? PsiTreeUtil.findCommonParent(elements) : elements[0].getParent();
    final List<PsiTryStatement> tryStatements = collectTryStatementStack(commonParent, enclosingCodeFragment);
    if (tryStatements.isEmpty()) return false;
    final PsiCodeBlock tryBlock = tryStatements.get(0).getTryBlock();
    if (tryBlock == null) return false;

    final Map<PsiVariable, IntList> visibleReadOffsets = worker.getVisibleReadsOffsets(writeOffsets, tryBlock);
    if (visibleReadOffsets.isEmpty()) return false;

    final Map<PsiVariable, Set<PsiElement>> afterWrite = worker.getReachableAfterWrite(writeOffsets, visibleReadOffsets);
    if (afterWrite.isEmpty()) return false;

    final PsiClassType runtimeException = PsiType.getJavaLangRuntimeException(tryBlock.getManager(), tryBlock.getResolveScope());
    final boolean runtimeExceptionIsCaught = ContainerUtil.
      exists(tryStatements, (tryStatement) -> isExceptionCaught(tryStatement, runtimeException));

    for (Map.Entry<PsiVariable, Set<PsiElement>> entry : afterWrite.entrySet()) {
      final PsiVariable variable = entry.getKey();
      final PsiElement[] psiElements = entry.getValue().toArray(PsiElement.EMPTY_ARRAY);
      final List<PsiClassType> thrownExceptions = ExceptionUtil.getThrownExceptions(psiElements);
      if (runtimeExceptionIsCaught) {
        thrownExceptions.add(runtimeException);
      }
      if (!thrownExceptions.isEmpty()) {
        final IntList catchOrFinallyOffsets = worker.getCatchOrFinallyOffsets(tryStatements, thrownExceptions);
        if (worker.isAnyReadOffsetReachableFrom(visibleReadOffsets.get(variable), catchOrFinallyOffsets)) {
          return true;
        }
      }
    }
    return false;
  }
public static boolean hasObservableThrowExitPoints(final @NotNull ControlFlow flow,
                                                     final int flowStart,
                                                     final int flowEnd,
                                                     PsiElement @NotNull [] elements,
                                                     @NotNull PsiElement enclosingCodeFragment)