blob: 0eda1e5296242581f2abc9a5d8eb4129420a24e5 [file] [log] [blame]
// Copyright (c) 2016, 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.optimize;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugEntry;
import com.android.tools.r8.graph.DexDebugEventBuilder;
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNaming;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.Range;
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.utils.InternalOptions;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.List;
public class DebugStripper {
private static final int USED_MORE_THAN_ONCE = 0;
private static final int USED_ONCE = -1;
private final ClassNameMapper classNameMapper;
private final InternalOptions options;
private final DexItemFactory dexItemFactory;
public DebugStripper(
ClassNameMapper classNameMapper, InternalOptions options, DexItemFactory dexItemFactory) {
this.classNameMapper = classNameMapper;
this.options = options;
this.dexItemFactory = dexItemFactory;
}
private String descriptorToName(String descriptor) {
// The format is L<name>; and '/' is used as package separator.
return descriptor.substring(1, descriptor.length() - 1).replace('/', '.');
}
private Range findRange(int value, List<Range> ranges, Range defaultRange) {
for (Range range : ranges) {
if (range.contains(value)) {
return range;
}
}
return defaultRange;
}
private static class NumberedDebugInfo {
final int numberOfEntries;
final DexDebugInfo info;
public NumberedDebugInfo(int numberOfEntries, DexDebugInfo info) {
this.numberOfEntries = numberOfEntries;
this.info = info;
}
}
private NumberedDebugInfo processDebugInfo(
DexEncodedMethod method, DexDebugInfo info, MemberNaming naming, int startLine) {
if (info == null || naming == null) {
return new NumberedDebugInfo(0, null);
}
List<Range> ranges = naming.getInlineRanges();
// Maintain line and address but only when entering or leaving a range of line numbers
// that pertains to a different method body.
Range currentRange = naming.topLevelRange;
DexDebugEventBuilder builder = new DexDebugEventBuilder(method, dexItemFactory);
// Below we insert the start-line at pc-0 except if another entry already defines pc-0.
int entryCount = 0;
for (DexDebugEntry entry : info.computeEntries()) {
boolean addEntry = false;
// We are in a range, check whether we have left it.
if (currentRange != null && !currentRange.contains(entry.line)) {
currentRange = null;
addEntry = true;
}
// We have no range (because we left the old one or never were in a range).
if (currentRange == null) {
currentRange = findRange(entry.line, ranges, naming.topLevelRange);
// We entered a new Range, emit this entry.
if (currentRange != null) {
addEntry = true;
}
}
if (addEntry) {
if (entryCount == 0 && entry.address > 0) {
++entryCount;
builder.setPosition(0, startLine);
}
int line = options.skipDebugLineNumberOpt
? entry.line
: startLine + ranges.indexOf(currentRange) + 1;
builder.setPosition(entry.address, line);
++entryCount;
}
}
if (entryCount == 0) {
++entryCount;
builder.setPosition(0, startLine);
}
return new NumberedDebugInfo(entryCount, builder.build());
}
private void processCode(DexEncodedMethod encodedMethod, MemberNaming naming,
Reference2IntMap<DexString> nameCounts) {
if (encodedMethod.getCode() == null) {
return;
}
DexCode code = encodedMethod.getCode().asDexCode();
DexString name = encodedMethod.method.name;
DexDebugInfo originalInfo = code.getDebugInfo();
if (originalInfo == null) {
return;
}
int startLine;
boolean isUsedOnce = false;
if (options.skipDebugLineNumberOpt) {
startLine = originalInfo.startLine;
} else {
int nameCount = nameCounts.getInt(name);
if (nameCount == USED_ONCE) {
isUsedOnce = true;
startLine = 0;
} else {
startLine = nameCount;
}
}
NumberedDebugInfo numberedInfo =
processDebugInfo(encodedMethod, originalInfo, naming, startLine);
DexDebugInfo newInfo = numberedInfo.info;
if (!options.skipDebugLineNumberOpt) {
// Fix up the line information.
int previousCount = nameCounts.getInt(name);
nameCounts.put(name, previousCount + numberedInfo.numberOfEntries);
// If we don't actually need line information and there are no debug entries, throw it away.
if (newInfo != null && isUsedOnce && newInfo.events.length == 0) {
newInfo = null;
} else if (naming != null && newInfo != null) {
naming.setCollapsedStartLineNumber(startLine);
// Preserve the line number information we had.
naming.setOriginalStartLineNumber(originalInfo.startLine);
}
}
code.setDebugInfo(newInfo);
}
private void processMethod(DexEncodedMethod method, ClassNaming classNaming,
Reference2IntMap<DexString> nameCounts) {
MemberNaming naming = null;
if (classNaming != null) {
Signature renamedSignature = classNameMapper.getRenamedMethodSignature(method.method);
naming = classNaming.lookup(renamedSignature);
}
processCode(method, naming, nameCounts);
}
public void processClass(DexProgramClass clazz) {
if (!clazz.hasMethodsOrFields()) {
return;
}
String name = descriptorToName(clazz.type.toDescriptorString());
ClassNaming naming = classNameMapper == null ? null : classNameMapper.getClassNaming(name);
Reference2IntMap<DexString> nameCounts = new Reference2IntOpenHashMap<>();
clazz.forEachMethod(method -> setIntialNameCounts(nameCounts, method));
clazz.forEachMethod(method -> processMethod(method, naming, nameCounts));
}
private void setIntialNameCounts(Reference2IntMap<DexString> nameCounts,
DexEncodedMethod method) {
if (nameCounts.containsKey(method.method.name)) {
nameCounts.put(method.method.name, USED_MORE_THAN_ONCE);
} else {
nameCounts.put(method.method.name, USED_ONCE);
}
}
}