Benedikt Meurer | e8783db | 2024-08-21 12:16:03 | [diff] [blame] | 1 | # Chromium DevTools support checklist for JavaScript language features |
| 2 | |
| 3 | [goo.gle/v8-checklist](https://goo.gle/v8-checklist) |
| 4 | |
| 5 | Implementation for new language features (NLF) are often intrusive and affect many parts of |
| 6 | [V8](https://v8.dev). This sometimes causes the debugger to not work seamlessly with the NLF |
| 7 | out of the box. We generally make the distinction between _Basic functionality_ and _Extended |
| 8 | functionality_ when talking about debugger support: |
| 9 | |
| 10 | - Basic functionality is required for every NLF in order to launch. The debugger must not crash |
| 11 | or act in a confusing way when interacting with the NLF. For example, stepping into a `Proxy` |
| 12 | trap handler should be possible. |
| 13 | - Extended functionality is often just nice-to-have, but in some cases required for launch. |
| 14 | This includes debugging capabilities specific to the language feature. For example, catch |
| 15 | prediction should work as expected for `Promise`s. |
| 16 | |
| 17 | This document attempts to list all relevant aspects of V8’s JavaScript debugger that constitute |
| 18 | basic functionality (checkout [this document](https://goo.gle/devtools-wasm-checklist) for V8's |
| 19 | WebAssembly debugger features). Items on the list may not apply to every language feature, |
| 20 | depending on its nature. |
| 21 | |
Benedikt Meurer | 4385a56 | 2024-08-21 13:19:52 | [diff] [blame] | 22 | *** note |
| 23 | **IMPORTANT:** Please take a look at the [DevTools UI feature checklist](./ui.md) prior |
| 24 | to changing or extending the DevTools user interface (UI). |
| 25 | *** |
| 26 | |
Benedikt Meurer | e8783db | 2024-08-21 12:16:03 | [diff] [blame] | 27 | [TOC] |
| 28 | |
| 29 | ## Console printing |
| 30 | |
| 31 | DevTools offers a REPL through the Console panel. Logging a value should print reasonable output. |
| 32 | |
| 33 | ### Affected |
| 34 | |
| 35 | All NLFs that affect how values should be printed in a REPL, such as NLFs that introduce new primitives, new `RegExp` flags, etc. |
| 36 | |
| 37 | ### How to test |
| 38 | |
| 39 | Open DevTools, select the Console panel, and enter a source snippet with the NLF. The printed result should look alright. |
| 40 | |
| 41 | ### Reading material |
| 42 | |
| 43 | [Example CL that adds printing support for a new `RegExp` flag](https://chromium-review.googlesource.com/c/v8/v8/+/2848460) |
| 44 | |
| 45 | |
| 46 | ## Syntax highlighting and pretty-printing |
| 47 | |
| 48 | DevTools provides syntax highlighting and pretty-printing for JavaScript sources. |
| 49 | |
| 50 | ### Affected |
| 51 | |
| 52 | All NLFs that introduce new syntax. |
| 53 | |
| 54 | ### How to test |
| 55 | |
| 56 | Open DevTools, select the Sources panel, and create a new source snippet with the NLF. The syntax highlighting of the source |
| 57 | should look alright. This is handled by [CodeMirror](https://codemirror.net/) inside of Chromium DevTools. |
| 58 | |
| 59 | Clicking on the pretty-printing button on the bottom left ("{}") should yield reasonable results. The formatting relies on the |
| 60 | [acorn](https://github.com/acornjs/acorn) parser, which needs to support the NLF in question for this to work. |
| 61 | |
| 62 | |
| 63 | ## Stack traces |
| 64 | |
| 65 | Stack traces is the most often used way for developers to debug issues. Aside from the default `Error.stack` string, we also |
| 66 | offer a way for user code to override how to format the `stack` property, and collect a more detailed structured stack trace |
| 67 | for DevTools. |
| 68 | |
| 69 | Sometimes, due to the way a feature is implemented, there may be stack frames that show up on the stack trace when they should |
| 70 | not, or vice versa. |
| 71 | |
| 72 | For runtime exceptions, we look for the closest code position that has a source position attached. That source position is used |
| 73 | as expression position for the exception. For syntax errors, we should report the correct location of the syntax error via |
| 74 | [`MessageHandler::ReportMessage()`](https://source.chromium.org/chromium/chromium/src/+/main:v8/src/execution/messages.h;l=174-176;drc=4e40b002b46c019d18eae68b5e5a342605609d95). |
| 75 | |
| 76 | Note that `Error.stack` is only collected for `Error` objects, at the time the `Error` object is constructed. This may be different |
| 77 | than the stack trace passed to DevTools via [`JSMessageObject`](https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-objects.h;l=1264-1345;drc=82dff63dbf9db05e9274e11d9128af7b9f51ceaa). |
| 78 | |
Benedikt Meurer | e8783db | 2024-08-21 12:16:03 | [diff] [blame] | 79 | ### Affected |
| 80 | |
| 81 | NLFs that can cause an exception to be thrown, or can call into another function that throws. |
| 82 | |
| 83 | ### How to test |
| 84 | |
| 85 | When throwing inside the NLF, or with it on the stack, the stack trace including source positions should make sense. Also check |
| 86 | the structured stack trace when the exception is not caught and logged into Chrome's DevTools console. |
| 87 | |
| 88 | Repeat with the "Disable async stack traces" checkbox in the Preferences checked. |
| 89 | |
| 90 | ### Test cases |
| 91 | |
| 92 | Test cases for stack traces is mandatory, if there is any way the NLF can interact with throwing exceptions. For examples look |
| 93 | for `mjsunit` tests with `stack-trace` in their names. |
| 94 | |
Benedikt Meurer | e8783db | 2024-08-21 12:16:03 | [diff] [blame] | 95 | ### Reading material |
| 96 | |
| 97 | [Design doc for debugging support for tail-calls](https://docs.google.com/a/google.com/document/d/1Bk4QahtaT-XzrMlHTkm0SVol3LoKXTrC9E7INxJBHrE/edit?usp=drive\_web) |
| 98 | |
Simon Zünd | 5cfe4e7 | 2024-10-22 07:36:50 | [diff] [blame] | 99 | ## Async stack traces |
| 100 | |
| 101 | DevTools offers a way to show async stack traces by stitching together stack traces collected at the |
| 102 | location where the callback is passed, and the actual location of the exception inside the callback. |
| 103 | |
| 104 | The canonical example of this is throwing inside a `setTimeout` callback. The async stack trace will |
| 105 | consist of the stack trace of the error plus a stack trace captured at the `setTimeout` call-site. |
| 106 | |
| 107 | The instrumentation is based on four [`ayncTask*` methods](https://source.chromium.org/chromium/chromium/src/+/main:v8/include/v8-inspector.h;l=370-375;drc=aafd25ffbc72935b52fee731f957e5827c73a096). Namely `asyncTaskScheduled`, `asyncTaskStarted`, `asyncTaskFinished` and `asyncTaskCancelled`. |
| 108 | V8 has a [higher-level interface](https://source.chromium.org/chromium/chromium/src/+/main:v8/src/debug/debug-interface.h;l=351-352;drc=e6fc2038d73ef96ff47deda3146d94d25530e13b) that translates Promise events (such as `await`, `.then`, etc) to these for `asyncTask*` methods. |
| 109 | |
| 110 | ### Affected |
| 111 | |
| 112 | NLFs touching promises or callback scheduling. |
| 113 | |
| 114 | ### How to test |
| 115 | |
| 116 | Throw or pause inside a new language feature and check either the call stack sidebar panel in "Sources" |
| 117 | or use a `console.trace` and check the async stack trace in the console. |
| 118 | |
| 119 | ### Test cases |
| 120 | |
| 121 | Test cases for async stack traces are mandatory, if there is any way the NLF interacts with callback scheduling or |
| 122 | Promises. For examples look in V8 `inspector` tests with `async-stack` in their name (e.g. [here](https://source.chromium.org/chromium/chromium/src/+/main:v8/test/inspector/debugger/async-stack-await.js)). |
Benedikt Meurer | e8783db | 2024-08-21 12:16:03 | [diff] [blame] | 123 | |
| 124 | ## Catch prediction |
| 125 | |
| 126 | Aside from offering stack traces, V8's debugger supports DevTools' pause-on-exception feature. This comes in two flavors: |
| 127 | pause on all exceptions, and pause on uncaught exceptions. In both cases, we break at the throw site (not at the `catch`, |
| 128 | or any rethrow via `try`-`finally`). |
| 129 | |
| 130 | For the former, this is as easy as breaking in the debugger on `Isolate::Throw()`. For the latter, we have to predict whether |
| 131 | the thrown exception will be caught or otherwise handled (for example by a `Promise`'s catch handler). |
| 132 | |
| 133 | ### Affected |
| 134 | |
| 135 | NLFs that can cause an exception to be thrown, or can call into another function that throws. |
| 136 | |
| 137 | ### How to test |
| 138 | |
| 139 | When pause-on-exception is enabled, and throwing inside the NLF or with it on the stack, the script should pause as expected. |
| 140 | |
| 141 | Repeat with the "pause on caught exception" checkbox checked. |
| 142 | |
| 143 | ### Test cases |
| 144 | |
| 145 | Test cases for exception prediction is mandatory, if there is any way the NLF can interact with exceptions, be it by throwing |
| 146 | exceptions, or by relying on `try`-`catch` or `try`-`finally` in its implementation. Look for `mjsunit` tests that contain the |
| 147 | string `setBreakOnException` or `setBreakOnUncaughtException`. |
| 148 | |
| 149 | ### Reading material |
| 150 | |
| 151 | [Design doc for exception prediction for async-await](https://docs.google.com/a/google.com/document/d/1ef1drN6RTRN7iDB8FXJg\_JJZFNObCFbM1UjZWK\_ORUE/edit?usp=drive\_web) |
| 152 | |
| 153 | |
| 154 | ## Breakpoints |
| 155 | |
| 156 | One of the most important features is setting break points. The semantics should be obvious. |
| 157 | |
| 158 | Break locations are function calls, return sites, and statements. Special care are necessary for loops: for example, in `for`-loops |
| 159 | we do want to be able to break separately at the loop entry, condition evaluation, and increment. |
| 160 | |
| 161 | When setting a break point at an arbitrary source position, we actually check for the closest breakable source position, and move |
| 162 | the break point there. Consecutive debug break events at the same source position are ignored by the debugger. |
| 163 | |
| 164 | ### Affected |
| 165 | |
| 166 | NLFs that change generated code, and especially once that introduce new break locations. |
| 167 | |
| 168 | ### How to test |
| 169 | |
| 170 | Open DevTools and set break points in parts of script related to the NLF, then run the script. |
| 171 | |
| 172 | ### Test cases |
| 173 | |
| 174 | Look for `mjsunit` tests with `debug-break` in their names. |
| 175 | |
| 176 | |
| 177 | ## Stepping |
| 178 | |
| 179 | Stepping is the logical consequence to breakpoints, and is based on the same mechanism in the debugger. We differentiate between |
| 180 | |
| 181 | * Step out, which takes us to the next break location in the caller. |
| 182 | * Step next, which takes us to the next break location while ignoring calls into other functions. Note that this includes recursive |
| 183 | calls. Step next at a return site is equivalent to a step out. |
| 184 | * Step in, which takes us to the next break location including calls into another function. |
| 185 | * Step frame, which takes us to another function, either a callee or a caller. This is used for framework blackboxing, where the V8 |
| 186 | inspector is not interested in stepping in the current function, and wants to be notified once we arrive at another function |
| 187 | |
| 188 | ### Affected |
| 189 | |
| 190 | NLFs that are affect breakpoints |
| 191 | |
| 192 | ### How to test |
| 193 | |
| 194 | Break inside the part of script related to the NLF, and try stepping in, next, and out. |
| 195 | |
| 196 | ### Test cases |
| 197 | |
| 198 | Look for `mjsunit` tests with `debug-step` in their names. |
| 199 | |
| 200 | ### Reading material |
| 201 | |
| 202 | [Design doc on stepping in async-await](https://docs.google.com/a/google.com/document/d/1nj3nMlQVEFlq57dA-K8wFxdOB5ovvNuwNbWxBhcBOKE/edit?usp=drive\_web) |
| 203 | |
| 204 | |
| 205 | ## Frame inspector |
| 206 | |
| 207 | The frame inspector in V8 offers a way to a way to introspect frames on the call stack at the debug break. For the top-most frame, |
| 208 | the break location is that of the debug break. For frames below the break location is the call site leading to the frame above. |
| 209 | |
| 210 | For each frame, we can |
| 211 | |
| 212 | - inspect the scope chain at the break location with the scope iterator, |
| 213 | - find out whether it's called as constructor, |
| 214 | - find out whether we are at a return position, |
| 215 | - get the function being called, |
| 216 | - get the receiver, |
| 217 | - get the arguments, and |
| 218 | - get the number of stack-allocated locals. |
| 219 | |
| 220 | For optimized code, we use the deoptimizer to get hold of receiver, arguments and stack locals, but this is often not possible, and we |
| 221 | get the `optimzed_out` sentinel value. |
| 222 | |
| 223 | ### Affected |
| 224 | |
| 225 | NLFs that affect the way V8 calls functions. |
| 226 | |
| 227 | ### How to test |
| 228 | |
| 229 | When paused inside the function affected by the NLF, the Call Stack view in the DevTools' Source panel should show useful information. |
| 230 | |
| 231 | ### Test cases |
| 232 | |
| 233 | Take a look at `test/mjsunit/wasm/frame-inspection.js`. |
| 234 | |
| 235 | |
| 236 | ## Scope iterator |
| 237 | |
| 238 | The scope iterator in V8 offers a way to introspect the scope chain at the break location. It includes not only the scopes outside of |
| 239 | the current function, but also scopes inside it, for example inner block scopes, catch scopes, with scopes, etc. |
| 240 | |
| 241 | For each scope inside the current function, we can materialize an object representing local variables belonging to it. For scopes |
| 242 | outside the current function this is not possible. |
| 243 | |
| 244 | We can use the scope iterator to alter the value of local variables, unless we are inside an optimized frame. |
| 245 | |
| 246 | ### Affected |
| 247 | |
| 248 | NLFs that introduce new scopes. |
| 249 | |
| 250 | ### How to test |
| 251 | |
| 252 | When paused in DevTools inside the scope introduced by the NLF, the "Scope" view on the Sources panel should show useful information. |
| 253 | Scopes that are introduced by the NLF for desugaring purposes may better be hidden. |
| 254 | |
| 255 | ### Test cases |
| 256 | |
| 257 | Take a look at `test/mjsunit/debug-scopes.js`. |
| 258 | |
| 259 | ### Reading material |
| 260 | |
| 261 | [CL that introduces hidden scopes](https://chromium.googlesource.com/v8/v8/+/672983830f36222d90748ff588831b6dae565c38) |
| 262 | |
| 263 | |
| 264 | ## Debug evaluate |
| 265 | |
| 266 | With debug-evaluate, V8 offers a way to evaluate scripts at a break, attempting to behave as if the script code was executed |
| 267 | right at the break location. It is based on the frame inspector and the scope iterator. |
| 268 | |
| 269 | It works by creating a context chain that not only references contexts on the current context chain, but also contains the |
| 270 | materialized stack, including arguments object and the receiver. The script is then compiled and executed inside this context chain. |
| 271 | |
| 272 | There are some limitations, and special attention has to be paid to variable name shadowing. |
| 273 | |
| 274 | Side-effect-free debug-evaluate statically determines whether a function should throw. You should check whether to update the |
| 275 | whitelist in `src/debug/debug-evaluate.cc`. |
| 276 | |
| 277 | ### Affected |
| 278 | |
| 279 | NLFs that are also affected by the scope iterator and frame inspector. |
| 280 | |
| 281 | ### How to test |
| 282 | |
| 283 | Use the DevTools console to run scripts at a debug break. In particular the preview shown in the DevTools console by default indicates |
| 284 | whether the side-effect detection works correctly (i.e. whether you updated the whitelist correctly). |
| 285 | |
| 286 | ### Test cases |
| 287 | |
| 288 | Look for mjsunit tests with "debug-evaluate" in their names. |
| 289 | |
| 290 | ### Reading material |
| 291 | |
| 292 | This [tea-and-crumpets](https://drive.google.com/file/d/0BwPS\_JpKyELWTXV4NGZzS085NVE/view) [presentation](https://docs.google.com/a/google.com/presentation/d/18-c04ri-Whcp1dbteVTqLtK2wqlUzFgfU4kp8KgyF3I/edit?usp=drive\_web) |
| 293 | |
| 294 | Debug-evaluate without side effect [doc](https://docs.google.com/document/d/1l\_JzDbOZ6Cn1k0c5vnyEXHe7B2Xxm7lROs1vYR3nR2I/edit) and [presentation](https://docs.google.com/presentation/d/1u9lDBPMRo-mSQ6mmO03ZmpIiQ-haKyd9O4aFF0nomWs/edit) |
| 295 | |
| 296 | |
| 297 | ## Code Coverage |
| 298 | |
| 299 | Code coverage gathers execution counts and exposes them to developers through the Inspector protocol. |
| 300 | |
| 301 | ### Affected |
| 302 | |
| 303 | NLFs that contain control flow (e.g branches, loops, etc.). |
| 304 | |
| 305 | ### How to test |
| 306 | |
| 307 | Run `d8` with `--lcov` and check whether the produced coverage information is correct. E.g. like this: |
| 308 | |
| 309 | ``` |
| 310 | ./d8 --lcov=cov.info test.js |
| 311 | genhtml cov.info -o coverage |
| 312 | ``` |
| 313 | |
| 314 | Then navigate your browser to `coverage/index.html`. |
| 315 | |
| 316 | ### Test cases |
| 317 | |
| 318 | `test/mjsunit/code-coverage-block.js` |
| 319 | |
| 320 | ### Reading material |
| 321 | |
| 322 | Design doc: [go/v8-designdoc-block-code-coverage](http://go/v8-designdoc-block-code-coverage) |
| 323 | |
| 324 | |
| 325 | ## Heap profiler |
| 326 | |
| 327 | The heap profiler is a tool usually used to find out what is taking so much memory, and find potential memory leaks. It is an object graph visitor. |
| 328 | |
| 329 | ### Affected |
| 330 | |
| 331 | NLFs that change object layouts or introduce new object types |
| 332 | |
| 333 | ### How to test |
| 334 | |
| 335 | Take a heap Snapshot in DevTools' Profiler panel and inspect the result. Objects related to the NLF should fan out to all objects it references to. |
| 336 | |
| 337 | ### Test cases |
| 338 | |
| 339 | Take a look at `test/cctest/test-heap-profiler.cc`. |
| 340 | |
Simon Zünd | 5cfe4e7 | 2024-10-22 07:36:50 | [diff] [blame] | 341 | ## Restart frame |
| 342 | |
| 343 | DevTools allows restarting of some stack frames, not just the top-level one. The feature is implemented by throwing a termination exception and unwinding the stack |
| 344 | until after the function we want to restart, and then re-invoking the function. This only works for certain functions, e.g. async functions can't be restarted |
| 345 | as that would require rolling back the underlying generator. |
| 346 | |
| 347 | ### Affected |
| 348 | |
| 349 | NLFs that change function execution or require a function to carry state (e.g. async functions |
| 350 | need a generator that can't be reset). |
| 351 | |
| 352 | ### How to test |
| 353 | |
| 354 | While paused, right click a stack frame with the NLF in the call stack view and select "Restart frame". If the NLF prevents the frame from being restarted, then |
| 355 | the "Restart frame" menu item must be disabled, otherwise the frame must be properly restarted. |
| 356 | |
| 357 | ### Test cases |
| 358 | |
| 359 | Take a look in `test/inspector/debugger/restart-frame/*`. |
Benedikt Meurer | e8783db | 2024-08-21 12:16:03 | [diff] [blame] | 360 | |
| 361 | ## LiveEdit |
| 362 | |
| 363 | LiveEdit is a feature that allows for script content to be replaced during its execution. While it has many limitations, the most often use case |
Simon Zünd | 5cfe4e7 | 2024-10-22 07:36:50 | [diff] [blame] | 364 | of editing the function we are paused in and restarting said function afterwards. |
Benedikt Meurer | e8783db | 2024-08-21 12:16:03 | [diff] [blame] | 365 | |
| 366 | ### Affected |
| 367 | |
| 368 | NLFs that affect code execution |
| 369 | |
| 370 | ### How to test |
| 371 | |
Simon Zünd | 5cfe4e7 | 2024-10-22 07:36:50 | [diff] [blame] | 372 | Open DevTools and break inside the part of script affected by the NLF. Change the code inside the function with the NLF and save the script. |
Benedikt Meurer | e8783db | 2024-08-21 12:16:03 | [diff] [blame] | 373 | |
| 374 | ### Test cases |
| 375 | |
| 376 | Look for `mjsunit` tests with `liveedit` in their names. |