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