Nate Fischer | 972db857 | 2019-06-25 00:53:35 | [diff] [blame] | 1 | # Class Verification Failures |
| 2 | |
| 3 | [TOC] |
| 4 | |
Sam Maier | 77f9c9a6 | 2022-10-18 14:39:50 | [diff] [blame] | 5 | ## This document is obsolete |
| 6 | |
| 7 | While class verification failures still exist, our Java optimizer, R8, has |
| 8 | solved this problem for us. Developers should not have to worry about this |
| 9 | problem unless there is a bug in R8. See [this bug](http://b/138781768) for where |
| 10 | they implemented this solution for us. |
Peter Conn | 487fcf45 | 2025-01-27 16:00:51 | [diff] [blame] | 11 | The 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 Maier | 77f9c9a6 | 2022-10-18 14:39:50 | [diff] [blame] | 14 | |
Nate Fischer | 972db857 | 2019-06-25 00:53:35 | [diff] [blame] | 15 | ## What's this all about? |
| 16 | |
| 17 | This document aims to explain class verification on Android, how this can affect |
| 18 | app performance, how to identify problems, and chromium-specific solutions. For |
| 19 | simplicity, this document focuses on how class verification is implemented by |
| 20 | ART, the virtual machine which replaced Dalvik starting in Android Lollipop. |
| 21 | |
| 22 | ## What is class verification? |
| 23 | |
| 24 | The Java language requires any virtual machine to _verify_ the class files it |
| 25 | loads and executes. Generally, verification is extra work the virtual machine is |
| 26 | responsible for doing, on top of the work of loading the class and performing |
| 27 | [class initialization][1]. |
| 28 | |
| 29 | A class may fail verification for a wide variety of reasons, but in practice |
| 30 | it's usually because the class's code refers to unknown classes or methods. An |
| 31 | example case might look like: |
| 32 | |
| 33 | ```java |
| 34 | public 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 | |
| 47 | In this example, `WindowHelper` is a helper class intended to help callers |
| 48 | figure out wide color gamut support, even on pre-OMR1 devices. However, this |
| 49 | class 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 |
| 51 | method. |
| 52 | |
| 53 | ### Huh? But we have an SDK check! |
| 54 | |
| 55 | SDK checks are completely irrelevant for class verification. Although readers |
| 56 | can see we'll never call the new-in-OMR1 API unless we're on >= OMR1 devices, |
| 57 | the Oreo version of ART doesn't know `isWideColorGamut()` was added in next |
| 58 | year's release. From ART's perspective, we may as well be calling |
| 59 | `methodWhichDoesNotExist()`, which would clearly be unsafe. |
| 60 | |
| 61 | All the SDK check does is protect us from crashing at runtime if we call this |
| 62 | method on Oreo or below. |
| 63 | |
| 64 | ### Class verification on ART |
| 65 | |
| 66 | While the above is a mostly general description of class verification, it's |
| 67 | important to understand how the Android runtime handles this. |
| 68 | |
| 69 | Since class verification is extra work, ART has an optimization called **AOT |
| 70 | ("ahead-of-time") verification**¹. Immediately after installing an app, ART will |
| 71 | scan the dex files and verify as many classes as it can. If a class fails |
| 72 | verification, this is usually a "soft failure" (hard failures are uncommon), and |
| 73 | ART marks the class with the status `RetryVerificationAtRuntime`. |
| 74 | |
| 75 | `RetryVerificationAtRuntime`, as the name suggests, means ART must try again to |
| 76 | verify the class at runtime. ART does so the first time you access the class |
| 77 | (right before class initialization/`<clinit>()` method). However, depending on |
| 78 | the class, this verification step can be very expensive (we've observed cases |
| 79 | which take [several milliseconds][3]). Since apps tend to initialize most of |
| 80 | their classes during startup, verification significantly increases startup time. |
| 81 | |
| 82 | Another minor cost to failing class verification is that ART cannot optimize |
| 83 | classes which fail verification, so **all** methods in the class will perform |
| 84 | slower at runtime, even after the verification step. |
| 85 | |
| 86 | *** aside |
| 87 | ¹ AOT _verification_ should not be confused with AOT _compilation_ (another ART |
| 88 | feature). Unlike compilation, AOT verification happens during install time for |
| 89 | every application, whereas recent versions of ART aim to apply AOT compilation |
| 90 | selectively to optimize space. |
| 91 | *** |
| 92 | |
| 93 | ## Chromium's solution |
| 94 | |
Sam Maier | 77f9c9a6 | 2022-10-18 14:39:50 | [diff] [blame] | 95 | **Note:** This section is no longer relevant as R8 has fixed this for us. We intend |
| 96 | to remove these ApiHelperFor classes - see [this bug](https://crbug.com/1302156). |
| 97 | |
Nate Fischer | 972db857 | 2019-06-25 00:53:35 | [diff] [blame] | 98 | In Chromium, we try to avoid doing class verification at runtime by |
| 99 | manually out-of-lining all Android API usage like so: |
| 100 | |
| 101 | ```java |
| 102 | public class ApiHelperForOMR1 { |
| 103 | public static boolean isWideColorGamut(Window window) { |
| 104 | return window.isWideColorGamut(); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | public 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 | |
| 119 | This pushes the class verification failure out of `WindowHelper` and into the |
| 120 | new `ApiHelperForOMR1` class. There's no magic here: `ApiHelperForOMR1` will |
| 121 | fail class verification on Oreo and below, for the same reason `WindowHelper` |
| 122 | did previously. |
| 123 | |
| 124 | The key is that, while `WindowHelper` is used on all API levels, it only calls |
| 125 | into `ApiHelperForOMR1` on OMR1 and above. Because we never use |
| 126 | `ApiHelperForOMR1` on Oreo and below, we never load and initialize the class, |
| 127 | and thanks to ART's lazy runtime class verification, we never actually retry |
| 128 | verification. **Note:** `list_class_verification_failures.py` will still list |
| 129 | `ApiHelperFor*` classes in its output, although these don't cause performance |
| 130 | issues. |
| 131 | |
| 132 | ### Creating ApiHelperFor\* classes |
| 133 | |
| 134 | There are several examples throughout the code base, but such classes should |
| 135 | look 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 Fischer | 4198408 | 2022-02-16 23:23:33 | [diff] [blame] | 143 | @RequiresApi(Build.VERSION_CODES.O_MR1) |
Nate Fischer | 972db857 | 2019-06-25 00:53:35 | [diff] [blame] | 144 | public class ApiHelperForOMR1 { |
| 145 | private ApiHelperForOMR1() {} |
| 146 | |
| 147 | // ... |
| 148 | } |
| 149 | ``` |
| 150 | |
Nate Fischer | 4198408 | 2022-02-16 23:23:33 | [diff] [blame] | 151 | * `@RequiresApi(Build.VERSION_CODES.O_MR1)`: this tells Android Lint it's OK to |
Nate Fischer | 972db857 | 2019-06-25 00:53:35 | [diff] [blame] | 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. |
Andrew Grieve | a142dd5 | 2022-02-15 15:00:38 | [diff] [blame] | 157 | * R8 is smart enough not to inline methods where doing so would introduce |
| 158 | verification failures (b/138781768) |
Nate Fischer | 972db857 | 2019-06-25 00:53:35 | [diff] [blame] | 159 | |
Nate Fischer | 853074cb9 | 2020-10-23 19:45:18 | [diff] [blame] | 160 | ### Out-of-lining if your method has a new type in its signature |
| 161 | |
| 162 | Sometimes you'll run into a situation where a class **needs** to have a method |
| 163 | which 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 |
| 165 | impossible to write such a class without referring to the new type, it's still |
| 166 | possible to avoid failing class verification. ART has a useful optimization: if |
| 167 | your class only moves a value between registers (i.e., it doesn't call any |
| 168 | methods or fields on the value), then ART will not check for the existence of |
| 169 | that value's type. This means you can write your class like so: |
| 170 | |
| 171 | ```java |
| 172 | public 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 Fischer | 4198408 | 2022-02-16 23:23:33 | [diff] [blame] | 188 | @RequiresApi(Build.VERSION_CODES.P) |
Nate Fischer | 853074cb9 | 2020-10-23 19:45:18 | [diff] [blame] | 189 | public 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 Fischer | 972db857 | 2019-06-25 00:53:35 | [diff] [blame] | 200 | ## Investigating class verification failures |
| 201 | |
| 202 | Class verification is generally surprising and nonintuitive. Fortunately, the |
| 203 | ART team have provided tools to investigate errors (and the chromium team has |
| 204 | built helpful wrappers). |
| 205 | |
| 206 | ### Listing failing classes |
| 207 | |
| 208 | The main starting point is to figure out which classes fail verification (those |
| 209 | which ART marks as `RetryVerificationAtRuntime`). This can be done for **any |
| 210 | Android 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. |
| 214 | autoninja -C out/Default chrome_public_apk |
| 215 | out/Default/bin/chrome_public_apk install |
| 216 | |
| 217 | # List all classes marked as 'RetryVerificationAtRuntime' |
| 218 | build/android/list_class_verification_failures.py --package="org.chromium.chrome" |
| 219 | W 0.000s Main Skipping deobfuscation because no map file was provided. |
| 220 | first.failing.Class |
| 221 | second.failing.Class |
| 222 | ... |
| 223 | ``` |
| 224 | |
| 225 | "Skipping deobfuscation because no map file was provided" is a warning, since |
| 226 | many Android applications (including Chrome's release builds) are built with |
| 227 | proguard (or similar tools) to obfuscate Java classes and shrink code. Although |
| 228 | it's safe to ignore this warning if you don't obfuscate Java code, the script |
| 229 | knows how to deobfuscate classes for you (useful for `is_debug = true` or |
| 230 | `is_java_debug = true`): |
| 231 | |
| 232 | ```shell |
| 233 | build/android/list_class_verification_failures.py --package="org.chromium.chrome" \ |
| 234 | --mapping=<path/to/file.mapping> # ex. out/Release/apks/ChromePublic.apk.mapping |
| 235 | android.support.design.widget.AppBarLayout |
| 236 | android.support.design.widget.TextInputLayout |
| 237 | ... |
| 238 | ``` |
| 239 | |
| 240 | Googlers can also download mappings for [official |
Nate Fischer | 30003e9 | 2020-07-21 19:15:17 | [diff] [blame] | 241 | builds](http://go/webview-official-builds). |
Nate Fischer | 972db857 | 2019-06-25 00:53:35 | [diff] [blame] | 242 | |
| 243 | ### Understanding the reason for the failure |
| 244 | |
| 245 | ART team also provide tooling for this. You can configure ART on a rooted device |
| 246 | to log all class verification failures (during installation), at which point the |
| 247 | cause is much clearer: |
| 248 | |
| 249 | ```shell |
| 250 | # Enable ART logging (requires root). Note the 2 pairs of quotes! |
| 251 | adb root |
| 252 | adb shell setprop dalvik.vm.dex2oat-flags '"--runtime-arg -verbose:verifier"' |
| 253 | |
| 254 | # Restart Android services to pick up the settings |
| 255 | adb shell stop && adb shell start |
| 256 | |
| 257 | # Optional: clear logs which aren't relevant |
| 258 | adb logcat -c |
| 259 | |
| 260 | # Install the app and check for ART logs |
| 261 | adb install -d -r out/Default/apks/ChromePublic.apk |
| 262 | adb 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 |
| 273 | manager 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 |
| 275 | verification. |
| 276 | *** |
| 277 | |
| 278 | In the above example, `SelectionPopupControllerImpl` fails verification on Oreo |
| 279 | (API 26) because it refers to [`TextClassification.getActions()`][5], which was |
| 280 | added in Pie (API 28). If `SelectionPopupControllerImpl` is used on pre-Pie |
| 281 | devices, 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() |