/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.kotlin.cfg; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import com.intellij.openapi.util.text.StringUtil; import kotlin.jvm.functions.Function3; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.analyzer.AnalysisResult; import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode; import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeImpl; import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeLabel; import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeUtil; import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction; import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl; import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.test.ConfigurationKind; import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.kotlin.test.KotlinTestWithEnvironment; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Set; public abstract class AbstractPseudocodeTest extends KotlinTestWithEnvironment { @Override protected KotlinCoreEnvironment createEnvironment() { return createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY); } protected void doTest(String fileName) throws Exception { File file = new File(fileName); KtFile jetFile = KotlinTestUtils.loadJetFile(getProject(), file); SetMultimap<KtElement, Pseudocode> data = LinkedHashMultimap.create(); AnalysisResult analysisResult = KotlinTestUtils.analyzeFile(jetFile, getEnvironment()); List<KtDeclaration> declarations = jetFile.getDeclarations(); BindingContext bindingContext = analysisResult.getBindingContext(); for (KtDeclaration declaration : declarations) { addDeclaration(data, bindingContext, declaration); if (declaration instanceof KtDeclarationContainer) { for (KtDeclaration member : ((KtDeclarationContainer) declaration).getDeclarations()) { // Properties and initializers are processed elsewhere if (member instanceof KtNamedFunction || member instanceof KtSecondaryConstructor) { addDeclaration(data, bindingContext, member); } } } } try { processCFData(file, data, bindingContext); } catch (IOException e) { throw new RuntimeException(e); } finally { if ("true".equals(System.getProperty("kotlin.control.flow.test.dump.graphs"))) { CFGraphToDotFilePrinter.dumpDot(file, data.values()); } } } private static void addDeclaration(SetMultimap<KtElement, Pseudocode> data, BindingContext bindingContext, KtDeclaration declaration) { Pseudocode pseudocode = PseudocodeUtil.generatePseudocode(declaration, bindingContext); data.put(declaration, pseudocode); for (LocalFunctionDeclarationInstruction instruction : pseudocode.getLocalDeclarations()) { Pseudocode localPseudocode = instruction.getBody(); data.put(localPseudocode.getCorrespondingElement(), localPseudocode); } } private void processCFData(File file, SetMultimap<KtElement, Pseudocode> data, BindingContext bindingContext) throws IOException { Collection<Pseudocode> pseudocodes = data.values(); StringBuilder instructionDump = new StringBuilder(); int i = 0; for (Pseudocode pseudocode : pseudocodes) { KtElement correspondingElement = pseudocode.getCorrespondingElement(); String label; assert (correspondingElement instanceof KtNamedDeclaration || correspondingElement instanceof KtPropertyAccessor) : "Unexpected element class is pseudocode: " + correspondingElement.getClass(); boolean isAnonymousFunction = correspondingElement instanceof KtFunctionLiteral || (correspondingElement instanceof KtNamedFunction && correspondingElement.getName() == null); if (isAnonymousFunction) { label = "anonymous_" + i++; } else if (correspondingElement instanceof KtNamedDeclaration) { KtNamedDeclaration namedDeclaration = (KtNamedDeclaration) correspondingElement; label = namedDeclaration.getName(); } else { String propertyName = ((KtProperty) correspondingElement.getParent()).getName(); label = (((KtPropertyAccessor) correspondingElement).isGetter() ? "get" : "set") + "_" + propertyName; } instructionDump.append("== ").append(label).append(" ==\n"); instructionDump.append(correspondingElement.getText()); instructionDump.append("\n---------------------\n"); dumpInstructions((PseudocodeImpl) pseudocode, instructionDump, bindingContext); instructionDump.append("=====================\n"); checkPseudocode((PseudocodeImpl) pseudocode); } File expectedInstructionsFile = KotlinTestUtils.replaceExtension(file, getDataFileExtension()); KotlinTestUtils.assertEqualsToFile(expectedInstructionsFile, instructionDump.toString()); } protected String getDataFileExtension() { return "instructions"; } protected void checkPseudocode(PseudocodeImpl pseudocode) { } private static String getIsDeadInstructionPrefix( @NotNull Instruction instruction, @NotNull Set<Instruction> remainedAfterPostProcessInstructions ) { boolean isRemovedThroughPostProcess = !remainedAfterPostProcessInstructions.contains(instruction); assert isRemovedThroughPostProcess == ((InstructionImpl)instruction).getMarkedAsDead(); return isRemovedThroughPostProcess ? "-" : " "; } private static String getDepthInstructionPrefix(@NotNull Instruction instruction, @Nullable Instruction previous) { Integer prevDepth = previous != null ? previous.getBlockScope().getDepth() : null; int depth = instruction.getBlockScope().getDepth(); if (prevDepth == null || depth != prevDepth) { return String.format("%2d ", depth); } return " "; } private static String formatInstruction(Instruction instruction, int maxLength, String prefix) { String[] parts = instruction.toString().split("\n"); if (parts.length == 1) { return prefix + String.format("%1$-" + maxLength + "s", instruction); } StringBuilder sb = new StringBuilder(); for (int i = 0, partsLength = parts.length; i < partsLength; i++) { String part = parts[i]; sb.append(prefix).append(String.format("%1$-" + maxLength + "s", part)); if (i < partsLength - 1) sb.append("\n"); } return sb.toString(); } protected abstract void dumpInstructions( @NotNull PseudocodeImpl pseudocode, @NotNull StringBuilder out, @NotNull BindingContext bindingContext ); protected void dumpInstructions( @NotNull PseudocodeImpl pseudocode, @NotNull StringBuilder out, @NotNull Function3<Instruction, /*next*/Instruction, /*prev*/Instruction, String> getInstructionData ) { List<Instruction> instructions = pseudocode.getInstructionsIncludingDeadCode(); Set<Instruction> remainedAfterPostProcessInstructions = Sets.newHashSet(pseudocode.getInstructions()); List<PseudocodeLabel> labels = pseudocode.getLabels(); int instructionColumnWidth = countInstructionColumnWidth(instructions); for (int i = 0; i < instructions.size(); i++) { Instruction instruction = instructions.get(i); for (PseudocodeLabel label: labels) { if (label.getTargetInstructionIndex() == i) { out.append(label).append(":\n"); } } StringBuilder line = new StringBuilder(); // Only print NEXT and PREV if the values are non-trivial Instruction next = i == instructions.size() - 1 ? null : instructions.get(i + 1); Instruction prev = i == 0 ? null : instructions.get(i - 1); String prefix = getIsDeadInstructionPrefix(instruction, remainedAfterPostProcessInstructions) + getDepthInstructionPrefix(instruction, prev); line.append(formatInstruction(instruction, instructionColumnWidth, prefix)); line.append(getInstructionData.invoke(instruction, next, prev)); out.append(StringUtil.trimTrailing(line.toString())); out.append("\n"); } } private static int countInstructionColumnWidth(List<Instruction> instructions) { int maxWidth = 0; for (Instruction instruction : instructions) { String instuctionText = instruction.toString(); if (instuctionText.length() > maxWidth) { String[] parts = instuctionText.split("\n"); if (parts.length > 1) { for (String part : parts) { if (part.length() > maxWidth) { maxWidth = part.length(); } } } else { if (instuctionText.length() > maxWidth) { maxWidth = instuctionText.length(); } } } } return maxWidth; } }