001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.codegen.inline;
018    
019    import com.google.common.base.Objects;
020    import com.google.common.collect.Lists;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.annotations.ReadOnly;
024    import org.jetbrains.annotations.TestOnly;
025    import org.jetbrains.kotlin.codegen.optimization.common.UtilKt;
026    import org.jetbrains.org.objectweb.asm.Label;
027    import org.jetbrains.org.objectweb.asm.Opcodes;
028    import org.jetbrains.org.objectweb.asm.Type;
029    import org.jetbrains.org.objectweb.asm.tree.*;
030    import org.jetbrains.org.objectweb.asm.util.Textifier;
031    import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
032    
033    import java.io.PrintWriter;
034    import java.io.StringWriter;
035    import java.util.*;
036    
037    import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.*;
038    
039    public class InternalFinallyBlockInliner extends CoveringTryCatchNodeProcessor {
040    
041        private static class FinallyBlockInfo {
042    
043            final AbstractInsnNode startIns;
044    
045            final AbstractInsnNode endInsExclusive;
046    
047            private FinallyBlockInfo(
048                    @NotNull AbstractInsnNode inclusiveStart,
049                    @NotNull AbstractInsnNode exclusiveEnd
050            ) {
051                startIns = inclusiveStart;
052                endInsExclusive = exclusiveEnd;
053            }
054    
055            public boolean isEmpty() {
056                if (!(startIns instanceof LabelNode)) {
057                    return false;
058    
059                }
060                AbstractInsnNode end = endInsExclusive;
061                while (end != startIns && end instanceof LabelNode) {
062                    end = end.getPrevious();
063                }
064                return startIns == end;
065            }
066        }
067    
068        public static void processInlineFunFinallyBlocks(@NotNull MethodNode inlineFun, int lambdaTryCatchBlockNodes, int finallyParamOffset) {
069            int index = 0;
070            List<TryCatchBlockNodeInfo> inlineFunTryBlockInfo = new ArrayList<TryCatchBlockNodeInfo>();
071            for (TryCatchBlockNode block : inlineFun.tryCatchBlocks) {
072                inlineFunTryBlockInfo.add(new TryCatchBlockNodeInfo(block, index++ < lambdaTryCatchBlockNodes));
073            }
074    
075            List<LocalVarNodeWrapper> localVars = new ArrayList<LocalVarNodeWrapper>();
076            for (LocalVariableNode var : inlineFun.localVariables) {
077                localVars.add(new LocalVarNodeWrapper(var));
078            }
079    
080            if (hasFinallyBlocks(inlineFunTryBlockInfo)) {
081                new InternalFinallyBlockInliner(inlineFun, inlineFunTryBlockInfo, localVars, finallyParamOffset).processInlineFunFinallyBlocks();
082            }
083        }
084    
085        @NotNull
086        private final MethodNode inlineFun;
087    
088    
089        //lambdaTryCatchBlockNodes is number of TryCatchBlockNodes that was inlined with lambdas into function
090        //due to code generation specific they placed before function TryCatchBlockNodes
091        private InternalFinallyBlockInliner(@NotNull MethodNode inlineFun,
092                @NotNull List<TryCatchBlockNodeInfo> inlineFunTryBlockInfo,
093                @NotNull List<LocalVarNodeWrapper> localVariableInfo,
094                int finallyParamOffset) {
095            super(finallyParamOffset);
096            this.inlineFun = inlineFun;
097            for (TryCatchBlockNodeInfo block : inlineFunTryBlockInfo) {
098                getTryBlocksMetaInfo().addNewInterval(block);
099            }
100    
101            for (LocalVarNodeWrapper wrapper : localVariableInfo) {
102                getLocalVarsMetaInfo().addNewInterval(wrapper);
103            }
104        }
105    
106        private int initAndGetVarIndexForNonLocalReturnValue() {
107            MaxLocalsCalculator tempCalcNode = new MaxLocalsCalculator(
108                    InlineCodegenUtil.API,
109                    inlineFun.access, inlineFun.desc, null
110            );
111            inlineFun.accept(tempCalcNode);
112            return tempCalcNode.getMaxLocals();
113        }
114    
115        private void processInlineFunFinallyBlocks() {
116            int nextTempNonLocalVarIndex = initAndGetVarIndexForNonLocalReturnValue();
117    
118            InsnList instructions = inlineFun.instructions;
119    
120            //As we do finally block code search after non-local return instruction
121            // we should be sure that all others non-local returns already processed in this finally block.
122            // So we do instruction processing in reverse order!
123            AbstractInsnNode curIns = instructions.getLast();
124            while (curIns != null) {
125                processInstruction(curIns, false);
126    
127                //At this point only global return is possible, local one already substituted with: goto endLabel
128                if (!InlineCodegenUtil.isReturnOpcode(curIns.getOpcode()) ||
129                    !InlineCodegenUtil.isMarkedReturn(curIns)) {
130                    curIns = curIns.getPrevious();
131                    continue;
132                }
133    
134                List<TryCatchBlockNodeInfo> currentCoveringNodesFromInnermost =
135                        sortTryCatchBlocks(new ArrayList<TryCatchBlockNodeInfo>(getTryBlocksMetaInfo().getCurrentIntervals()));
136                checkCoveringBlocksInvariant(Lists.reverse(currentCoveringNodesFromInnermost));
137    
138                if (currentCoveringNodesFromInnermost.isEmpty() ||
139                    currentCoveringNodesFromInnermost.get(currentCoveringNodesFromInnermost.size() - 1).getOnlyCopyNotProcess()) {
140                    curIns = curIns.getPrevious();
141                    continue;
142                }
143    
144                AbstractInsnNode markedReturn = curIns;
145                AbstractInsnNode instrInsertFinallyBefore = markedReturn.getPrevious();
146                AbstractInsnNode nextPrev = instrInsertFinallyBefore.getPrevious();
147                assert markedReturn.getNext() instanceof LabelNode : "Label should be occurred after non-local return";
148                LabelNode newFinallyEnd = (LabelNode) markedReturn.getNext();
149                Type nonLocalReturnType = InlineCodegenUtil.getReturnType(markedReturn.getOpcode());
150    
151                //Generally there could be several tryCatch blocks (group) on one code interval (same start and end labels, but maybe different handlers) -
152                // all of them refer to one try/*catches*/finally or try/catches.
153                // Each group that corresponds to try/*catches*/finally contains tryCatch block with default handler.
154                // For each such group we should insert corresponding finally before non-local return.
155                // So we split all try blocks on current instructions to groups and process them independently
156                List<TryBlockCluster<TryCatchBlockNodeInfo>> clustersFromInnermost = TryBlockClusteringKt.doClustering(
157                        currentCoveringNodesFromInnermost);
158                Iterator<TryBlockCluster<TryCatchBlockNodeInfo>> tryCatchBlockIterator = clustersFromInnermost.iterator();
159    
160                checkClusterInvariant(clustersFromInnermost);
161    
162                int originalDepthIndex = 0;
163    
164                while (tryCatchBlockIterator.hasNext()) {
165                    TryBlockCluster<TryCatchBlockNodeInfo> clusterToFindFinally = tryCatchBlockIterator.next();
166                    List<TryCatchBlockNodeInfo> clusterBlocks = clusterToFindFinally.getBlocks();
167                    TryCatchBlockNodeInfo nodeWithDefaultHandlerIfExists = clusterBlocks.get(clusterBlocks.size() - 1);
168    
169                    FinallyBlockInfo finallyInfo = findFinallyBlockBody(nodeWithDefaultHandlerIfExists, getTryBlocksMetaInfo().getAllIntervals());
170                    if (finallyInfo == null) continue;
171    
172                    if (nodeWithDefaultHandlerIfExists.getOnlyCopyNotProcess()) {
173                        //lambdas finally generated before non-local return instruction,
174                        //so it's a gap in try/catch handlers
175                        throw new RuntimeException("Lambda try blocks should be skipped");
176                    }
177    
178                    originalDepthIndex++;
179    
180                    instructions.resetLabels();
181    
182                    List<TryCatchBlockNodePosition> tryCatchBlockInlinedInFinally = findTryCatchBlocksInlinedInFinally(finallyInfo);
183    
184                    //Creating temp node for finally block copy with some additional instruction
185                    MethodNode finallyBlockCopy = createEmptyMethodNode();
186                    Label newFinallyStart = new Label();
187                    Label insertedBlockEnd = new Label();
188    
189                    boolean generateAloadAstore = nonLocalReturnType != Type.VOID_TYPE && !finallyInfo.isEmpty();
190                    if (generateAloadAstore) {
191                        finallyBlockCopy.visitVarInsn(nonLocalReturnType.getOpcode(Opcodes.ISTORE), nextTempNonLocalVarIndex);
192                    }
193                    finallyBlockCopy.visitLabel(newFinallyStart);
194    
195                    //Keep some information about label nodes, we need it to understand whether it's jump inside finally block or outside
196                    // in first case we do call VISIT on instruction otherwise recreating jump instruction (see below)
197                    Set<LabelNode> labelsInsideFinally = rememberOriginalLabelNodes(finallyInfo);
198                    //Writing finally block body to temporary node
199                    AbstractInsnNode currentIns = finallyInfo.startIns;
200                    while (currentIns != finallyInfo.endInsExclusive) {
201                        boolean isInsOrJumpInsideFinally =
202                                !(currentIns instanceof JumpInsnNode) ||
203                                labelsInsideFinally.contains(((JumpInsnNode) currentIns).label);
204    
205                        copyInstruction(finallyBlockCopy, currentIns, isInsOrJumpInsideFinally, originalDepthIndex);
206    
207                        currentIns = currentIns.getNext();
208                    }
209    
210                    if (generateAloadAstore) {
211                        finallyBlockCopy.visitVarInsn(nonLocalReturnType.getOpcode(Opcodes.ILOAD), nextTempNonLocalVarIndex);
212                        nextTempNonLocalVarIndex += nonLocalReturnType.getSize(); //TODO: do more wise indexing
213                    }
214    
215                    finallyBlockCopy.visitLabel(insertedBlockEnd);
216    
217                    //Copying finally body before non-local return instruction
218                    InlineCodegenUtil.insertNodeBefore(finallyBlockCopy, inlineFun, instrInsertFinallyBefore);
219    
220                    updateExceptionTable(clusterBlocks, newFinallyStart, newFinallyEnd,
221                                         tryCatchBlockInlinedInFinally, labelsInsideFinally, (LabelNode) insertedBlockEnd.info);
222                }
223    
224                //skip just inserted finally
225                curIns = markedReturn.getPrevious();
226                while (curIns != null && curIns != nextPrev) {
227                    processInstruction(curIns, false);
228                    curIns = curIns.getPrevious();
229                }
230    
231                //finally block inserted so we need split update localVarTable in lambda
232                if (instrInsertFinallyBefore.getPrevious() != nextPrev && curIns != null) {
233                    LabelNode startNode = new LabelNode();
234                    LabelNode endNode = new LabelNode();
235                    instructions.insert(curIns, startNode);
236                    //TODO: note that on return expression we have no variables
237                    instructions.insert(markedReturn, endNode);
238                    getLocalVarsMetaInfo().splitCurrentIntervals(new SimpleInterval(startNode, endNode), true);
239                }
240            }
241    
242            substituteTryBlockNodes(inlineFun);
243            substituteLocalVarTable(inlineFun);
244        }
245    
246        private static void copyInstruction(
247                @NotNull MethodNode finallyBlockCopy,
248                @NotNull AbstractInsnNode currentIns,
249                boolean isInsOrJumpInsideFinally,
250                int depthShift
251        ) {
252            if (isInsOrJumpInsideFinally) {
253                if (InlineCodegenUtil.isFinallyMarker(currentIns.getNext())) {
254                    Integer constant = getConstant(currentIns);
255                    finallyBlockCopy.visitLdcInsn(constant + depthShift);
256                } else {
257                    currentIns.accept(finallyBlockCopy); //VISIT
258                }
259            }
260            else {
261                //keep original jump: add currentIns clone
262                finallyBlockCopy.instructions.add(new JumpInsnNode(currentIns.getOpcode(), ((JumpInsnNode) currentIns).label));
263            }
264        }
265    
266        private static void checkCoveringBlocksInvariant(@ReadOnly @NotNull List<TryCatchBlockNodeInfo> currentCoveringNodesFromOuterMost) {
267            boolean isWasOnlyLocal = false;
268            for (TryCatchBlockNodeInfo info : currentCoveringNodesFromOuterMost) {
269                assert !isWasOnlyLocal || info.getOnlyCopyNotProcess() : "There are some problems with try-catch structure";
270                isWasOnlyLocal = info.getOnlyCopyNotProcess();
271            }
272        }
273    
274        private static void checkClusterInvariant(List<TryBlockCluster<TryCatchBlockNodeInfo>> clusters) {
275            boolean isWasOnlyLocal;
276            isWasOnlyLocal = false;
277            for (TryBlockCluster<TryCatchBlockNodeInfo> cluster : Lists.reverse(clusters)) {
278                TryCatchBlockNodeInfo info = cluster.getBlocks().get(0);
279                assert !isWasOnlyLocal || info.getOnlyCopyNotProcess();
280                if (info.getOnlyCopyNotProcess()) {
281                    isWasOnlyLocal = true;
282                }
283            }
284        }
285    
286        @NotNull
287        private static Set<LabelNode> rememberOriginalLabelNodes(@NotNull FinallyBlockInfo finallyInfo) {
288            Set<LabelNode> labelsInsideFinally = new HashSet<LabelNode>();
289            for (AbstractInsnNode currentIns = finallyInfo.startIns; currentIns != finallyInfo.endInsExclusive; currentIns = currentIns.getNext()) {
290                if (currentIns instanceof LabelNode) {
291                    labelsInsideFinally.add((LabelNode) currentIns);
292                }
293            }
294            return labelsInsideFinally;
295        }
296    
297        private void updateExceptionTable(
298                @NotNull List<TryCatchBlockNodeInfo> updatingClusterBlocks,
299                @NotNull Label newFinallyStart,
300                @NotNull LabelNode newFinallyEnd,
301                @NotNull List<TryCatchBlockNodePosition> tryCatchBlockPresentInFinally,
302                @NotNull Set<LabelNode> labelsInsideFinally,
303                @NotNull LabelNode insertedBlockEnd
304        ) {
305    
306            //copy tryCatchFinallies that totally in finally block
307            List<TryBlockCluster<TryCatchBlockNodePosition>> clusters = TryBlockClusteringKt.doClustering(tryCatchBlockPresentInFinally);
308            Map<LabelNode, TryBlockCluster<TryCatchBlockNodePosition>> handler2Cluster = new HashMap<LabelNode, TryBlockCluster<TryCatchBlockNodePosition>>();
309    
310            IntervalMetaInfo<TryCatchBlockNodeInfo> tryBlocksMetaInfo = getTryBlocksMetaInfo();
311            for (TryBlockCluster<TryCatchBlockNodePosition> cluster : clusters) {
312                List<TryCatchBlockNodePosition> clusterBlocks = cluster.getBlocks();
313                TryCatchBlockNodePosition block0 = clusterBlocks.get(0);
314                TryCatchPosition clusterPosition = block0.getPosition();
315                if (clusterPosition == TryCatchPosition.INNER) {
316                    for (TryCatchBlockNodePosition position : clusterBlocks) {
317                        assert clusterPosition == position.getPosition() : "Wrong inner tryCatchBlock structure";
318                        TryCatchBlockNode tryCatchBlockNode = position.getNodeInfo().getNode();
319    
320                        assert inlineFun.instructions.indexOf(tryCatchBlockNode.start) <= inlineFun.instructions.indexOf(tryCatchBlockNode.end);
321    
322                        TryCatchBlockNode additionalTryCatchBlock =
323                                new TryCatchBlockNode((LabelNode) tryCatchBlockNode.start.getLabel().info,
324                                                      (LabelNode) tryCatchBlockNode.end.getLabel().info,
325                                                      getNewOrOldLabel(tryCatchBlockNode.handler, labelsInsideFinally),
326                                                      tryCatchBlockNode.type);
327    
328    
329                        assert inlineFun.instructions.indexOf(additionalTryCatchBlock.start) <= inlineFun.instructions.indexOf(additionalTryCatchBlock.end);
330    
331                        tryBlocksMetaInfo.addNewInterval(new TryCatchBlockNodeInfo(additionalTryCatchBlock, true));
332                    }
333                }
334                else if (clusterPosition == TryCatchPosition.END) {
335                    TryCatchBlockNodePosition defaultHandler = cluster.getDefaultHandler();
336                    assert defaultHandler != null : "Default handler should be present";
337                    handler2Cluster.put(defaultHandler.getHandler(), cluster);
338                }
339                else {
340                    assert clusterPosition == TryCatchPosition.START;
341                    TryCatchBlockNodePosition defaultHandler = cluster.getDefaultHandler();
342                    assert defaultHandler != null : "Default handler should be present";
343                    TryBlockCluster<TryCatchBlockNodePosition> endCluster = handler2Cluster.remove(defaultHandler.getHandler());
344                    assert endCluster != null : "Could find start cluster for  " + clusterPosition;
345    
346                    //at this point only external finallies could occurs
347                    //they don't collision with updatingClusterBlocks, but may with external ones on next updateExceptionTable invocation
348                    Iterator<TryCatchBlockNodePosition> startBlockPositions = clusterBlocks.iterator();
349                    for (TryCatchBlockNodePosition endBlockPosition : endCluster.getBlocks()) {
350                        TryCatchBlockNodeInfo startNode = startBlockPositions.next().getNodeInfo();
351                        TryCatchBlockNodeInfo endNode = endBlockPosition.getNodeInfo();
352    
353                        assert Objects.equal(startNode.getType(), endNode.getType()) : "Different handler types : " + startNode.getType() + " " + endNode.getType();
354    
355                        getTryBlocksMetaInfo()
356                                .split(endNode, new SimpleInterval((LabelNode) endNode.getNode().end.getLabel().info,
357                                                                   (LabelNode) startNode.getStartLabel().getLabel().info), false);
358                    }
359                }
360            }
361    
362            if (handler2Cluster.size() == 1) {
363                TryBlockCluster<TryCatchBlockNodePosition> singleCluster = handler2Cluster.values().iterator().next();
364                if (singleCluster.getBlocks().get(0).getPosition() == TryCatchPosition.END) {
365                    //Pair that starts on default handler don't added to tryCatchBlockPresentInFinally cause it's out of finally block
366                    //TODO rewrite to clusters
367                    for (TryCatchBlockNodePosition endBlockPosition : singleCluster.getBlocks()) {
368                        TryCatchBlockNodeInfo endNode = endBlockPosition.getNodeInfo();
369                        getTryBlocksMetaInfo()
370                                .split(endNode, new SimpleInterval((LabelNode) endNode.getNode().end.getLabel().info,
371                                                                   (LabelNode) insertedBlockEnd.getLabel().info), false);
372                    }
373    
374                    handler2Cluster.clear();
375                }
376            }
377            assert handler2Cluster.isEmpty() : "Unmatched clusters " + handler2Cluster.size();
378    
379            SimpleInterval splitBy = new SimpleInterval((LabelNode) newFinallyStart.info, newFinallyEnd);
380            // Inserted finally shouldn't be handled by corresponding catches,
381            // so we should split original interval by inserted finally one
382            for (TryCatchBlockNodeInfo block : updatingClusterBlocks) {
383                //update exception mapping
384                SplitPair<TryCatchBlockNodeInfo> split = tryBlocksMetaInfo.splitAndRemoveInterval(block, splitBy, false);
385                checkFinally(split.getNewPart());
386                checkFinally(split.getPatchedPart());
387                //block patched in split method
388                assert !block.isEmpty() : "Finally block should be non-empty";
389                //TODO add assert
390            }
391    
392            sortTryCatchBlocks(tryBlocksMetaInfo.getAllIntervals());
393        }
394    
395        private static LabelNode getNewOrOldLabel(LabelNode oldHandler, @NotNull Set<LabelNode> labelsInsideFinally) {
396            if (labelsInsideFinally.contains(oldHandler)) {
397                return (LabelNode) oldHandler.getLabel().info;
398            }
399    
400            return oldHandler;
401        }
402    
403        private static boolean hasFinallyBlocks(List<TryCatchBlockNodeInfo> inlineFunTryBlockInfo) {
404            for (TryCatchBlockNodeInfo block : inlineFunTryBlockInfo) {
405                if (!block.getOnlyCopyNotProcess() && block.getNode().type == null) {
406                    return true;
407                }
408            }
409            return false;
410        }
411    
412        //As described above all tryCatch group that have finally block also should contains tryCatchBlockNode with default handler.
413        //So we assume that instructions between end of tryCatchBlock and start of next tryCatchBlock with same default handler is required finally body.
414        //There is at least two tryCatchBlockNodes in list cause there is always tryCatchBlockNode on first instruction of default handler:
415        // "ASTORE defaultHandlerExceptionIndex" (handles itself, as does java).
416        @Nullable
417        private FinallyBlockInfo findFinallyBlockBody(
418                @NotNull TryCatchBlockNodeInfo tryCatchBlock,
419                @ReadOnly @NotNull List<TryCatchBlockNodeInfo> tryCatchBlocks
420        ) {
421            List<TryCatchBlockNodeInfo> sameDefaultHandler = new ArrayList<TryCatchBlockNodeInfo>();
422            LabelNode defaultHandler = null;
423            boolean afterStartBlock = false;
424            for (TryCatchBlockNodeInfo block : tryCatchBlocks) {
425                if (tryCatchBlock == block) {
426                    afterStartBlock = true;
427                }
428    
429                if (afterStartBlock) {
430                    if (block.getNode().type == null && (firstLabelInChain(tryCatchBlock.getNode().start) == firstLabelInChain(block.getNode().start) &&
431                                                         firstLabelInChain(tryCatchBlock.getNode().end) == firstLabelInChain(block.getNode().end)
432                                                         || defaultHandler == firstLabelInChain(block.getNode().handler))) {
433                        sameDefaultHandler.add(block); //first is tryCatchBlock if no catch clauses
434                        if (defaultHandler == null) {
435                            defaultHandler = firstLabelInChain(block.getNode().handler);
436                        }
437                    }
438                }
439            }
440    
441            if (sameDefaultHandler.isEmpty()) {
442                //there is no finally block
443                //it always should be present in default handler
444                return null;
445            }
446    
447            TryCatchBlockNodeInfo nextIntervalWithSameDefaultHandler = sameDefaultHandler.get(1);
448            AbstractInsnNode startFinallyChain = tryCatchBlock.getNode().end;
449            AbstractInsnNode meaningful = getNextMeaningful(startFinallyChain);
450            assert meaningful != null : "Can't find meaningful in finally block" + startFinallyChain;
451    
452            Integer finallyDepth = getConstant(meaningful);
453            AbstractInsnNode endFinallyChainExclusive = nextIntervalWithSameDefaultHandler.getNode().start;
454            AbstractInsnNode current = meaningful.getNext();
455            while (endFinallyChainExclusive != current) {
456                current = current.getNext();
457                if (InlineCodegenUtil.isFinallyEnd(current)) {
458                    Integer currentDepth = getConstant(current.getPrevious());
459                    if (currentDepth.equals(finallyDepth)) {
460                        endFinallyChainExclusive = current.getNext();
461                        break;
462                    }
463                }
464            }
465    
466            FinallyBlockInfo finallyInfo = new FinallyBlockInfo(startFinallyChain.getNext(), endFinallyChainExclusive);
467    
468            checkFinally(finallyInfo);
469    
470            return finallyInfo;
471        }
472    
473        private void checkFinally(FinallyBlockInfo finallyInfo) {
474            checkFinally(finallyInfo.startIns, finallyInfo.endInsExclusive);
475        }
476    
477        private void checkFinally(IntervalWithHandler intervalWithHandler) {
478            checkFinally(intervalWithHandler.getStartLabel(), intervalWithHandler.getEndLabel());
479        }
480    
481        private void checkFinally(AbstractInsnNode startIns, AbstractInsnNode endInsExclusive) {
482            if (inlineFun.instructions.indexOf(startIns) >= inlineFun.instructions.indexOf(endInsExclusive)) {
483                throw new AssertionError("Inconsistent finally: block end occurs before start " + traceInterval(endInsExclusive, startIns));
484            }
485        }
486    
487        @NotNull
488        private List<TryCatchBlockNodePosition> findTryCatchBlocksInlinedInFinally(@NotNull FinallyBlockInfo finallyInfo) {
489            List<TryCatchBlockNodePosition> result = new ArrayList<TryCatchBlockNodePosition>();
490            Map<TryCatchBlockNodeInfo, TryCatchBlockNodePosition> processedBlocks = new HashMap<TryCatchBlockNodeInfo, TryCatchBlockNodePosition>();
491    
492            for (AbstractInsnNode curInstr = finallyInfo.startIns; curInstr != finallyInfo.endInsExclusive; curInstr = curInstr.getNext()) {
493                if (!(curInstr instanceof LabelNode)) continue;
494    
495                LabelNode curLabel = (LabelNode) curInstr;
496                List<TryCatchBlockNodeInfo> startedTryBlocks = getStartNodes(curLabel);
497                for (TryCatchBlockNodeInfo block : startedTryBlocks) {
498                    assert !processedBlocks.containsKey(block) : "Try catch block already processed before start label!!! " + block;
499                    TryCatchBlockNodePosition info = new TryCatchBlockNodePosition(block, TryCatchPosition.START);
500                    processedBlocks.put(block, info);
501                    result.add(info);
502                }
503    
504                List<TryCatchBlockNodeInfo> endedTryBlocks = getEndNodes(curLabel);
505    
506                for (TryCatchBlockNodeInfo block : endedTryBlocks) {
507                    TryCatchBlockNodePosition info = processedBlocks.get(block);
508                    if (info != null) {
509                        assert info.getPosition() == TryCatchPosition.START;
510                        info.setPosition(TryCatchPosition.INNER);
511                    }
512                    else {
513                        info = new TryCatchBlockNodePosition(block, TryCatchPosition.END);
514                        processedBlocks.put(block, info);
515                        result.add(info);
516                    }
517                }
518            }
519            return result;
520        }
521    
522        @Nullable
523        private static AbstractInsnNode getNextMeaningful(@NotNull AbstractInsnNode node) {
524            AbstractInsnNode result = node.getNext();
525            while (result != null && !UtilKt.isMeaningful(result)) {
526                result = result.getNext();
527            }
528            return result;
529        }
530    
531        @Override
532        public int instructionIndex(@NotNull AbstractInsnNode inst) {
533            return inlineFun.instructions.indexOf(inst);
534        }
535    
536        private static String traceInterval(AbstractInsnNode startNode, AbstractInsnNode stopNode) {
537            Textifier p = new Textifier();
538            TraceMethodVisitor visitor = new TraceMethodVisitor(p);
539            while (startNode != stopNode) {
540                startNode.accept(visitor);
541                startNode = startNode.getNext();
542            }
543            startNode.accept(visitor);
544            StringWriter out = new StringWriter();
545            p.print(new PrintWriter(out));
546            return out.toString();
547        }
548    
549        @SuppressWarnings({"UnusedDeclaration", "UseOfSystemOutOrSystemErr"})
550        @TestOnly
551        private void flushCurrentState(@NotNull AbstractInsnNode curNonLocal) {
552            substituteTryBlockNodes(inlineFun);
553            System.out.println("Will process instruction at : " + inlineFun.instructions.indexOf(curNonLocal) + " " + curNonLocal.toString());
554            String text = getNodeText(inlineFun);
555            System.out.println(text);
556        }
557    
558    }