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