blob: ae22ff183a460d25fc0cecdd2d34f5b32ae216d9 [file] [log] [blame] [view]
Morten Krogh-Jespersen00d79802021-09-06 13:55:28 +02001# R8, Retrace and map file versioning
2
3Programs compiled by R8 are not structurally the same as the input. For example,
4names, invokes and line numbers can all change when compiling with R8. The
5correspondence between the input and the output is recorded in a mapping file.
6The mapping file can be used with retrace to recover the original stack trace
7from the stack traces produced by running the R8 compiled program. More
8information about R8 and mapping files can be found
9[here](https://developer.android.com/studio/build/shrink-code#decode-stack-trace).
10
11## Additional information appended as comments to the file
12
13The format for additional information is encoded as comments with json formatted
14data. The data has an ID to disambiguate it from other information.
15
16### Version
17
18The version information states what version the content of the mapping file is
19using.
20
21The format of the version information is:
22```
23# {"id":"com.android.tools.r8.mapping","version":"1.0"}
24```
25Here `id` must be `com.android.tools.r8.mapping` and `version` is the version of
26the mapping file.
27
28The version information applies to content of the file following the position of
29the version entry until either the end of the file or another version entry is
30present.
31
32If no version information is present, the content is assumed to have version
33zero and no additional mapping information except [Source File](#source-file).
34
35When interpreting the mapping file, any additional mapping information
36pertaining to a later version should be ignored. In other words, treated as a
37normal comment.
38
39Retracing tools supporting this versioning scheme should issue a warning when
40given mapping files with versions higher than the version supported by the tool.
41
Morten Krogh-Jespersen2e515292021-11-08 09:18:38 +010042- Version 1.0 was introduced by R8 in version 3.1.21
43
Morten Krogh-Jespersen00d79802021-09-06 13:55:28 +020044### Source File
45
46The source file information states what source file a class originated from.
47
48The source file information must be placed directly below the class mapping it
49pertains to. The format of the source file information is:
50```
51some.package.Class -> foo.a:
52# {"id":"sourceFile","fileName":"R8Test.kt"}
53```
54Here `id` must be the string `sourceFile` and `fileName` is the source file name
55as a string value (in this example `R8Test.kt`).
56
57Note that the `id` of the source file information is unqualified. It is the only
58allowed unqualified identifier as it was introduced prior to the versioning
59scheme.
60
61### Synthesized (Introduced at version 1.0)
62
63The synthesized information states what parts of the compiled output program are
64synthesized by the compiler. A retracing tool should use the synthesized
65information to strip out synthesized method frames from retraced stacks.
66
67The synthesized information must be placed directly below the class, field or
68method mapping it pertains to. The format of the synthesized information is:
69```
70# {'id':'com.android.tools.r8.synthesized'}
71```
72Here `id` must be `com.android.tools.r8.synthesized`. There is no other content.
73
74A class mapping would be:
75```
76some.package.SomeSynthesizedClass -> x.y.z:
77# {'id':'com.android.tools.r8.synthesized'}
78```
79This specifies that the class `x.y.z` has been synthesized by the compiler and
80thus it did not exist in the original input program.
81
82Notice that the left-hand side and right-hand side, here
83`some.package.SomeSynthesizedClass` and `x.y.z` respectively, could be any class
84name. It is likely that the mapping for synthetics is the identity, but a useful
85name can be placed here if possible which legacy retrace tools would use when
86retracing.
87
88A field mapping would be:
89```
90some.package.Class -> x.y.z:
91 int someField -> a
92 # {'id':'com.android.tools.r8.synthesized'}
93```
94This specifies that the field `x.y.z.a` has been synthesized by the compiler. As
95for classes, since the field is not part of the original input program, the
96left- and right-hand side names could be anything. Note that a field can be
97synthesized without the class being synthesized.
98
99A method mapping would be:
100```
101some.package.SomeSynthesizedClass -> x.y.z:
102 void someMethod() -> a
103 # {'id':'com.android.tools.r8.synthesized'}
104```
105This specifies that the method `x.y.z.a()` has been synthesized by the compiler.
106As for classes, since the method is not part of the original input program, the
107left- and right-hand side names could be anything. Note that a method can be
108synthesized without the class being synthesized.
109
110For inline frames a mapping would be:
111```
112some.package.Class -> foo.a:
113 4:4:void example.Foo.lambda$main$0():225 -> a
114 4:4:void run(example.Foo):2 -> a
115 # {'id':'com.android.tools.r8.synthesized'}
116 5:5:void example.Foo.lambda$main$1():228 -> a
117 5:5:void run(example.Foo):4 -> a
118 # {'id':'com.android.tools.r8.synthesized'} <-- redundant
119```
120This specifies that line 4 in the method `foo.a.a` is in a method that has
121been synthesized by the compiler. Since the method is either synthesized or not
122any extra synthesized comments will have no effect.
123
124Synthesized information should never be placed on inlined frames:
125```
126some.package.Class -> foo.a:
1274:4:void example.Foo.syntheticThatIsInlined():225 -> a
128# {'id':'com.android.tools.r8.synthesized'}
1294:4:void run(example.Foo):2 -> a
130```
131In the above, the mapping information suggests that the inline frame
132`example.Foo.syntheticThatIsInlined` should be marked as `synthesized`. However,
133since that method was not part of the input program it should not be in the
134output mapping information at all.
135
Morten Krogh-Jespersen25529002021-11-09 00:05:11 +0100136### RewriteFrame (Introduced at version 2.0)
137
138The RewriteFrame information informs the retrace tool that when retracing a
139frame it should rewrite it. The mapping information has the form:
140
141```
142 # { id: 'com.android.tools.r8.rewriteFrame', "
143 conditions: ['throws(<exceptionDescriptor>)'],
144 actions: ['removeInnerFrames(<count>)'] }
145```
146
147The format is to specify conditions for when the rule should be applied and then
148describe the actions to take. The following conditions exist:
149
150- `throws(<exceptionDescriptor>)`: Will be true if the thrown exception above is
151`<exceptionDescriptor>`
152
153Conditions can be combined by adding more items to the list. The semantics of
154having more elements in the list is that the conditions are AND'ed together. To
155achieve OR one should duplicate the information.
156
157Actions describe what should happen to the retraced frames if the condition
158holds. Multiple specified actions will be applied from left to right. The
159following actions exist:
160
161- `removeInnerFrames(<count>)`: Will remove the number of frames starting with
162inner most frame. It is an error to specify a count higher than all frames.
163
164An example could be to remove an inlined frame if a null-pointer-exception is
165thrown:
166
167```
168some.Class -> a:
169 4:4:void other.Class.inlinee():23:23 -> a
170 4:4:void caller(other.Class):7 -> a\n"
171 # { id: 'com.android.tools.r8.rewriteFrame', "
172 conditions: ['throws(Ljava/lang/NullPointerException;)'],
173 actions: ['removeInnerFrames(1)'] }
174```
175
176When retracing:
177```
178Exception in thread "main" java.lang.NullPointerException: ...
179 at a.a(:4)
180```
181
182It will normally retrace to:
183```
184Exception in thread "main" java.lang.NullPointerException: ...
185 at other.Class.inlinee(Class.java:23)
186 at some.Class.caller(Class.java:7)
187```
188
189Amending the last mapping with the above inline information instructs the
190retracer to discard frames above, resulting in the retrace result:
191```
192Exception in thread "main" java.lang.NullPointerException: ...
193 at some.Class.caller(Class.java:7)
194```
195
196The `rewriteFrame` information will only be applied if the line that is being
197retraced is directly under the exception line.
198
199### Outline (Introduced at version 2.0)
200
201The outline information can be used by compilers to specify that a method is an
202outline. It has the following format:
203
204```
205# { 'id':'com.android.tools.r8.outline' }
206```
207
208When a retracer retraces a frame that has the outline mapping information it
209should carry the reported position to the next frame and use the
210`outlineCallsite` to obtain the correct position.
211
Morten Krogh-Jespersene2124262023-01-10 00:21:55 +0100212### Outline Call Site (Introduced at version 2.0, updated at 2.2)
Morten Krogh-Jespersen25529002021-11-09 00:05:11 +0100213
214A position in an outline can correspond to multiple different positions
215depending on the context. The information can be stored in the mapping file with
216the following format:
217
218```
219# { 'id':'com.android.tools.r8.outlineCallsite',
220 'positions': {
221 'outline_pos_1': callsite_pos_1,
222 'outline_pos_2': callsite_pos_2,
223 ...
Morten Krogh-Jespersene2124262023-01-10 00:21:55 +0100224 },
225 'outline': 'outline':'La;a()I'
Morten Krogh-Jespersen25529002021-11-09 00:05:11 +0100226 }
227```
228
Morten Krogh-Jespersene2124262023-01-10 00:21:55 +0100229The `outline` key was added in 2.2 and should be the residual descriptor of the
230outline.
231
Morten Krogh-Jespersen25529002021-11-09 00:05:11 +0100232The retracer should when seeing the `outline` information carry the line number
233to the next frame. The position should be rewritten by using the positions map
234before using the resulting position for further retracing. Here is an example:
235
236```
237# { id: 'com.android.tools.r8.mapping', version: '2.0' }
238outline.Class -> a:
239 1:2:int outline() -> a
240# { 'id':'com.android.tools.r8.outline' }
241some.Class -> b:
242 1:1:void foo.bar.Baz.qux():42:42 -> s
243 4:4:int outlineCaller(int):98:98 -> s
244 5:5:int outlineCaller(int):100:100 -> s
245 27:27:int outlineCaller(int):0:0 -> s
246# { 'id':'com.android.tools.r8.outlineCallsite',
Morten Krogh-Jespersene2124262023-01-10 00:21:55 +0100247 'positions': { '1': 4, '2': 5 },
248 'outline':'La;a()I'}
Morten Krogh-Jespersen25529002021-11-09 00:05:11 +0100249```
250
251Retracing the following stack trace lines:
252
253```
254 at a.a(:1)
255 at b.s(:27)
256```
257
258Should first retrace the first line and see it is an `outline` and then use
259the `outlineCallsite` for `b.s` at position `27` to map the read position `1` to
260position `4` and then use that to find the actual mapping, resulting in the
261retraced stack:
262
263```
264 at some.Class.outlineCaller(Class.java:98)
265```
266
267It should be such that for all stack traces, if a retracer ever see an outline
268the next obfuscated line should contain `outlineCallSite` information.
269
270### Catch all range for methods with a single unique position
271
272If only a single position is needed for retracing a method correctly one can
273skip emitting the position and rely on retrace to retrace correctly. To ensure
274compatibility R8 emits a catch-all range `0:65535` as such:
275
276```
2770:65535:void foo():33:33 -> a
278```
279
280It does not matter if the mapping is an inline frame. Catch all ranges should
Morten Krogh-Jespersene2124262023-01-10 00:21:55 +0100281never be used for overloads.
282
283### Residual signature (Introduced at 2.2)
284
285The residual signature information was added to mitigate the problem with no
286right hand side signature in mapping files. The information should be placed
287directly under the first occurrence of a field or method.
288
289```
290com.bar -> a:
291com.foo -> b:
292 com.bar m1(int) -> m2,
293 # { id: 'com.android.tools.r8.residualsignature', signature:'(Z)a' }
294```
295
296Similar for fields:
297```
298com.bar -> a:
299com.foo -> b:
300 com.bar f1 -> f2,
301 # { id: 'com.android.tools.r8.residualsignature', signature:'a' }
302```
303
304If the residual definition has changed arguments or return type then the
305signature should be emitted. The residual signature has no effect on retracing
306stack traces but they are necessary when interacting with residual signatures
307through the Retrace Api or for composing mapping files.