blob: 8d3d1afb27a38c82ad2e02183ec51b160d266aba [file] [log] [blame] [view]
Nate Fischer972db8572019-06-25 00:53:351# Class Verification Failures
2
3[TOC]
4
Sam Maier77f9c9a62022-10-18 14:39:505## This document is obsolete
6
7While class verification failures still exist, our Java optimizer, R8, has
8solved this problem for us. Developers should not have to worry about this
9problem unless there is a bug in R8. See [this bug](http://b/138781768) for where
10they implemented this solution for us.
Peter Conn487fcf452025-01-27 16:00:5111The one exception is the `GlueApiHelperFor...` classes in
12`//android_webview/glue`, R8 is not yet able to fix the issue for system APIs
13(see [this bug](https://b/254522150)).
Sam Maier77f9c9a62022-10-18 14:39:5014
Nate Fischer972db8572019-06-25 00:53:3515## What's this all about?
16
17This document aims to explain class verification on Android, how this can affect
18app performance, how to identify problems, and chromium-specific solutions. For
19simplicity, this document focuses on how class verification is implemented by
20ART, the virtual machine which replaced Dalvik starting in Android Lollipop.
21
22## What is class verification?
23
24The Java language requires any virtual machine to _verify_ the class files it
25loads and executes. Generally, verification is extra work the virtual machine is
26responsible for doing, on top of the work of loading the class and performing
27[class initialization][1].
28
29A class may fail verification for a wide variety of reasons, but in practice
30it's usually because the class's code refers to unknown classes or methods. An
31example case might look like:
32
33```java
34public class WindowHelper {
35 // ...
36 public boolean isWideColorGamut() {
37 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
38 return mWindow.isWideColorGamut();
39 }
40 return false;
41 }
42}
43```
44
45### Why does that fail?
46
47In this example, `WindowHelper` is a helper class intended to help callers
48figure out wide color gamut support, even on pre-OMR1 devices. However, this
49class will fail class verification on pre-OMR1 devices, because it refers to
50[`Window#isWideColorGamut()`][2] (new-in-OMR1), which appears to be an undefined
51method.
52
53### Huh? But we have an SDK check!
54
55SDK checks are completely irrelevant for class verification. Although readers
56can see we'll never call the new-in-OMR1 API unless we're on >= OMR1 devices,
57the Oreo version of ART doesn't know `isWideColorGamut()` was added in next
58year's release. From ART's perspective, we may as well be calling
59`methodWhichDoesNotExist()`, which would clearly be unsafe.
60
61All the SDK check does is protect us from crashing at runtime if we call this
62method on Oreo or below.
63
64### Class verification on ART
65
66While the above is a mostly general description of class verification, it's
67important to understand how the Android runtime handles this.
68
69Since class verification is extra work, ART has an optimization called **AOT
70("ahead-of-time") verification**¹. Immediately after installing an app, ART will
71scan the dex files and verify as many classes as it can. If a class fails
72verification, this is usually a "soft failure" (hard failures are uncommon), and
73ART marks the class with the status `RetryVerificationAtRuntime`.
74
75`RetryVerificationAtRuntime`, as the name suggests, means ART must try again to
76verify the class at runtime. ART does so the first time you access the class
77(right before class initialization/`<clinit>()` method). However, depending on
78the class, this verification step can be very expensive (we've observed cases
79which take [several milliseconds][3]). Since apps tend to initialize most of
80their classes during startup, verification significantly increases startup time.
81
82Another minor cost to failing class verification is that ART cannot optimize
83classes which fail verification, so **all** methods in the class will perform
84slower at runtime, even after the verification step.
85
86*** aside
87¹ AOT _verification_ should not be confused with AOT _compilation_ (another ART
88feature). Unlike compilation, AOT verification happens during install time for
89every application, whereas recent versions of ART aim to apply AOT compilation
90selectively to optimize space.
91***
92
93## Chromium's solution
94
Sam Maier77f9c9a62022-10-18 14:39:5095**Note:** This section is no longer relevant as R8 has fixed this for us. We intend
96to remove these ApiHelperFor classes - see [this bug](https://crbug.com/1302156).
97
Nate Fischer972db8572019-06-25 00:53:3598In Chromium, we try to avoid doing class verification at runtime by
99manually out-of-lining all Android API usage like so:
100
101```java
102public class ApiHelperForOMR1 {
103 public static boolean isWideColorGamut(Window window) {
104 return window.isWideColorGamut();
105 }
106}
107
108public class WindowHelper {
109 // ...
110 public boolean isWideColorGamut() {
111 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
112 return ApiHelperForOMR1.isWideColorGamut(mWindow);
113 }
114 return false;
115 }
116}
117```
118
119This pushes the class verification failure out of `WindowHelper` and into the
120new `ApiHelperForOMR1` class. There's no magic here: `ApiHelperForOMR1` will
121fail class verification on Oreo and below, for the same reason `WindowHelper`
122did previously.
123
124The key is that, while `WindowHelper` is used on all API levels, it only calls
125into `ApiHelperForOMR1` on OMR1 and above. Because we never use
126`ApiHelperForOMR1` on Oreo and below, we never load and initialize the class,
127and thanks to ART's lazy runtime class verification, we never actually retry
128verification. **Note:** `list_class_verification_failures.py` will still list
129`ApiHelperFor*` classes in its output, although these don't cause performance
130issues.
131
132### Creating ApiHelperFor\* classes
133
134There are several examples throughout the code base, but such classes should
135look as follows:
136
137```java
138/**
139 * Utility class to use new APIs that were added in O_MR1 (API level 27).
140 * These need to exist in a separate class so that Android framework can successfully verify
141 * classes without encountering the new APIs.
142 */
Nate Fischer41984082022-02-16 23:23:33143@RequiresApi(Build.VERSION_CODES.O_MR1)
Nate Fischer972db8572019-06-25 00:53:35144public class ApiHelperForOMR1 {
145 private ApiHelperForOMR1() {}
146
147 // ...
148}
149```
150
Nate Fischer41984082022-02-16 23:23:33151* `@RequiresApi(Build.VERSION_CODES.O_MR1)`: this tells Android Lint it's OK to
Nate Fischer972db8572019-06-25 00:53:35152 use OMR1 APIs since this class is only used on OMR1 and above. Substitute
153 `O_MR1` for the [appropriate constant][4], depending when the APIs were
154 introduced.
155* Don't put any `SDK_INT` checks inside this class, because it must only be
156 called on >= OMR1.
Andrew Grievea142dd52022-02-15 15:00:38157* R8 is smart enough not to inline methods where doing so would introduce
158 verification failures (b/138781768)
Nate Fischer972db8572019-06-25 00:53:35159
Nate Fischer853074cb92020-10-23 19:45:18160### Out-of-lining if your method has a new type in its signature
161
162Sometimes you'll run into a situation where a class **needs** to have a method
163which either accepts a parameter which is a new type or returns a new type
164(e.g., externally-facing code, such as WebView's glue layer). Even though it's
165impossible to write such a class without referring to the new type, it's still
166possible to avoid failing class verification. ART has a useful optimization: if
167your class only moves a value between registers (i.e., it doesn't call any
168methods or fields on the value), then ART will not check for the existence of
169that value's type. This means you can write your class like so:
170
171```java
172public class FooBar {
173 // FooBar needs to have the getNewTypeInAndroidP method, but it would be
174 // expensive to fail verification. This method will only be called on >= P
175 // but other methods on the class will be used on lower OS versions (and
176 // also can't be factored into another class).
177 public NewTypeInAndroidP getNewTypeInAndroidP() {
178 assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
179 // Stores a NewTypeInAndroidP in the return register, but doesn't do
180 // anything else with it
181 return ApiHelperForP.getNewTypeInAndroidP();
182 }
183
184 // ...
185}
186
187@VerifiesOnP
Nate Fischer41984082022-02-16 23:23:33188@RequiresApi(Build.VERSION_CODES.P)
Nate Fischer853074cb92020-10-23 19:45:18189public class ApiHelperForP {
190 public static NewTypeInAndroidP getNewTypeInAndroidP() {
191 return new NewTypeInAndroidP();
192 }
193
194 // ...
195}
196```
197
198**Note:** this only works in ART (L+), not Dalvik (KitKat and earlier).
199
Nate Fischer972db8572019-06-25 00:53:35200## Investigating class verification failures
201
202Class verification is generally surprising and nonintuitive. Fortunately, the
203ART team have provided tools to investigate errors (and the chromium team has
204built helpful wrappers).
205
206### Listing failing classes
207
208The main starting point is to figure out which classes fail verification (those
209which ART marks as `RetryVerificationAtRuntime`). This can be done for **any
210Android app** (it doesn't have to be from the chromium project) like so:
211
212```shell
213# Install the app first. Using Chrome as an example.
214autoninja -C out/Default chrome_public_apk
215out/Default/bin/chrome_public_apk install
216
217# List all classes marked as 'RetryVerificationAtRuntime'
218build/android/list_class_verification_failures.py --package="org.chromium.chrome"
219W 0.000s Main Skipping deobfuscation because no map file was provided.
220first.failing.Class
221second.failing.Class
222...
223```
224
225"Skipping deobfuscation because no map file was provided" is a warning, since
226many Android applications (including Chrome's release builds) are built with
227proguard (or similar tools) to obfuscate Java classes and shrink code. Although
228it's safe to ignore this warning if you don't obfuscate Java code, the script
229knows how to deobfuscate classes for you (useful for `is_debug = true` or
230`is_java_debug = true`):
231
232```shell
233build/android/list_class_verification_failures.py --package="org.chromium.chrome" \
234 --mapping=<path/to/file.mapping> # ex. out/Release/apks/ChromePublic.apk.mapping
235android.support.design.widget.AppBarLayout
236android.support.design.widget.TextInputLayout
237...
238```
239
240Googlers can also download mappings for [official
Nate Fischer30003e92020-07-21 19:15:17241builds](http://go/webview-official-builds).
Nate Fischer972db8572019-06-25 00:53:35242
243### Understanding the reason for the failure
244
245ART team also provide tooling for this. You can configure ART on a rooted device
246to log all class verification failures (during installation), at which point the
247cause is much clearer:
248
249```shell
250# Enable ART logging (requires root). Note the 2 pairs of quotes!
251adb root
252adb shell setprop dalvik.vm.dex2oat-flags '"--runtime-arg -verbose:verifier"'
253
254# Restart Android services to pick up the settings
255adb shell stop && adb shell start
256
257# Optional: clear logs which aren't relevant
258adb logcat -c
259
260# Install the app and check for ART logs
261adb install -d -r out/Default/apks/ChromePublic.apk
262adb logcat | grep 'dex2oat'
263...
264... I dex2oat : Soft verification failures in boolean org.chromium.content.browser.selection.SelectionPopupControllerImpl.b(android.view.ActionMode, android.view.Menu)
265... I dex2oat : boolean org.chromium.content.browser.selection.SelectionPopupControllerImpl.b(android.view.ActionMode, android.view.Menu): [0xF0] couldn't find method android.view.textclassifier.TextClassification.getActions ()Ljava/util/List;
266... I dex2oat : boolean org.chromium.content.browser.selection.SelectionPopupControllerImpl.b(android.view.ActionMode, android.view.Menu): [0xFA] couldn't find method android.view.textclassifier.TextClassification.getActions ()Ljava/util/List;
267...
268```
269
270*** note
271**Note:** you may want to avoid `adb` wrapper scripts (ex.
272`out/Default/bin/chrome_public_apk install`). These scripts cache the package
273manager state to optimize away idempotent installs. However in this case, we
274**do** want to trigger idempotent installs, because we want to re-trigger AOT
275verification.
276***
277
278In the above example, `SelectionPopupControllerImpl` fails verification on Oreo
279(API 26) because it refers to [`TextClassification.getActions()`][5], which was
280added in Pie (API 28). If `SelectionPopupControllerImpl` is used on pre-Pie
281devices, then `TextClassification.getActions()` must be out-of-lined.
282
283## See also
284
285* Bugs or questions? Contact ntfschr@chromium.org
286* ART team's Google I/O talks: [2014](https://youtu.be/EBlTzQsUoOw) and later
287 years
288* Analysis of class verification in Chrome and WebView (Google-only
289 [doc](http://go/class-verification-chromium-analysis))
290* Presentation on class verification in Chrome and WebView (Google-only
291 [slide deck](http://go/class-verification-chromium-slides))
292
293[1]: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.5
294[2]: https://developer.android.com/reference/android/view/Window.html#isWideColorGamut()
295[3]: https://bugs.chromium.org/p/chromium/issues/detail?id=838702
296[4]: https://developer.android.com/reference/android/os/Build.VERSION_CODES
297[5]: https://developer.android.com/reference/android/view/textclassifier/TextClassification.html#getActions()