| // 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); |
| } |
| } |
| } |