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