blob: 20fc11c665065f0936c6b423136038cc995abca8 [file] [log] [blame]
// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfPosition;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimaps;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.function.Predicate;
public class SourceDebugExtensionRewriter {
private static final String SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX = "$i$f$";
private final AppView<?> appView;
private final DexItemFactory factory;
public SourceDebugExtensionRewriter(AppView<?> appView) {
this.appView = appView;
this.factory = appView.dexItemFactory();
}
public SourceDebugExtensionRewriter analyze(Predicate<DexProgramClass> shouldProcess) {
for (DexProgramClass clazz : appView.appInfo().classes()) {
if (!shouldProcess.test(clazz)) {
continue;
}
DexAnnotation sourceDebug =
clazz.annotations.getFirstMatching(factory.annotationSourceDebugExtension);
if (sourceDebug == null || sourceDebug.annotation.elements.length != 1) {
continue;
}
DexValueString dexValueString = sourceDebug.annotation.elements[0].value.asDexValueString();
if (dexValueString == null) {
continue;
}
Result parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
if (parsedData == null) {
continue;
}
for (DexEncodedMethod method : clazz.methods()) {
if (method.getCode().isCfCode()) {
processMethod(method, parsedData);
}
}
}
return this;
}
private static class Context {
private Position currentPosition = null;
private LocalVariableInfo localVariableInliningInfoEntry = null;
private final Stack<CfLabel> endRangeLabels = new Stack<>();
private final List<CfInstruction> resultingList;
private final ImmutableListMultimap<CfLabel, LocalVariableInfo> localVariableInfoStartMap;
private int lastPosition = -1;
Context(
int initialSize,
ImmutableListMultimap<CfLabel, LocalVariableInfo> localVariableInfoStartMap) {
this.resultingList = new ArrayList<>(initialSize);
this.localVariableInfoStartMap = localVariableInfoStartMap;
}
String getInlinedFunctionName() {
return localVariableInliningInfoEntry
.getLocal()
.name
.toString()
.substring(SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX.length());
}
}
private void processMethod(DexEncodedMethod method, Result parsedData) {
CfCode cfCode = method.getCode().asCfCode();
Context context =
new Context(
cfCode.getInstructions().size() + parsedData.getPositions().size(),
Multimaps.index(cfCode.getLocalVariables(), LocalVariableInfo::getStart));
for (CfInstruction instruction : cfCode.getInstructions()) {
if (instruction.isLabel()) {
handleLabel(context, instruction.asLabel());
} else if (instruction.isPosition()
&& (context.currentPosition != null || context.localVariableInliningInfoEntry != null)) {
handlePosition(context, instruction.asPosition(), parsedData);
} else {
context.resultingList.add(instruction);
}
}
cfCode.instructions = context.resultingList;
}
private void handleLabel(Context context, CfLabel label) {
ImmutableList<LocalVariableInfo> localVariableInfos =
context.localVariableInfoStartMap.get(label);
if (localVariableInfos != null) {
LocalVariableInfo newLocalVariableInliningInfo = null;
for (LocalVariableInfo localVariableInfo : localVariableInfos) {
String localVariableName = localVariableInfo.getLocal().name.toString();
if (!localVariableName.startsWith(SYNTHETIC_INLINE_FUNCTION_NAME_PREFIX)) {
continue;
}
// Only one synthetic inlining label for a position should exist.
assert newLocalVariableInliningInfo == null;
newLocalVariableInliningInfo = localVariableInfo;
}
context.localVariableInliningInfoEntry = newLocalVariableInliningInfo;
}
while (!context.endRangeLabels.empty() && context.endRangeLabels.peek() == label) {
// The inlined range is ending here. Multiple inline ranges can end at the same label.
assert context.currentPosition != null;
context.currentPosition = context.currentPosition.callerPosition;
context.endRangeLabels.pop();
}
// Ensure endRangeLabels are in sync with the current position.
assert !context.endRangeLabels.empty() || context.currentPosition == null;
context.resultingList.add(label);
}
private void handlePosition(Context context, CfPosition position, Result parsedData) {
if (context.localVariableInliningInfoEntry != null) {
// This is potentially a new inlining frame.
KotlinSourceDebugExtensionParser.Position parsedInlinePosition =
parsedData.getPositions().get(position.getPosition().line);
if (parsedInlinePosition != null) {
String descriptor = "L" + parsedInlinePosition.getSource().getPath() + ";";
if (DescriptorUtils.isClassDescriptor(descriptor)) {
// This is a new inline function. Build up the inlining information from the parsed data
// and the local variable table.
DexType sourceHolder = factory.createType(descriptor);
final String inlinee = context.getInlinedFunctionName();
// TODO(b/145904809): See if we can find the inline function.
DexMethod syntheticExistingMethod =
factory.createMethod(
sourceHolder,
factory.createProto(factory.voidType),
factory.createString(inlinee));
context.currentPosition =
new Position(
parsedInlinePosition.getRange().from,
null,
syntheticExistingMethod,
context.currentPosition);
context.endRangeLabels.push(context.localVariableInliningInfoEntry.getEnd());
context.lastPosition = position.getPosition().line;
}
}
context.localVariableInliningInfoEntry = null;
}
if (context.currentPosition != null) {
// We have a line-entry in a mapped range. Make sure to increment the index according to
// the delta in the inlined source.
Position currentPosition = context.currentPosition;
assert context.lastPosition > -1;
int delta = position.getPosition().line - context.lastPosition;
context.currentPosition =
new Position(
context.currentPosition.line + delta,
null,
currentPosition.method,
currentPosition.callerPosition);
// Append the original line index as the current caller context.
context.resultingList.add(
new CfPosition(
position.getLabel(),
appendAsOuterMostCaller(context.currentPosition, position.getPosition())));
} else {
context.resultingList.add(position);
}
}
private Position appendAsOuterMostCaller(Position position, Position callerPosition) {
if (position == null) {
return callerPosition;
} else {
return new Position(
position.line,
position.file,
position.method,
appendAsOuterMostCaller(position.callerPosition, callerPosition));
}
}
}