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.cfg.pseudocode;
018    
019    import com.google.common.collect.*;
020    import com.intellij.util.containers.BidirectionalMap;
021    import kotlin.collections.MapsKt;
022    import kotlin.jvm.functions.Function0;
023    import kotlin.jvm.functions.Function1;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.kotlin.cfg.Label;
027    import org.jetbrains.kotlin.cfg.pseudocode.instructions.*;
028    import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction;
029    import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind;
030    import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MergeInstruction;
031    import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.AbstractJumpInstruction;
032    import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ConditionalJumpInstruction;
033    import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.NondeterministicJumpInstruction;
034    import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction;
035    import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterInstruction;
036    import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineExitInstruction;
037    import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction;
038    import org.jetbrains.kotlin.cfg.pseudocodeTraverser.PseudocodeTraverserKt;
039    import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraverseInstructionResult;
040    import org.jetbrains.kotlin.psi.KtElement;
041    
042    import java.util.*;
043    
044    import static org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.BACKWARD;
045    import static org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.FORWARD;
046    
047    public class PseudocodeImpl implements Pseudocode {
048    
049        public class PseudocodeLabel implements Label {
050            private final String name;
051            private final String comment;
052            private Integer targetInstructionIndex;
053    
054    
055            private PseudocodeLabel(@NotNull String name, @Nullable String comment) {
056                this.name = name;
057                this.comment = comment;
058            }
059    
060            @NotNull
061            @Override
062            public String getName() {
063                return name;
064            }
065    
066            @Override
067            public String toString() {
068                return comment == null ? name : (name + " [" + comment + "]");
069            }
070    
071            public Integer getTargetInstructionIndex() {
072                return targetInstructionIndex;
073            }
074    
075            public void setTargetInstructionIndex(int targetInstructionIndex) {
076                this.targetInstructionIndex = targetInstructionIndex;
077            }
078    
079            @Nullable
080            private List<Instruction> resolve() {
081                assert targetInstructionIndex != null;
082                return mutableInstructionList.subList(getTargetInstructionIndex(), mutableInstructionList.size());
083            }
084    
085            public Instruction resolveToInstruction() {
086                assert targetInstructionIndex != null;
087                return mutableInstructionList.get(targetInstructionIndex);
088            }
089    
090            public PseudocodeLabel copy(int newLabelIndex) {
091                return new PseudocodeLabel("L" + newLabelIndex, "copy of " + name + ", " + comment);
092            }
093    
094            public PseudocodeImpl getPseudocode() {
095                return PseudocodeImpl.this;
096            }
097        }
098    
099        private final List<Instruction> mutableInstructionList = new ArrayList<Instruction>();
100        private final List<Instruction> instructions = new ArrayList<Instruction>();
101    
102        private final BidirectionalMap<KtElement, PseudoValue> elementsToValues = new BidirectionalMap<KtElement, PseudoValue>();
103    
104        private final Map<PseudoValue, List<Instruction>> valueUsages = Maps.newHashMap();
105        private final Map<PseudoValue, Set<PseudoValue>> mergedValues = Maps.newHashMap();
106        private final Set<Instruction> sideEffectFree = Sets.newHashSet();
107    
108        private Pseudocode parent = null;
109        private Set<LocalFunctionDeclarationInstruction> localDeclarations = null;
110        //todo getters
111        private final Map<KtElement, Instruction> representativeInstructions = new HashMap<KtElement, Instruction>();
112    
113        private final List<PseudocodeLabel> labels = new ArrayList<PseudocodeLabel>();
114    
115        private final KtElement correspondingElement;
116        private SubroutineExitInstruction exitInstruction;
117        private SubroutineSinkInstruction sinkInstruction;
118        private SubroutineExitInstruction errorInstruction;
119        private boolean postPrecessed = false;
120    
121        public PseudocodeImpl(KtElement correspondingElement) {
122            this.correspondingElement = correspondingElement;
123        }
124    
125        @NotNull
126        @Override
127        public KtElement getCorrespondingElement() {
128            return correspondingElement;
129        }
130    
131        @NotNull
132        @Override
133        public Set<LocalFunctionDeclarationInstruction> getLocalDeclarations() {
134            if (localDeclarations == null) {
135                localDeclarations = getLocalDeclarations(this);
136            }
137            return localDeclarations;
138        }
139    
140        @NotNull
141        private static Set<LocalFunctionDeclarationInstruction> getLocalDeclarations(@NotNull Pseudocode pseudocode) {
142            Set<LocalFunctionDeclarationInstruction> localDeclarations = Sets.newLinkedHashSet();
143            for (Instruction instruction : ((PseudocodeImpl)pseudocode).mutableInstructionList) {
144                if (instruction instanceof LocalFunctionDeclarationInstruction) {
145                    localDeclarations.add((LocalFunctionDeclarationInstruction) instruction);
146                    localDeclarations.addAll(getLocalDeclarations(((LocalFunctionDeclarationInstruction)instruction).getBody()));
147                }
148            }
149            return localDeclarations;
150        }
151    
152        @Override
153        @Nullable
154        public Pseudocode getParent() {
155            return parent;
156        }
157    
158        private void setParent(Pseudocode parent) {
159            this.parent = parent;
160        }
161    
162        @NotNull
163        public Pseudocode getRootPseudocode() {
164            Pseudocode parent = getParent();
165            while (parent != null) {
166                if (parent.getParent() == null) return parent;
167                parent = parent.getParent();
168            }
169            return this;
170        }
171    
172        /*package*/ PseudocodeLabel createLabel(@NotNull String name, @Nullable String comment) {
173            PseudocodeLabel label = new PseudocodeLabel(name, comment);
174            labels.add(label);
175            return label;
176        }
177        
178        @Override
179        @NotNull
180        public List<Instruction> getInstructions() {
181            return instructions;
182        }
183    
184        @NotNull
185        @Override
186        public List<Instruction> getReversedInstructions() {
187            LinkedHashSet<Instruction> traversedInstructions = Sets.newLinkedHashSet();
188            PseudocodeTraverserKt.traverseFollowingInstructions(sinkInstruction, traversedInstructions, BACKWARD, null);
189            if (traversedInstructions.size() < instructions.size()) {
190                List<Instruction> simplyReversedInstructions = Lists.newArrayList(instructions);
191                Collections.reverse(simplyReversedInstructions);
192                for (Instruction instruction : simplyReversedInstructions) {
193                    if (!traversedInstructions.contains(instruction)) {
194                        PseudocodeTraverserKt.traverseFollowingInstructions(instruction, traversedInstructions, BACKWARD, null);
195                    }
196                }
197            }
198            return Lists.newArrayList(traversedInstructions);
199        }
200    
201        @Override
202        @NotNull
203        public List<Instruction> getInstructionsIncludingDeadCode() {
204            return mutableInstructionList;
205        }
206    
207        //for tests only
208        @NotNull
209        public List<PseudocodeLabel> getLabels() {
210            return labels;
211        }
212    
213        /*package*/ void addExitInstruction(SubroutineExitInstruction exitInstruction) {
214            addInstruction(exitInstruction);
215            assert this.exitInstruction == null;
216            this.exitInstruction = exitInstruction;
217        }
218        
219        /*package*/ void addSinkInstruction(SubroutineSinkInstruction sinkInstruction) {
220            addInstruction(sinkInstruction);
221            assert this.sinkInstruction == null;
222            this.sinkInstruction = sinkInstruction;
223        }
224    
225        /*package*/ void addErrorInstruction(SubroutineExitInstruction errorInstruction) {
226            addInstruction(errorInstruction);
227            assert this.errorInstruction == null;
228            this.errorInstruction = errorInstruction;
229        }
230    
231        /*package*/ void addInstruction(Instruction instruction) {
232            mutableInstructionList.add(instruction);
233            instruction.setOwner(this);
234    
235            if (instruction instanceof KtElementInstruction) {
236                KtElementInstruction elementInstruction = (KtElementInstruction) instruction;
237                representativeInstructions.put(elementInstruction.getElement(), instruction);
238            }
239    
240            if (instruction instanceof MergeInstruction) {
241                addMergedValues((MergeInstruction) instruction);
242            }
243    
244            for (PseudoValue inputValue : instruction.getInputValues()) {
245                addValueUsage(inputValue, instruction);
246                for (PseudoValue mergedValue : getMergedValues(inputValue)) {
247                    addValueUsage(mergedValue, instruction);
248                }
249            }
250            if (PseudocodeUtilsKt.calcSideEffectFree(instruction)) {
251                sideEffectFree.add(instruction);
252            }
253        }
254    
255        @Override
256        @NotNull
257        public SubroutineExitInstruction getExitInstruction() {
258            return exitInstruction;
259        }
260    
261        @Override
262        @NotNull
263        public SubroutineSinkInstruction getSinkInstruction() {
264            return sinkInstruction;
265        }
266    
267        @Override
268        @NotNull
269        public SubroutineEnterInstruction getEnterInstruction() {
270            return (SubroutineEnterInstruction) mutableInstructionList.get(0);
271        }
272    
273        @Nullable
274        @Override
275        public PseudoValue getElementValue(@Nullable KtElement element) {
276            return elementsToValues.get(element);
277        }
278    
279        @NotNull
280        @Override
281        public List<? extends KtElement> getValueElements(@Nullable PseudoValue value) {
282            List<? extends KtElement> result = elementsToValues.getKeysByValue(value);
283            return result != null ? result : Collections.<KtElement>emptyList();
284        }
285    
286        @NotNull
287        @Override
288        public List<? extends Instruction> getUsages(@Nullable PseudoValue value) {
289            List<? extends Instruction> result = valueUsages.get(value);
290            return result != null ? result : Collections.<Instruction>emptyList();
291        }
292    
293        @Override
294        public boolean isSideEffectFree(@NotNull Instruction instruction) {
295            return sideEffectFree.contains(instruction);
296        }
297    
298        /*package*/ void bindElementToValue(@NotNull KtElement element, @NotNull PseudoValue value) {
299            elementsToValues.put(element, value);
300        }
301    
302        /*package*/ void bindLabel(Label label) {
303            ((PseudocodeLabel) label).setTargetInstructionIndex(mutableInstructionList.size());
304        }
305        
306        private Set<PseudoValue> getMergedValues(@NotNull PseudoValue value) {
307            Set<PseudoValue> result = mergedValues.get(value);
308            return result != null ? result : Collections.<PseudoValue>emptySet();
309        }
310        
311        private void addMergedValues(@NotNull MergeInstruction instruction) {
312            Set<PseudoValue> result = new LinkedHashSet<PseudoValue>();
313            for (PseudoValue value : instruction.getInputValues()) {
314                result.addAll(getMergedValues(value));
315                result.add(value);
316            }
317            mergedValues.put(instruction.getOutputValue(), result);
318        }
319    
320        private void addValueUsage(PseudoValue value, Instruction usage) {
321            if (usage instanceof MergeInstruction) return;
322            MapsKt.getOrPut(
323                    valueUsages,
324                    value,
325                    new Function0<List<Instruction>>() {
326                        @Override
327                        public List<Instruction> invoke() {
328                            return Lists.newArrayList();
329                        }
330                    }
331            ).add(usage);
332        }
333    
334        public void postProcess() {
335            if (postPrecessed) return;
336            postPrecessed = true;
337            errorInstruction.setSink(getSinkInstruction());
338            exitInstruction.setSink(getSinkInstruction());
339            int index = 0;
340            for (Instruction instruction : mutableInstructionList) {
341                //recursively invokes 'postProcess' for local declarations
342                processInstruction(instruction, index);
343                index++;
344            }
345            if (getParent() != null) return;
346    
347            // Collecting reachable instructions should be done after processing all instructions
348            // (including instructions in local declarations) to avoid being in incomplete state.
349            collectAndCacheReachableInstructions();
350            for (LocalFunctionDeclarationInstruction localFunctionDeclarationInstruction : getLocalDeclarations()) {
351                ((PseudocodeImpl) localFunctionDeclarationInstruction.getBody()).collectAndCacheReachableInstructions();
352            }
353        }
354    
355        private void collectAndCacheReachableInstructions() {
356            Set<Instruction> reachableInstructions = collectReachableInstructions();
357            for (Instruction instruction : mutableInstructionList) {
358                if (reachableInstructions.contains(instruction)) {
359                    instructions.add(instruction);
360                }
361            }
362            markDeadInstructions();
363        }
364    
365        private void processInstruction(Instruction instruction, final int currentPosition) {
366            instruction.accept(new InstructionVisitor() {
367                @Override
368                public void visitInstructionWithNext(@NotNull InstructionWithNext instruction) {
369                    instruction.setNext(getNextPosition(currentPosition));
370                }
371    
372                @Override
373                public void visitJump(@NotNull AbstractJumpInstruction instruction) {
374                    instruction.setResolvedTarget(getJumpTarget(instruction.getTargetLabel()));
375                }
376    
377                @Override
378                public void visitNondeterministicJump(@NotNull NondeterministicJumpInstruction instruction) {
379                    instruction.setNext(getNextPosition(currentPosition));
380                    List<Label> targetLabels = instruction.getTargetLabels();
381                    for (Label targetLabel : targetLabels) {
382                        instruction.setResolvedTarget(targetLabel, getJumpTarget(targetLabel));
383                    }
384                }
385    
386                @Override
387                public void visitConditionalJump(@NotNull ConditionalJumpInstruction instruction) {
388                    Instruction nextInstruction = getNextPosition(currentPosition);
389                    Instruction jumpTarget = getJumpTarget(instruction.getTargetLabel());
390                    if (instruction.getOnTrue()) {
391                        instruction.setNextOnFalse(nextInstruction);
392                        instruction.setNextOnTrue(jumpTarget);
393                    }
394                    else {
395                        instruction.setNextOnFalse(jumpTarget);
396                        instruction.setNextOnTrue(nextInstruction);
397                    }
398                    visitJump(instruction);
399                }
400    
401                @Override
402                public void visitLocalFunctionDeclarationInstruction(@NotNull LocalFunctionDeclarationInstruction instruction) {
403                    PseudocodeImpl body = (PseudocodeImpl) instruction.getBody();
404                    body.setParent(PseudocodeImpl.this);
405                    body.postProcess();
406                    instruction.setNext(getSinkInstruction());
407                }
408    
409                @Override
410                public void visitSubroutineExit(@NotNull SubroutineExitInstruction instruction) {
411                    // Nothing
412                }
413    
414                @Override
415                public void visitSubroutineSink(@NotNull SubroutineSinkInstruction instruction) {
416                    // Nothing
417                }
418    
419                @Override
420                public void visitInstruction(@NotNull Instruction instruction) {
421                    throw new UnsupportedOperationException(instruction.toString());
422                }
423            });
424        }
425    
426        private Set<Instruction> collectReachableInstructions() {
427            Set<Instruction> visited = Sets.newHashSet();
428            PseudocodeTraverserKt.traverseFollowingInstructions(getEnterInstruction(), visited, FORWARD,
429                                                                new Function1<Instruction, TraverseInstructionResult>() {
430                @Override
431                public TraverseInstructionResult invoke(Instruction instruction) {
432                    if (instruction instanceof MagicInstruction &&
433                        ((MagicInstruction) instruction).getKind() == MagicKind.EXHAUSTIVE_WHEN_ELSE) {
434                        return TraverseInstructionResult.SKIP;
435                    }
436                    return TraverseInstructionResult.CONTINUE;
437                }
438            });
439            if (!visited.contains(getExitInstruction())) {
440                visited.add(getExitInstruction());
441            }
442            if (!visited.contains(errorInstruction)) {
443                visited.add(errorInstruction);
444            }
445            if (!visited.contains(getSinkInstruction())) {
446                visited.add(getSinkInstruction());
447            }
448            return visited;
449        }
450    
451        private void markDeadInstructions() {
452            Set<Instruction> instructionSet = Sets.newHashSet(instructions);
453            for (Instruction instruction : mutableInstructionList) {
454                if (!instructionSet.contains(instruction)) {
455                    ((InstructionImpl)instruction).setMarkedAsDead(true);
456                    for (Instruction nextInstruction : instruction.getNextInstructions()) {
457                        nextInstruction.getPreviousInstructions().remove(instruction);
458                    }
459                }
460            }
461        }
462    
463        @NotNull
464        private Instruction getJumpTarget(@NotNull Label targetLabel) {
465            return ((PseudocodeLabel)targetLabel).resolveToInstruction();
466        }
467    
468        @NotNull
469        private Instruction getNextPosition(int currentPosition) {
470            int targetPosition = currentPosition + 1;
471            assert targetPosition < mutableInstructionList.size() : currentPosition;
472            return mutableInstructionList.get(targetPosition);
473        }
474    
475        @Override
476        public PseudocodeImpl copy() {
477            PseudocodeImpl result = new PseudocodeImpl(correspondingElement);
478            result.repeatWhole(this);
479            return result;
480        }
481    
482        private void repeatWhole(@NotNull PseudocodeImpl originalPseudocode) {
483            repeatInternal(originalPseudocode, null, null, 0);
484            parent = originalPseudocode.parent;
485        }
486    
487        public int repeatPart(@NotNull Label startLabel, @NotNull Label finishLabel, int labelCount) {
488            return repeatInternal(((PseudocodeLabel) startLabel).getPseudocode(), startLabel, finishLabel, labelCount);
489        }
490    
491        private int repeatInternal(
492                @NotNull PseudocodeImpl originalPseudocode,
493                @Nullable Label startLabel, @Nullable Label finishLabel,
494                int labelCount) {
495            Integer startIndex = startLabel != null ? ((PseudocodeLabel) startLabel).getTargetInstructionIndex() : Integer.valueOf(0);
496            assert startIndex != null;
497            Integer finishIndex = finishLabel != null
498                                  ? ((PseudocodeLabel) finishLabel).getTargetInstructionIndex()
499                                  : Integer.valueOf(originalPseudocode.mutableInstructionList.size());
500            assert finishIndex != null;
501    
502            Map<Label, Label> originalToCopy = Maps.newLinkedHashMap();
503            Multimap<Instruction, Label> originalLabelsForInstruction = HashMultimap.create();
504            for (PseudocodeLabel label : originalPseudocode.labels) {
505                Integer index = label.getTargetInstructionIndex();
506                if (index == null) continue; //label is not bounded yet
507                if (label == startLabel || label == finishLabel) continue;
508    
509                if (startIndex <= index && index <= finishIndex) {
510                    originalToCopy.put(label, label.copy(labelCount++));
511                    originalLabelsForInstruction.put(getJumpTarget(label), label);
512                }
513            }
514            for (Label label : originalToCopy.values()) {
515                labels.add((PseudocodeLabel) label);
516            }
517            for (int index = startIndex; index < finishIndex; index++) {
518                Instruction originalInstruction = originalPseudocode.mutableInstructionList.get(index);
519                repeatLabelsBindingForInstruction(originalInstruction, originalToCopy, originalLabelsForInstruction);
520                Instruction copy = copyInstruction(originalInstruction, originalToCopy);
521                addInstruction(copy);
522                if (originalInstruction == originalPseudocode.errorInstruction && copy instanceof SubroutineExitInstruction) {
523                    errorInstruction = (SubroutineExitInstruction) copy;
524                }
525                if (originalInstruction == originalPseudocode.exitInstruction && copy instanceof SubroutineExitInstruction) {
526                    exitInstruction = (SubroutineExitInstruction) copy;
527                }
528                if (originalInstruction == originalPseudocode.sinkInstruction && copy instanceof SubroutineSinkInstruction) {
529                    sinkInstruction = (SubroutineSinkInstruction) copy;
530                }
531            }
532            if (finishIndex < mutableInstructionList.size()) {
533                repeatLabelsBindingForInstruction(originalPseudocode.mutableInstructionList.get(finishIndex),
534                                                  originalToCopy,
535                                                  originalLabelsForInstruction);
536            }
537            return labelCount;
538        }
539    
540        private void repeatLabelsBindingForInstruction(
541                @NotNull Instruction originalInstruction,
542                @NotNull Map<Label, Label> originalToCopy,
543                @NotNull Multimap<Instruction, Label> originalLabelsForInstruction
544        ) {
545            for (Label originalLabel : originalLabelsForInstruction.get(originalInstruction)) {
546                bindLabel(originalToCopy.get(originalLabel));
547            }
548        }
549    
550        private static Instruction copyInstruction(@NotNull Instruction instruction, @NotNull Map<Label, Label> originalToCopy) {
551            if (instruction instanceof AbstractJumpInstruction) {
552                Label originalTarget = ((AbstractJumpInstruction) instruction).getTargetLabel();
553                if (originalToCopy.containsKey(originalTarget)) {
554                    return ((AbstractJumpInstruction)instruction).copy(originalToCopy.get(originalTarget));
555                }
556            }
557            if (instruction instanceof NondeterministicJumpInstruction) {
558                List<Label> originalTargets = ((NondeterministicJumpInstruction) instruction).getTargetLabels();
559                List<Label> copyTargets = copyLabels(originalTargets, originalToCopy);
560                return ((NondeterministicJumpInstruction) instruction).copy(copyTargets);
561            }
562            return ((InstructionImpl)instruction).copy();
563        }
564    
565        @NotNull
566        private static List<Label> copyLabels(Collection<Label> labels, Map<Label, Label> originalToCopy) {
567            List<Label> newLabels = Lists.newArrayList();
568            for (Label label : labels) {
569                Label newLabel = originalToCopy.get(label);
570                newLabels.add(newLabel != null ? newLabel : label);
571            }
572            return newLabels;
573        }
574    }