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