Source mapping generation on code compilation

This commit is contained in:
Michael Bogdanov
2015-03-05 15:29:15 +03:00
parent 99aa57b432
commit a7183f6e53
10 changed files with 525 additions and 64 deletions
@@ -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<T extends JetElement/* TODO: & JetDeclaratio
protected ExpressionCodegen clInit;
private NameGenerator inlineNameGenerator;
private SourceMapper sourceMapper;
public MemberCodegen(
@NotNull GenerationState state,
@Nullable MemberCodegen<?> parentCodegen,
@@ -135,8 +136,16 @@ public abstract class MemberCodegen<T extends JetElement/* TODO: & JetDeclaratio
FunctionCodegen.endVisit(clInit.v, "static initializer", element);
}
writeInnerClasses();
if (sourceMapper != null) {
List<RawFileMapping> mapping = sourceMapper.getFileMapping();
for (RawFileMapping fileMapping : mapping) {
v.addSMAP(fileMapping.toFileMapping());
}
}
v.done();
}
@@ -198,7 +207,8 @@ public abstract class MemberCodegen<T extends JetElement/* TODO: & JetDeclaratio
if (aClass instanceof JetClass && ((JetClass) aClass).isTrait()) {
Type traitImplType = state.getTypeMapper().mapTraitImpl(descriptor);
ClassBuilder traitImplBuilder = state.getFactory().newVisitor(TraitImpl(aClass, descriptor), traitImplType, aClass.getContainingFile());
ClassBuilder traitImplBuilder = state.getFactory().newVisitor(TraitImpl(aClass, descriptor), traitImplType,
aClass.getContainingFile());
ClassContext traitImplContext = parentContext.intoClass(descriptor, OwnerKind.TRAIT_IMPL, state);
new TraitImplBodyCodegen(aClass, traitImplContext, traitImplBuilder, state, parentCodegen).generate();
}
@@ -505,8 +515,23 @@ public abstract class MemberCodegen<T extends JetElement/* TODO: & JetDeclaratio
return parentCodegen;
}
@Override
public String toString() {
return context.toString();
}
public void addSMAP(FileMapping fm) {
v.addSMAP(fm);
}
public SourceMapper getSourceMapper() {
if (sourceMapper == null) {
assert element != null : "Couldn't create source mapper for null element " + this;
Integer lineNumbers = CodegenUtil.getLineNumberForElement(element.getContainingFile(), true);
assert lineNumbers != null : "Couldn't extract line count in " + element.getContainingFile();
sourceMapper = new SourceMapper(lineNumbers);
}
return sourceMapper;
}
}
@@ -182,7 +182,8 @@ public class AnonymousObjectTransformer {
parentRemapper);
MethodInliner inliner = new MethodInliner(sourceNode, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")),
remapper, isSameModule, "Transformer for " + anonymousObjectGen.getOwnerInternalName());
remapper, isSameModule, "Transformer for " + anonymousObjectGen.getOwnerInternalName(),
null);
InlineResult result = inliner.doInline(resultVisitor, new LocalVarRemapper(parameters, 0), false, LabelOwner.NOT_APPLICABLE);
result.getReifiedTypeParametersUsages().mergeAll(typeParametersToReify);
@@ -270,7 +271,8 @@ public class AnonymousObjectTransformer {
parentRemapper);
MethodInliner inliner = new MethodInliner(constructor, constructorParameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")),
remapper, isSameModule, "Transformer for constructor of " + anonymousObjectGen.getOwnerInternalName());
remapper, isSameModule, "Transformer for constructor of " + anonymousObjectGen.getOwnerInternalName(),
null);
InlineResult result = inliner.doInline(capturedFieldInitializer, new LocalVarRemapper(constructorParameters, 0), false,
LabelOwner.NOT_APPLICABLE);
constructorVisitor.visitMaxs(-1, -1);
@@ -27,16 +27,18 @@ import java.util.List;
public class InlineAdapter extends InstructionAdapter {
private int nextLocalIndex = 0;
private final SourceMapper sourceMapper;
private boolean isInlining = false;
private boolean isLambdaInlining = false;
private final List<CatchBlock> blocks = new ArrayList<CatchBlock>();
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) {
@@ -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<FileMapping> mappings = new ArrayList<FileMapping>();
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
@@ -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());
}
@@ -54,6 +54,8 @@ public class MethodInliner {
private final String errorPrefix;
private SourceMapper sourceMapper;
private final JetTypeMapper typeMapper;
private final List<InvokeCall> invokeCalls = new ArrayList<InvokeCall>();
@@ -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<LabelNode> possibleDeadLabels = new HashSet<LabelNode>();
while (cur != null) {
Frame<SourceValue> frame = sources[index];
@@ -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<FileMapping>,
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<RawFileMapping> = 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<FileMapping>) {
var fileMappings: List<FileMapping> = 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<Int, Int>()
private val rangeMappings = arrayListOf<RangeMapping>()
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<RangeMapping>()
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<RangeMapping> {
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
}
}
}
}
@@ -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<LineNumberNode>()).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
}
@@ -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<Int, FileMapping>()
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())
}
}
@@ -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<Pair<FqName, IdeaModuleInfo>, CachedValue<JetTypeMapper>>()
@@ -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<ReferenceType>()
if (name != null) {
for (name in names) {
result.addAll(myDebugProcess.getVirtualMachineProxy().classesByName(name))
}
return result
}
private fun classNameForPositionAndInlinedOnes(sourcePosition: SourcePosition): List<String> {
val result = arrayListOf<String>()
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<String> {
return runReadAction {
var result = emptyList<String>()
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<String>()
val usagesSearchRequest = DefaultSearchHelper<JetNamedFunction>(true).newRequest(usagesSearchTarget)
usagesSearchRequest.search().forEach {
val psiElement = it.getElement()
if (psiElement is JetElement) {
val name = classNameForPosition(psiElement)
if (name != null) {
(result as MutableList<String>).add(name)
}
}
}
}
}
}
result
}
}
}