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