1# Class Verification Failures 2 3[TOC] 4 5## 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. 11The 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)). 14 15## 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 95**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 98In 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 */ 143@RequiresApi(Build.VERSION_CODES.O_MR1) 144public class ApiHelperForOMR1 { 145 private ApiHelperForOMR1() {} 146 147 // ... 148} 149``` 150 151* `@RequiresApi(Build.VERSION_CODES.O_MR1)`: this tells Android Lint it's OK to 152 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. 157* R8 is smart enough not to inline methods where doing so would introduce 158 verification failures (b/138781768) 159 160### 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 188@RequiresApi(Build.VERSION_CODES.P) 189public 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 200## 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 241builds](http://go/webview-official-builds). 242 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() 298