Sam Maier | 11ca8b4 | 2022-07-22 18:11:47 | [diff] [blame] | 1 | # Java Asserts in Chromium |
| 2 | This doc exists to explain how asserts in Java are enabled and disabled by |
| 3 | Chromium's build system. |
| 4 | |
| 5 | ## javac Assertion Bytecode |
| 6 | Whenever javac compiles a Java class, assertions are transformed into the |
| 7 | following bytecode: |
| 8 | |
| 9 | ``` |
| 10 | Code: |
| 11 | 0: getstatic #2 // Static field $assertionsDisabled |
| 12 | 3: ifne 20 // Conditional jump past assertion throw |
| 13 | 12: new #3 // Class java/lang/AssertionError |
| 14 | 19: athrow // Throwing AssertionError |
| 15 | 20: return |
| 16 | |
| 17 | // NOTE: this static block was made just to check the desiredAssertionStatus. |
| 18 | // There was no static block on the class before javac created one. |
| 19 | static {}; |
| 20 | Code: |
| 21 | 2: invokevirtual #6 // Method java/lang/Class.desiredAssertionStatus() |
| 22 | 5: ifne 12 |
| 23 | 8: iconst_1 |
| 24 | 9: goto 13 |
| 25 | 12: iconst_0 |
| 26 | 13: putstatic #2 // Static field $assertionsDisabled |
| 27 | 16: return |
| 28 | ``` |
| 29 | |
| 30 | TL;DR - every single assertion is gated behind a `assertionDisabled` flag check, |
| 31 | which is a static field that can be set by the JRE's |
| 32 | `setDefaultAssertionStatus`, `setPackageAssertionStatus`, and |
| 33 | `setClassAssertionStatus` methods. |
| 34 | |
| 35 | ## Assertion Enabling/Disabling |
| 36 | Our tools which consume javac output, namely R8 and D8, each have flags which |
| 37 | the build system uses to enable or disable asserts. We control this with the |
| 38 | `enable_java_asserts` gn arg. It does this by deleting the gating check on |
| 39 | `assertionsDisabled` when enabling, and by eliminating any reference to the |
| 40 | assert when disabling. |
| 41 | |
| 42 | ```java |
| 43 | // Example equivalents of: |
| 44 | a = foo(); |
| 45 | assert a != 0; |
| 46 | return a; |
| 47 | |
| 48 | // Traditional, unoptimized javac output. |
| 49 | a = foo(); |
| 50 | if (!assertionsDisabled && a == 0) { |
| 51 | throw new AssertionError(); |
| 52 | } |
| 53 | return a; |
| 54 | |
| 55 | // Optimized with assertions enabled. |
| 56 | a = foo(); |
| 57 | if (a == 0) { |
| 58 | throw new AssertionError(); |
| 59 | } |
| 60 | return a; |
| 61 | |
| 62 | // Optimized with assertions disabled. |
| 63 | a = foo(); |
| 64 | return a; |
| 65 | ``` |
| 66 | |
| 67 | ## Assertion Enabling on Canary |
| 68 | Recently we [enabled |
| 69 | asserts](https://chromium-review.googlesource.com/c/chromium/src/+/3307087) on |
| 70 | Canary. It spiked our crash rate, and it was decided to not do this again, as |
| 71 | it's bad user experience to crash the app incessantly for non-fatal issues. |
| 72 | |
Sam Maier | c465ee8 | 2022-07-22 21:01:16 | [diff] [blame] | 73 | So, we asked the R8 team for a feature which would rewrite the bytecode of these |
| 74 | assertions, which they implemented for us. Now, instead of just turning it on |
| 75 | and throwing an `AssertionError`, [R8 would call a provided assertion |
Sam Maier | 11ca8b4 | 2022-07-22 18:11:47 | [diff] [blame] | 76 | handler](https://r8.googlesource.com/r8/+/aefe7bc18a7ce19f3e9c6dac0bedf6d182bbe142/src/main/java/com/android/tools/r8/ParseFlagInfoImpl.java#124) |
| 77 | with the `AssertionError`. We then wrote a [silent assertion |
| 78 | reporter](https://chromium-review.googlesource.com/c/chromium/src/+/3746261) |
| 79 | and this reports Java `AssertionErrors` to our crash server without crashing |
| 80 | the browser. |