blob: bf9a8a2952c9e86977694c11debea16c011f7fc2 [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 */
130@DoNotInline
131@TargetApi(Build.VERSION_CODES.O_MR1)
132public class ApiHelperForOMR1 {
133 private ApiHelperForOMR1() {}
134
135 // ...
136}
137```
138
139* `@DoNotInline`: this is a chromium-defined annotation to tell proguard (and
140 similar tools) not to inline this class or its methods (since that would
141 defeat the point of out-of-lining!)
142* `@TargetApi(Build.VERSION_CODES.O_MR1)`: this tells Android Lint it's OK to
143 use OMR1 APIs since this class is only used on OMR1 and above. Substitute
144 `O_MR1` for the [appropriate constant][4], depending when the APIs were
145 introduced.
146* Don't put any `SDK_INT` checks inside this class, because it must only be
147 called on >= OMR1.
148
149## Investigating class verification failures
150
151Class verification is generally surprising and nonintuitive. Fortunately, the
152ART team have provided tools to investigate errors (and the chromium team has
153built helpful wrappers).
154
155### Listing failing classes
156
157The main starting point is to figure out which classes fail verification (those
158which ART marks as `RetryVerificationAtRuntime`). This can be done for **any
159Android app** (it doesn't have to be from the chromium project) like so:
160
161```shell
162# Install the app first. Using Chrome as an example.
163autoninja -C out/Default chrome_public_apk
164out/Default/bin/chrome_public_apk install
165
166# List all classes marked as 'RetryVerificationAtRuntime'
167build/android/list_class_verification_failures.py --package="org.chromium.chrome"
168W 0.000s Main Skipping deobfuscation because no map file was provided.
169first.failing.Class
170second.failing.Class
171...
172```
173
174"Skipping deobfuscation because no map file was provided" is a warning, since
175many Android applications (including Chrome's release builds) are built with
176proguard (or similar tools) to obfuscate Java classes and shrink code. Although
177it's safe to ignore this warning if you don't obfuscate Java code, the script
178knows how to deobfuscate classes for you (useful for `is_debug = true` or
179`is_java_debug = true`):
180
181```shell
182build/android/list_class_verification_failures.py --package="org.chromium.chrome" \
183 --mapping=<path/to/file.mapping> # ex. out/Release/apks/ChromePublic.apk.mapping
184android.support.design.widget.AppBarLayout
185android.support.design.widget.TextInputLayout
186...
187```
188
189Googlers can also download mappings for [official
190builds](http://go/clank-webview/official-builds).
191
192### Understanding the reason for the failure
193
194ART team also provide tooling for this. You can configure ART on a rooted device
195to log all class verification failures (during installation), at which point the
196cause is much clearer:
197
198```shell
199# Enable ART logging (requires root). Note the 2 pairs of quotes!
200adb root
201adb shell setprop dalvik.vm.dex2oat-flags '"--runtime-arg -verbose:verifier"'
202
203# Restart Android services to pick up the settings
204adb shell stop && adb shell start
205
206# Optional: clear logs which aren't relevant
207adb logcat -c
208
209# Install the app and check for ART logs
210adb install -d -r out/Default/apks/ChromePublic.apk
211adb logcat | grep 'dex2oat'
212...
213... I dex2oat : Soft verification failures in boolean org.chromium.content.browser.selection.SelectionPopupControllerImpl.b(android.view.ActionMode, android.view.Menu)
214... 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;
215... 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;
216...
217```
218
219*** note
220**Note:** you may want to avoid `adb` wrapper scripts (ex.
221`out/Default/bin/chrome_public_apk install`). These scripts cache the package
222manager state to optimize away idempotent installs. However in this case, we
223**do** want to trigger idempotent installs, because we want to re-trigger AOT
224verification.
225***
226
227In the above example, `SelectionPopupControllerImpl` fails verification on Oreo
228(API 26) because it refers to [`TextClassification.getActions()`][5], which was
229added in Pie (API 28). If `SelectionPopupControllerImpl` is used on pre-Pie
230devices, then `TextClassification.getActions()` must be out-of-lined.
231
232## See also
233
234* Bugs or questions? Contact ntfschr@chromium.org
235* ART team's Google I/O talks: [2014](https://youtu.be/EBlTzQsUoOw) and later
236 years
237* Analysis of class verification in Chrome and WebView (Google-only
238 [doc](http://go/class-verification-chromium-analysis))
239* Presentation on class verification in Chrome and WebView (Google-only
240 [slide deck](http://go/class-verification-chromium-slides))
241
242[1]: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.5
243[2]: https://developer.android.com/reference/android/view/Window.html#isWideColorGamut()
244[3]: https://bugs.chromium.org/p/chromium/issues/detail?id=838702
245[4]: https://developer.android.com/reference/android/os/Build.VERSION_CODES
246[5]: https://developer.android.com/reference/android/view/textclassifier/TextClassification.html#getActions()