1// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file 2// for details. All rights reserved. Use of this source code is governed by a 3// BSD-style license that can be found in the LICENSE file. 4package com.android.tools.r8.optimize; 5 6import com.android.tools.r8.graph.DexCode; 7import com.android.tools.r8.graph.DexDebugEntry; 8import com.android.tools.r8.graph.DexDebugEventBuilder; 9import com.android.tools.r8.graph.DexDebugInfo; 10import com.android.tools.r8.graph.DexEncodedMethod; 11import com.android.tools.r8.graph.DexItemFactory; 12import com.android.tools.r8.graph.DexProgramClass; 13import com.android.tools.r8.graph.DexString; 14import com.android.tools.r8.naming.ClassNameMapper; 15import com.android.tools.r8.naming.ClassNaming; 16import com.android.tools.r8.naming.MemberNaming; 17import com.android.tools.r8.naming.MemberNaming.Range; 18import com.android.tools.r8.naming.MemberNaming.Signature; 19import com.android.tools.r8.utils.InternalOptions; 20import it.unimi.dsi.fastutil.objects.Reference2IntMap; 21import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; 22import java.util.List; 23 24public class DebugStripper { 25 26 private static final int USED_MORE_THAN_ONCE = 0; 27 private static final int USED_ONCE = -1; 28 29 private final ClassNameMapper classNameMapper; 30 private final InternalOptions options; 31 private final DexItemFactory dexItemFactory; 32 33 public DebugStripper( 34 ClassNameMapper classNameMapper, InternalOptions options, DexItemFactory dexItemFactory) { 35 this.classNameMapper = classNameMapper; 36 this.options = options; 37 this.dexItemFactory = dexItemFactory; 38 } 39 40 private String descriptorToName(String descriptor) { 41 // The format is L<name>; and '/' is used as package separator. 42 return descriptor.substring(1, descriptor.length() - 1).replace('/', '.'); 43 } 44 45 private Range findRange(int value, List<Range> ranges, Range defaultRange) { 46 for (Range range : ranges) { 47 if (range.contains(value)) { 48 return range; 49 } 50 } 51 return defaultRange; 52 } 53 54 private static class NumberedDebugInfo { 55 56 final int numberOfEntries; 57 final DexDebugInfo info; 58 59 public NumberedDebugInfo(int numberOfEntries, DexDebugInfo info) { 60 this.numberOfEntries = numberOfEntries; 61 this.info = info; 62 } 63 } 64 65 private NumberedDebugInfo processDebugInfo( 66 DexEncodedMethod method, DexDebugInfo info, MemberNaming naming, int startLine) { 67 if (info == null || naming == null) { 68 return new NumberedDebugInfo(0, null); 69 } 70 List<Range> ranges = naming.getInlineRanges(); 71 // Maintain line and address but only when entering or leaving a range of line numbers 72 // that pertains to a different method body. 73 Range currentRange = naming.topLevelRange; 74 DexDebugEventBuilder builder = new DexDebugEventBuilder(method, dexItemFactory); 75 // Below we insert the start-line at pc-0 except if another entry already defines pc-0. 76 int entryCount = 0; 77 for (DexDebugEntry entry : info.computeEntries()) { 78 boolean addEntry = false; 79 // We are in a range, check whether we have left it. 80 if (currentRange != null && !currentRange.contains(entry.line)) { 81 currentRange = null; 82 addEntry = true; 83 } 84 // We have no range (because we left the old one or never were in a range). 85 if (currentRange == null) { 86 currentRange = findRange(entry.line, ranges, naming.topLevelRange); 87 // We entered a new Range, emit this entry. 88 if (currentRange != null) { 89 addEntry = true; 90 } 91 } 92 if (addEntry) { 93 if (entryCount == 0 && entry.address > 0) { 94 ++entryCount; 95 builder.setPosition(0, startLine); 96 } 97 int line = options.skipDebugLineNumberOpt 98 ? entry.line 99 : startLine + ranges.indexOf(currentRange) + 1; 100 builder.setPosition(entry.address, line); 101 ++entryCount; 102 } 103 } 104 if (entryCount == 0) { 105 ++entryCount; 106 builder.setPosition(0, startLine); 107 } 108 return new NumberedDebugInfo(entryCount, builder.build()); 109 } 110 111 private void processCode(DexEncodedMethod encodedMethod, MemberNaming naming, 112 Reference2IntMap<DexString> nameCounts) { 113 if (encodedMethod.getCode() == null) { 114 return; 115 } 116 DexCode code = encodedMethod.getCode().asDexCode(); 117 DexString name = encodedMethod.method.name; 118 DexDebugInfo originalInfo = code.getDebugInfo(); 119 if (originalInfo == null) { 120 return; 121 } 122 int startLine; 123 boolean isUsedOnce = false; 124 if (options.skipDebugLineNumberOpt) { 125 startLine = originalInfo.startLine; 126 } else { 127 int nameCount = nameCounts.getInt(name); 128 if (nameCount == USED_ONCE) { 129 isUsedOnce = true; 130 startLine = 0; 131 } else { 132 startLine = nameCount; 133 } 134 } 135 136 NumberedDebugInfo numberedInfo = 137 processDebugInfo(encodedMethod, originalInfo, naming, startLine); 138 DexDebugInfo newInfo = numberedInfo.info; 139 if (!options.skipDebugLineNumberOpt) { 140 // Fix up the line information. 141 int previousCount = nameCounts.getInt(name); 142 nameCounts.put(name, previousCount + numberedInfo.numberOfEntries); 143 // If we don't actually need line information and there are no debug entries, throw it away. 144 if (newInfo != null && isUsedOnce && newInfo.events.length == 0) { 145 newInfo = null; 146 } else if (naming != null && newInfo != null) { 147 naming.setCollapsedStartLineNumber(startLine); 148 // Preserve the line number information we had. 149 naming.setOriginalStartLineNumber(originalInfo.startLine); 150 } 151 } 152 code.setDebugInfo(newInfo); 153 } 154 155 private void processMethod(DexEncodedMethod method, ClassNaming classNaming, 156 Reference2IntMap<DexString> nameCounts) { 157 MemberNaming naming = null; 158 if (classNaming != null) { 159 Signature renamedSignature = classNameMapper.getRenamedMethodSignature(method.method); 160 naming = classNaming.lookup(renamedSignature); 161 } 162 processCode(method, naming, nameCounts); 163 } 164 165 private void processMethods(DexEncodedMethod[] methods, ClassNaming naming, 166 Reference2IntMap<DexString> nameCounts) { 167 if (methods == null) { 168 return; 169 } 170 for (DexEncodedMethod method : methods) { 171 processMethod(method, naming, nameCounts); 172 } 173 } 174 175 public void processClass(DexProgramClass clazz) { 176 if (!clazz.hasMethodsOrFields()) { 177 return; 178 } 179 String name = descriptorToName(clazz.type.toDescriptorString()); 180 ClassNaming naming = classNameMapper == null ? null : classNameMapper.getClassNaming(name); 181 Reference2IntMap<DexString> nameCounts = new Reference2IntOpenHashMap<>(); 182 setIntialNameCounts(nameCounts, clazz.directMethods()); 183 setIntialNameCounts(nameCounts, clazz.virtualMethods()); 184 processMethods(clazz.directMethods(), naming, nameCounts); 185 processMethods(clazz.virtualMethods(), naming, nameCounts); 186 } 187 188 private void setIntialNameCounts(Reference2IntMap<DexString> nameCounts, 189 DexEncodedMethod[] methods) { 190 for (DexEncodedMethod method : methods) { 191 if (nameCounts.containsKey(method.method.name)) { 192 nameCounts.put(method.method.name, USED_MORE_THAN_ONCE); 193 } else { 194 nameCounts.put(method.method.name, USED_ONCE); 195 } 196 } 197 } 198} 199