From a7183f6e53edbd7b56f2c9c85961337e926f99e1 Mon Sep 17 00:00:00 2001 From: Michael Bogdanov Date: Thu, 5 Mar 2015 15:29:15 +0300 Subject: [PATCH] Source mapping generation on code compilation --- .../kotlin/codegen/MemberCodegen.java | 33 ++- .../inline/AnonymousObjectTransformer.java | 6 +- .../kotlin/codegen/inline/InlineAdapter.java | 21 +- .../kotlin/codegen/inline/InlineCodegen.java | 66 ++++-- .../codegen/inline/InlineCodegenUtil.java | 17 +- .../kotlin/codegen/inline/MethodInliner.java | 20 +- .../jetbrains/kotlin/codegen/inline/SMAP.kt | 202 ++++++++++++++++++ .../codegen/inline/SMAPAndMethodNode.kt | 44 ++++ .../kotlin/codegen/inline/SMAPParser.kt | 65 ++++++ .../idea/debugger/JetPositionManager.kt | 115 ++++++++-- 10 files changed, 525 insertions(+), 64 deletions(-) create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPAndMethodNode.kt create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/MemberCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/MemberCodegen.java index d94fee65dcf..6f1c5213baf 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/MemberCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/MemberCodegen.java @@ -20,10 +20,9 @@ import com.intellij.openapi.progress.ProcessCanceledException; import kotlin.Function0; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.backend.common.CodegenUtil; import org.jetbrains.kotlin.codegen.context.*; -import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil; -import org.jetbrains.kotlin.codegen.inline.NameGenerator; -import org.jetbrains.kotlin.codegen.inline.ReifiedTypeParametersUsages; +import org.jetbrains.kotlin.codegen.inline.*; import org.jetbrains.kotlin.codegen.state.GenerationState; import org.jetbrains.kotlin.codegen.state.JetTypeMapper; import org.jetbrains.kotlin.descriptors.*; @@ -81,6 +80,8 @@ public abstract class MemberCodegen parentCodegen, @@ -135,8 +136,16 @@ public abstract class MemberCodegen mapping = sourceMapper.getFileMapping(); + for (RawFileMapping fileMapping : mapping) { + v.addSMAP(fileMapping.toFileMapping()); + } + } + v.done(); } @@ -198,7 +207,8 @@ public abstract class MemberCodegen blocks = new ArrayList(); private int nextLocalIndexBeforeInline = -1; - public InlineAdapter(MethodVisitor mv, int localsSize) { + public InlineAdapter(MethodVisitor mv, int localsSize, SourceMapper sourceMapper) { super(InlineCodegenUtil.API, mv); nextLocalIndex = localsSize; + this.sourceMapper = sourceMapper; } @Override @@ -63,7 +65,7 @@ public class InlineAdapter extends InstructionAdapter { } public void setLambdaInlining(boolean isInlining) { - this.isInlining = isInlining; + this.isLambdaInlining = isInlining; if (isInlining) { nextLocalIndexBeforeInline = nextLocalIndex; } else { @@ -74,7 +76,7 @@ public class InlineAdapter extends InstructionAdapter { @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { - if(!isInlining) { + if(!isLambdaInlining) { blocks.add(new CatchBlock(start, end, handler, type)); } else { @@ -82,6 +84,17 @@ public class InlineAdapter extends InstructionAdapter { } } + @Override + public void visitLineNumber(int line, Label start) { + if (!isLambdaInlining) { + if (sourceMapper != null) { + sourceMapper.visitLineNumber(mv, line, start); + } + } else { + super.visitLineNumber(line, start); + } + } + @Override public void visitMaxs(int stack, int locals) { for (CatchBlock b : blocks) { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java index a59fab079b6..34567d4fcaf 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java @@ -18,8 +18,10 @@ package org.jetbrains.kotlin.codegen.inline; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.backend.common.CodegenUtil; import org.jetbrains.kotlin.builtins.InlineStrategy; import org.jetbrains.kotlin.builtins.InlineUtil; import org.jetbrains.kotlin.codegen.*; @@ -50,10 +52,7 @@ import org.jetbrains.org.objectweb.asm.tree.MethodNode; import org.jetbrains.org.objectweb.asm.tree.TryCatchBlockNode; import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; +import java.util.*; import static org.jetbrains.kotlin.codegen.AsmUtil.getMethodAsmFlags; import static org.jetbrains.kotlin.codegen.AsmUtil.isPrimitive; @@ -82,6 +81,8 @@ public class InlineCodegen extends CallGenerator { private LambdaInfo activeLambda; + private SourceMapper sourceMapper; + public InlineCodegen( @NotNull ExpressionCodegen codegen, @NotNull GenerationState state, @@ -109,6 +110,11 @@ public class InlineCodegen extends CallGenerator { this.asFunctionInline = false; isSameModule = JvmCodegenUtil.isCallInsideSameModuleAsDeclared(functionDescriptor, codegen.getContext(), state.getOutDirectory()); + + Integer lineNumbers = CodegenUtil.getLineNumberForElement(callElement.getContainingFile(), true); + assert lineNumbers != null : "Couldn't extract line count in " + callElement.getContainingFile(); + + sourceMapper = codegen.getParentCodegen().getSourceMapper(); } @Override @@ -120,11 +126,11 @@ public class InlineCodegen extends CallGenerator { @Override public void genCallInner(@NotNull CallableMethod callableMethod, @Nullable ResolvedCall resolvedCall, boolean callDefault, @NotNull ExpressionCodegen codegen) { - MethodNode node = null; + SMAPAndMethodNode nodeAndSmap = null; try { - node = createMethodNode(callDefault); - endCall(inlineCall(node)); + nodeAndSmap = createMethodNode(callDefault); + endCall(inlineCall(nodeAndSmap)); } catch (CompilationException e) { throw e; @@ -135,7 +141,7 @@ public class InlineCodegen extends CallGenerator { throw new CompilationException("Couldn't inline method call '" + functionDescriptor.getName() + "' into \n" + (element != null ? element.getText() : "null psi element " + this.codegen.getContext().getContextDescriptor()) + - (generateNodeText ? ("\ncause: " + InlineCodegenUtil.getNodeText(node)) : ""), + (generateNodeText ? ("\ncause: " + InlineCodegenUtil.getNodeText(nodeAndSmap != null ? nodeAndSmap.getNode(): null)) : ""), e, callElement); } @@ -151,7 +157,7 @@ public class InlineCodegen extends CallGenerator { } @NotNull - private MethodNode createMethodNode(boolean callDefault) throws ClassNotFoundException, IOException { + private SMAPAndMethodNode createMethodNode(boolean callDefault) throws ClassNotFoundException, IOException { JvmMethodSignature jvmSignature = typeMapper.mapSignature(functionDescriptor, context.getContextKind()); Method asmMethod; @@ -162,23 +168,24 @@ public class InlineCodegen extends CallGenerator { asmMethod = jvmSignature.getAsmMethod(); } - MethodNode node; + SMAPAndMethodNode nodeAndSMAP; if (functionDescriptor instanceof DeserializedSimpleFunctionDescriptor) { VirtualFile file = InlineCodegenUtil.getVirtualFileForCallable((DeserializedSimpleFunctionDescriptor) functionDescriptor, state); - node = InlineCodegenUtil.getMethodNode(file.contentsToByteArray(), asmMethod.getName(), asmMethod.getDescriptor()); + nodeAndSMAP = InlineCodegenUtil.getMethodNode(file.contentsToByteArray(), asmMethod.getName(), asmMethod.getDescriptor()); - if (node == null) { + if (nodeAndSMAP == null) { throw new RuntimeException("Couldn't obtain compiled function body for " + descriptorName(functionDescriptor)); } } else { PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor); + Type ownerType = typeMapper.mapOwner(functionDescriptor, false); if (element == null) { throw new RuntimeException("Couldn't find declaration for function " + descriptorName(functionDescriptor)); } - node = new MethodNode(InlineCodegenUtil.API, + MethodNode node = new MethodNode(InlineCodegenUtil.API, getMethodAsmFlags(functionDescriptor, context.getContextKind()) | (callDefault ? Opcodes.ACC_STATIC : 0), asmMethod.getName(), asmMethod.getDescriptor(), @@ -188,22 +195,32 @@ public class InlineCodegen extends CallGenerator { //for maxLocals calculation MethodVisitor maxCalcAdapter = InlineCodegenUtil.wrapWithMaxLocalCalc(node); MethodContext methodContext = context.getParentContext().intoFunction(functionDescriptor); + SMAP smap; if (callDefault) { + FakeMemberCodegen parentCodegen = new FakeMemberCodegen(codegen.getParentCodegen()); FunctionCodegen.generateDefaultImplBody( methodContext, functionDescriptor, maxCalcAdapter, DefaultParameterValueLoader.DEFAULT, - (JetNamedFunction) element, codegen.getParentCodegen() + (JetNamedFunction) element, parentCodegen ); + + smap = new SMAP(parentCodegen.mappings); } else { - generateMethodBody(maxCalcAdapter, functionDescriptor, methodContext, (JetDeclarationWithBody) element, jvmSignature); + smap = generateMethodBody(maxCalcAdapter, functionDescriptor, methodContext, (JetDeclarationWithBody) element, + jvmSignature); } + PsiFile file = element.getContainingFile(); + nodeAndSMAP = new SMAPAndMethodNode(node, file.getName(), ownerType.getInternalName(), smap); maxCalcAdapter.visitMaxs(-1, -1); maxCalcAdapter.visitEnd(); } - return node; + return nodeAndSMAP; } - private InlineResult inlineCall(MethodNode node) { + private InlineResult inlineCall(SMAPAndMethodNode nodeAndSmap) { + sourceMapper.visitSource(nodeAndSmap.getSource(), nodeAndSmap.getSourcePath()); + + MethodNode node = nodeAndSmap.getNode(); ReifiedTypeParametersUsages reificationResult = reifiedTypeInliner.reifyInstructions(node.instructions); generateClosuresBodies(); @@ -222,7 +239,8 @@ public class InlineCodegen extends CallGenerator { callElement, codegen.getParentCodegen().getClassName(), reifiedTypeInliner); - MethodInliner inliner = new MethodInliner(node, parameters, info, new FieldRemapper(null, null, parameters), isSameModule, "Method inlining " + callElement.getText()); //with captured + MethodInliner inliner = new MethodInliner(node, parameters, info, new FieldRemapper(null, null, parameters), isSameModule, + "Method inlining " + callElement.getText(), sourceMapper); //with captured LocalVarRemapper remapper = new LocalVarRemapper(parameters, initialFrameSize); @@ -283,23 +301,30 @@ public class InlineCodegen extends CallGenerator { return methodNode; } - private void generateMethodBody( + private SMAP generateMethodBody( @NotNull MethodVisitor adapter, @NotNull FunctionDescriptor descriptor, @NotNull MethodContext context, @NotNull JetDeclarationWithBody declaration, @NotNull JvmMethodSignature jvmMethodSignature ) { + FakeMemberCodegen parentCodegen = new FakeMemberCodegen(codegen.getParentCodegen()); FunctionCodegen.generateMethodBody( adapter, descriptor, context, jvmMethodSignature, new FunctionGenerationStrategy.FunctionDefault(state, descriptor, declaration), // Wrapping for preventing marking actual parent codegen as containing reifier markers - new FakeMemberCodegen(codegen.getParentCodegen()) + parentCodegen ); + + return new SMAP(parentCodegen.mappings); } private static class FakeMemberCodegen extends MemberCodegen { + private final MemberCodegen delegate; + + List mappings = new ArrayList(); + public FakeMemberCodegen(@NotNull MemberCodegen wrapped) { super(wrapped); delegate = wrapped; @@ -325,6 +350,7 @@ public class InlineCodegen extends CallGenerator { public NameGenerator getInlineNameGenerator() { return delegate.getInlineNameGenerator(); } + } @Override diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java index 32f1b395dd5..68bbf9b3fb6 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java @@ -76,25 +76,34 @@ public class InlineCodegenUtil { public static final String INLINE_MARKER_GOTO_TRY_CATCH_BLOCK_END = "goToTryCatchBlockEnd"; @Nullable - public static MethodNode getMethodNode( + public static SMAPAndMethodNode getMethodNode( byte[] classData, final String methodName, final String methodDescriptor ) throws ClassNotFoundException, IOException { ClassReader cr = new ClassReader(classData); - final MethodNode[] methodNode = new MethodNode[1]; + final MethodNode[] node = new MethodNode[1]; + final String [] debugInfo = new String[2]; cr.accept(new ClassVisitor(API) { + @Override + public void visitSource(String source, String debug) { + super.visitSource(source, debug); + debugInfo[0] = source; + debugInfo[1] = debug; + } + @Override public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) { if (methodName.equals(name) && methodDescriptor.equals(desc)) { - return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions); + node[0] = new MethodNode(access, name, desc, signature, exceptions); + return node[0]; } return null; } }, ClassReader.SKIP_FRAMES); - return methodNode[0]; + return new SMAPAndMethodNode(node[0], debugInfo[0], "TODO", new SMAPParser(debugInfo[1], "TODO").parse()); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java index e9c357e610c..d3d8470f34f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java @@ -54,6 +54,8 @@ public class MethodInliner { private final String errorPrefix; + private SourceMapper sourceMapper; + private final JetTypeMapper typeMapper; private final List invokeCalls = new ArrayList(); @@ -80,7 +82,8 @@ public class MethodInliner { @NotNull InliningContext parent, @NotNull FieldRemapper nodeRemapper, boolean isSameModule, - @NotNull String errorPrefix + @NotNull String errorPrefix, + SourceMapper sourceMapper ) { this.node = node; this.parameters = parameters; @@ -88,6 +91,7 @@ public class MethodInliner { this.nodeRemapper = nodeRemapper; this.isSameModule = isSameModule; this.errorPrefix = errorPrefix; + this.sourceMapper = sourceMapper; this.typeMapper = parent.state.getTypeMapper(); this.result = InlineResult.create(); } @@ -142,7 +146,7 @@ public class MethodInliner { RemappingMethodAdapter remappingMethodAdapter = new RemappingMethodAdapter(resultNode.access, resultNode.desc, resultNode, new TypeRemapper(currentTypeMapping)); - InlineAdapter lambdaInliner = new InlineAdapter(remappingMethodAdapter, parameters.totalSize()) { + InlineAdapter lambdaInliner = new InlineAdapter(remappingMethodAdapter, parameters.totalSize(), sourceMapper) { private AnonymousObjectGeneration anonymousObjectGen; private void handleAnonymousObjectGeneration() { @@ -215,7 +219,8 @@ public class MethodInliner { MethodInliner inliner = new MethodInliner(info.getNode(), lambdaParameters, inliningContext.subInlineLambda(info), newCapturedRemapper, true /*cause all calls in same module as lambda*/, - "Lambda inlining " + info.getLambdaClassType().getInternalName()); + "Lambda inlining " + info.getLambdaClassType().getInternalName(), + IdenticalSourceMapper.INSTANCE$); LocalVarRemapper remapper = new LocalVarRemapper(lambdaParameters, valueParamShift); InlineResult lambdaResult = inliner.doInline(this.mv, remapper, true, info);//TODO add skipped this and receiver @@ -267,6 +272,7 @@ public class MethodInliner { }; + node.accept(lambdaInliner); return resultNode; @@ -317,13 +323,6 @@ public class MethodInliner { super.visitMaxs(maxStack, maxLocals + capturedParamsSize); } - @Override - public void visitLineNumber(int line, @NotNull Label start) { - if(isInliningLambda) { - super.visitLineNumber(line, start); - } - } - @Override public void visitLocalVariable( @NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index @@ -378,7 +377,6 @@ public class MethodInliner { int index = 0; boolean awaitClassReification = false; - Set possibleDeadLabels = new HashSet(); while (cur != null) { Frame frame = sources[index]; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt new file mode 100644 index 00000000000..45839f60fd5 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt @@ -0,0 +1,202 @@ +/* + * Copyright 2010-2014 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.codegen.inline + +import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Label +import java.util +import java.util.HashSet +import java.util.Arrays + +public class SMAPBuilder(val source: String, + val path: String, + val fileMappings: List, + val lineCountInOriginalFile: Int) { + + val header = "SMAP\n$source\nKotlin\n*S Kotlin" + + fun build(): String? { + if (fileMappings.isEmpty()) { + return null; + } + + + val defaultSourceMapping = RawFileMapping(source, path) + for(i in 1..lineCountInOriginalFile) { + defaultSourceMapping.mapLine(i, i - 1, true) + } + val allMappings = arrayListOf(defaultSourceMapping.toFileMapping()) + allMappings.addAll(fileMappings) + + var id = 1; + + val fileIds = "*F" + + allMappings.fold("") {(a, e) -> + a + "\n${e.toSMAPFile(id++)}" + } + + val fileMappings = "*L" + + allMappings.fold("") {(a, e) -> + a + "${e.toSMAPMapping()}" + } + + return header + "\n" + fileIds +"\n" + fileMappings + "\n*E\n" + } + + fun RangeMapping.toSMAP(fileId: Int): String { + return if (range == 1) "$source#$fileId:$dest" else "$source#$fileId,$range:$dest" + } + + fun FileMapping.toSMAPFile(id: Int): String { + this.id = id; + return "+ $id $name\n$path" + } + + fun FileMapping.toSMAPMapping(): String { + return lineMappings.fold("") { + (a, e) -> + "$a\n${e.toSMAP(id)}" + } + } +} + +public object IdenticalSourceMapper: SourceMapper(-1) { + + override fun visitSource(name: String, path: String) { + + } + + override fun visitLineNumber(iv: MethodVisitor, lineNumber: Int, start: Label) { + iv.visitLineNumber(lineNumber, start) + } +} + +public open class SourceMapper(val lineNumbers: Int) { + + private var maxUsedValue = lineNumbers; + + private var lastMappedWithChanges: RawFileMapping? = null; + + var fileMapping: MutableList = arrayListOf(); + + open fun visitSource(name: String, path: String) { + fileMapping.add(RawFileMapping(name, path)) + } + + open fun visitLineNumber(iv: MethodVisitor, lineNumber: Int, start: Label) { + val fileMapping = fileMapping.last() + val mappedLineIndex = fileMapping.mapLine(lineNumber, maxUsedValue, true) + iv.visitLineNumber(mappedLineIndex, start) + if (mappedLineIndex > maxUsedValue) { + lastMappedWithChanges = fileMapping + maxUsedValue = mappedLineIndex + } + } + +} +/*Source Mapping*/ +class SMAP(fileMappings: List) { + var fileMappings: List = fileMappings + + val intervals = fileMappings.flatMap { it -> it.lineMappings }.sortBy(RangeMapping.Comparator) + + class object { + val FILE_SECTION = "*F" + + val LINE_SECTION = "*L" + + val END = "*E" + } +} + +class RawFileMapping(val name: String, val path: String) { + private val lineMappings = linkedMapOf() + private val rangeMappings = arrayListOf() + + private var lastMappedWithNewIndex = -1000; + + fun toFileMapping(): FileMapping { + val fileMapping = FileMapping(name, path) + for (range in rangeMappings) { + fileMapping.addRangeMapping(range) + } + return fileMapping + } + + fun mapLine(source: Int, currentIndex: Int, isLastMapped: Boolean): Int { + var dest = lineMappings.get(source); + if (dest == null) { + val rangeMapping: RangeMapping + if (rangeMappings.isNotEmpty() && isLastMapped && couldFoldInRange(lastMappedWithNewIndex, source)) { + rangeMapping = rangeMappings.last() + rangeMapping.range += source - lastMappedWithNewIndex; + dest = lineMappings.get(lastMappedWithNewIndex) + source - lastMappedWithNewIndex; + } else { + dest = currentIndex + 1; + rangeMapping = RangeMapping(source, dest) + rangeMappings.add(rangeMapping) + } + + lineMappings.put(source, dest) + lastMappedWithNewIndex = source; + } + + return dest + } + + private fun couldFoldInRange(first: Int, second: Int): Boolean { + val delta = second - first + return delta > 0 && delta <= 10; + } +} + +public class FileMapping(val name: String, val path: String) { + val lineMappings = arrayListOf() + + var id = -1; + + fun addRangeMapping(lineMapping: RangeMapping) { + lineMappings.add(lineMapping) + lineMapping.parent = this + } +} + +public class RangeMapping(val source: Int, val dest: Int, var range: Int = 1) { + + var parent: FileMapping? = null; + + fun contains(destLine: Int): Boolean { + return dest <= destLine && destLine < dest + range + } + + fun map(destLine: Int): Int { + return source + destLine - dest + } + + object Comparator : util.Comparator { + override fun compare(o1: RangeMapping, o2: RangeMapping): Int { + if (o1 == o2) return 0 + + val res = o1.dest - o2.dest + if (res == 0) { + return o1.range - o2.range + } else { + return res + } + } + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPAndMethodNode.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPAndMethodNode.kt new file mode 100644 index 00000000000..b6d37845c53 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPAndMethodNode.kt @@ -0,0 +1,44 @@ +/* + * 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.codegen.inline + +import org.jetbrains.org.objectweb.asm.tree.MethodNode +import org.jetbrains.kotlin.codegen.optimization.common.InsnStream +import org.jetbrains.org.objectweb.asm.tree.LineNumberNode +import org.jetbrains.org.objectweb.asm.Label +import kotlin.properties.Delegates +import java.util.Collections + +class SMAPAndMethodNode(val node: MethodNode, val source: String, val sourcePath: String, classSMAP: SMAP) { + + val lineNumbers = Delegates.lazy { + InsnStream(node.instructions.getFirst(), null).filterIsInstance(javaClass()).map { + val index = Collections.binarySearch(classSMAP.intervals, RangeMapping(it.line, it.line, 1), { + (value, key) -> if (value.contains(key.dest)) 0 else RangeMapping.Comparator.compare(value, key) + }) + if (index < 0) throw IllegalStateException("Unmapped lable in inlined function " + it) + LabelAndMapping(it, classSMAP.intervals[index]) + } + }; + +} + +class LabelAndMapping(val lineNumberNode: LineNumberNode, val mapper: RangeMapping) { + val getOriginalLine = mapper.map(lineNumberNode.line) + val getOriginalFileName = mapper.parent!!.name + val getOriginalPath = mapper.parent!!.path +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt new file mode 100644 index 00000000000..133933aa033 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt @@ -0,0 +1,65 @@ +/* + * 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.codegen.inline + +class SMAPParser(val input: String?, val path: String) { + + val fileMappings = linkedMapOf() + + fun parse() : SMAP { + if (input == null || input.isEmpty()) { + return SMAP(listOf()) + } + + val fileSectionStart = input.indexOf(SMAP.FILE_SECTION) + SMAP.FILE_SECTION.length() + val lineSectionAnchor = input.indexOf(SMAP.LINE_SECTION) + val files = input.substring(fileSectionStart, lineSectionAnchor) + + + val fileEntries = files.trim().split('+') + + for (fileDeclaration in fileEntries) { + if (fileDeclaration == "") continue; + val fileInternalName = fileDeclaration.trim() + + val indexEnd = fileInternalName.indexOf(' ') + val fileIndex = Integer.valueOf(fileInternalName.substring(0, indexEnd)) + val newLine = fileInternalName.indexOf('\n') + val fileName = fileInternalName.substring(indexEnd + 1, newLine) + fileMappings.put(fileIndex, FileMapping(fileName, fileInternalName.substring(newLine + 1).trim())) + } + + + val lines = input.substring(lineSectionAnchor + SMAP.LINE_SECTION.length(), input.indexOf(SMAP.END)).trim().split('\n') + for (lineMapping in lines) { + /*only simple mapping now*/ + val targetSplit = lineMapping.indexOf(':') + val originalPart = lineMapping.substring(0, targetSplit) + var rangeSeparator = originalPart.indexOf(',').let { if (it < 0) targetSplit else it} + + val fileSeparator = lineMapping.indexOf('#') + val originalIndex = Integer.valueOf(originalPart.substring(0, fileSeparator)) + val range = if (rangeSeparator == targetSplit) 1 else Integer.valueOf(originalPart.substring(rangeSeparator + 1, targetSplit)) + + val fileIndex = Integer.valueOf(lineMapping.substring(fileSeparator + 1, rangeSeparator)) + val targetIndex = Integer.valueOf(lineMapping.substring(targetSplit + 1)) + fileMappings[fileIndex]!!.addRangeMapping(RangeMapping(originalIndex, targetIndex, range)) + } + + return SMAP(fileMappings.values().toList()) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt index 2eef93c91cf..30284f0429a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt @@ -50,12 +50,17 @@ import org.jetbrains.kotlin.builtins.InlineUtil import org.jetbrains.kotlin.idea.caches.resolve.* import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.util.DebuggerUtils - -import java.util.* - -import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass -import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil +import org.jetbrains.kotlin.idea.search.usagesSearch.DefaultSearchHelper +import com.intellij.find.findUsages.FindUsagesOptions +import org.jetbrains.kotlin.idea.findUsages.toSearchTarget +import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor +import org.jetbrains.kotlin.idea.search.usagesSearch.search +import java.util.WeakHashMap import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil +import java.util.ArrayList +import org.jetbrains.kotlin.codegen.binding.CodegenBinding + public class JetPositionManager(private val myDebugProcess: DebugProcess) : PositionManager { private val myTypeMappers = WeakHashMap, CachedValue>() @@ -131,9 +136,24 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Posi } - // JDI names are of form "package.Class$InnerClass" - val referenceFqName = location.declaringType().name() - val referenceInternalName = referenceFqName.replace('.', '/') + val referenceInternalName: String + try { + if (location.declaringType().availableStrata().contains("Kotlin")) { + referenceInternalName = location.sourcePath() + } else { + //no stratum or source path => use default one + val referenceFqName = location.declaringType().name() + // JDI names are of form "package.Class$InnerClass" + referenceInternalName = referenceFqName.replace('.', '/') + } + } + catch (e: AbsentInformationException) { + //no stratum or source path => use default one + val referenceFqName = location.declaringType().name() + // JDI names are of form "package.Class$InnerClass" + referenceInternalName = referenceFqName.replace('.', '/') + } + val className = JvmClassName.byInternalName(referenceInternalName) val project = myDebugProcess.getProject() @@ -147,20 +167,40 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Posi if (sourcePosition.getFile() !is JetFile) { throw NoDataException() } - val name = classNameForPosition(sourcePosition) + val names = classNameForPositionAndInlinedOnes(sourcePosition) val result = ArrayList() - if (name != null) { + for (name in names) { result.addAll(myDebugProcess.getVirtualMachineProxy().classesByName(name)) } return result } + private fun classNameForPositionAndInlinedOnes(sourcePosition: SourcePosition): List { + val result = arrayListOf() + val name = classNameForPosition(sourcePosition) + if (name != null) { + result.add(name) + } + val list = findInlinedCalls(sourcePosition.getElementAt()) + result.addAll(list) + + return result; + } + private fun classNameForPosition(sourcePosition: SourcePosition): String? { + val psiElement = sourcePosition.getElementAt() + if (psiElement == null) { + return null + } + return classNameForPosition(psiElement) + } + + private fun classNameForPosition(element: PsiElement): String? { return runReadAction { - val file = sourcePosition.getFile() as JetFile + val file = element.getContainingFile() as JetFile val isInLibrary = LibraryUtil.findLibraryEntry(file.getVirtualFile(), file.getProject()) != null - val typeMapper = if (!isInLibrary) prepareTypeMapper(file) else createTypeMapperForLibraryFile(sourcePosition.getElementAt(), file) - getClassNameForElement(sourcePosition.getElementAt(), typeMapper, file, isInLibrary) + val typeMapper = if (!isInLibrary) prepareTypeMapper(file) else createTypeMapperForLibraryFile(element, file) + getClassNameForElement(element, typeMapper, file, isInLibrary) } } @@ -188,7 +228,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Posi try { val line = position.getLine() + 1 val locations = if (myDebugProcess.getVirtualMachineProxy().versionHigher("1.4")) - type.locationsOfLine(DebugProcess.JAVA_STRATUM, null, line) + type.locationsOfLine("Kotlin", null, line) else type.locationsOfLine(line) if (locations == null || locations.isEmpty()) throw NoDataException() @@ -203,11 +243,16 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Posi if (sourcePosition.getFile() !is JetFile) { throw NoDataException() } - val className = classNameForPosition(sourcePosition) - if (className == null) { + val classNames = classNameForPositionAndInlinedOnes(sourcePosition) + if (classNames.isEmpty()) { return null } - return myDebugProcess.getRequestsManager().createClassPrepareRequest(classPrepareRequestor, className.replace('/', '.')) + val classPrepareRequest = myDebugProcess.getRequestsManager().createClassPrepareRequest(classPrepareRequestor, classNames.first().replace('/', '.')) +// classNames.subList(1, classNames.size()).forEach { +// classPrepareRequest.addClassFilter(it.replace('/', '.')) +// } + + return classPrepareRequest } TestOnly @@ -242,7 +287,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Posi return getClassNameForElement(element.getParent(), typeMapper, file, isInLibrary) } else { - val asmType = asmTypeForAnonymousClass(typeMapper.getBindingContext(), element) + val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.getBindingContext(), element) return asmType.getInternalName() } } @@ -275,7 +320,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Posi return getJvmInternalNameForImpl(typeMapper, parent) } else if (parent != null) { - val asmType = asmTypeForAnonymousClass(typeMapper.getBindingContext(), element) + val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.getBindingContext(), element) return asmType.getInternalName() } } @@ -374,4 +419,36 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Posi private fun createKeyForTypeMapper(file: JetFile) = Pair(file.getPackageFqName(), file.getModuleInfo()) } + + private fun findInlinedCalls(element: PsiElement?): List { + return runReadAction { + var result = emptyList() + if (element != null) { + val psiElement = getElementToCalculateClassName(element) + if (psiElement is JetNamedFunction) { + val typeMapper = prepareTypeMapper(psiElement.getContainingFile() as JetFile) + val descriptor = typeMapper.getBindingContext().get(BindingContext.DECLARATION_TO_DESCRIPTOR, psiElement) + if (descriptor is SimpleFunctionDescriptor && descriptor.getInlineStrategy().isInline()) { + + val project = myDebugProcess.getProject() + val usagesSearchTarget = FindUsagesOptions(project).toSearchTarget(psiElement, true) + + result = arrayListOf() + val usagesSearchRequest = DefaultSearchHelper(true).newRequest(usagesSearchTarget) + usagesSearchRequest.search().forEach { + val psiElement = it.getElement() + if (psiElement is JetElement) { + val name = classNameForPosition(psiElement) + if (name != null) { + (result as MutableList).add(name) + } + } + } + } + } + } + + result + } + } }