| # Chromium DevTools support checklist for JavaScript language features |
| |
| [goo.gle/v8-checklist](https://goo.gle/v8-checklist) |
| |
| Implementation for new language features (NLF) are often intrusive and affect many parts of |
| [V8](https://v8.dev). This sometimes causes the debugger to not work seamlessly with the NLF |
| out of the box. We generally make the distinction between _Basic functionality_ and _Extended |
| functionality_ when talking about debugger support: |
| |
| - Basic functionality is required for every NLF in order to launch. The debugger must not crash |
| or act in a confusing way when interacting with the NLF. For example, stepping into a `Proxy` |
| trap handler should be possible. |
| - Extended functionality is often just nice-to-have, but in some cases required for launch. |
| This includes debugging capabilities specific to the language feature. For example, catch |
| prediction should work as expected for `Promise`s. |
| |
| This document attempts to list all relevant aspects of V8’s JavaScript debugger that constitute |
| basic functionality (checkout [this document](https://goo.gle/devtools-wasm-checklist) for V8's |
| WebAssembly debugger features). Items on the list may not apply to every language feature, |
| depending on its nature. |
| |
| *** note |
| **IMPORTANT:** Please take a look at the [DevTools UI feature checklist](./ui.md) prior |
| to changing or extending the DevTools user interface (UI). |
| *** |
| |
| [TOC] |
| |
| ## Console printing |
| |
| DevTools offers a REPL through the Console panel. Logging a value should print reasonable output. |
| |
| ### Affected |
| |
| All NLFs that affect how values should be printed in a REPL, such as NLFs that introduce new primitives, new `RegExp` flags, etc. |
| |
| ### How to test |
| |
| Open DevTools, select the Console panel, and enter a source snippet with the NLF. The printed result should look alright. |
| |
| ### Reading material |
| |
| [Example CL that adds printing support for a new `RegExp` flag](https://chromium-review.googlesource.com/c/v8/v8/+/2848460) |
| |
| |
| ## Syntax highlighting and pretty-printing |
| |
| DevTools provides syntax highlighting and pretty-printing for JavaScript sources. |
| |
| ### Affected |
| |
| All NLFs that introduce new syntax. |
| |
| ### How to test |
| |
| Open DevTools, select the Sources panel, and create a new source snippet with the NLF. The syntax highlighting of the source |
| should look alright. This is handled by [CodeMirror](https://codemirror.net/) inside of Chromium DevTools. |
| |
| Clicking on the pretty-printing button on the bottom left ("{}") should yield reasonable results. The formatting relies on the |
| [acorn](https://github.com/acornjs/acorn) parser, which needs to support the NLF in question for this to work. |
| |
| |
| ## Stack traces |
| |
| Stack traces is the most often used way for developers to debug issues. Aside from the default `Error.stack` string, we also |
| offer a way for user code to override how to format the `stack` property, and collect a more detailed structured stack trace |
| for DevTools. |
| |
| Sometimes, due to the way a feature is implemented, there may be stack frames that show up on the stack trace when they should |
| not, or vice versa. |
| |
| For runtime exceptions, we look for the closest code position that has a source position attached. That source position is used |
| as expression position for the exception. For syntax errors, we should report the correct location of the syntax error via |
| [`MessageHandler::ReportMessage()`](https://source.chromium.org/chromium/chromium/src/+/main:v8/src/execution/messages.h;l=174-176;drc=4e40b002b46c019d18eae68b5e5a342605609d95). |
| |
| Note that `Error.stack` is only collected for `Error` objects, at the time the `Error` object is constructed. This may be different |
| 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). |
| |
| ### Affected |
| |
| NLFs that can cause an exception to be thrown, or can call into another function that throws. |
| |
| ### How to test |
| |
| When throwing inside the NLF, or with it on the stack, the stack trace including source positions should make sense. Also check |
| the structured stack trace when the exception is not caught and logged into Chrome's DevTools console. |
| |
| Repeat with the "Disable async stack traces" checkbox in the Preferences checked. |
| |
| ### Test cases |
| |
| Test cases for stack traces is mandatory, if there is any way the NLF can interact with throwing exceptions. For examples look |
| for `mjsunit` tests with `stack-trace` in their names. |
| |
| ### Reading material |
| |
| [Design doc for debugging support for tail-calls](https://docs.google.com/a/google.com/document/d/1Bk4QahtaT-XzrMlHTkm0SVol3LoKXTrC9E7INxJBHrE/edit?usp=drive\_web) |
| |
| ## Async stack traces |
| |
| DevTools offers a way to show async stack traces by stitching together stack traces collected at the |
| location where the callback is passed, and the actual location of the exception inside the callback. |
| |
| The canonical example of this is throwing inside a `setTimeout` callback. The async stack trace will |
| consist of the stack trace of the error plus a stack trace captured at the `setTimeout` call-site. |
| |
| 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`. |
| 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. |
| |
| ### Affected |
| |
| NLFs touching promises or callback scheduling. |
| |
| ### How to test |
| |
| Throw or pause inside a new language feature and check either the call stack sidebar panel in "Sources" |
| or use a `console.trace` and check the async stack trace in the console. |
| |
| ### Test cases |
| |
| Test cases for async stack traces are mandatory, if there is any way the NLF interacts with callback scheduling or |
| 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)). |
| |
| ## Catch prediction |
| |
| Aside from offering stack traces, V8's debugger supports DevTools' pause-on-exception feature. This comes in two flavors: |
| pause on all exceptions, and pause on uncaught exceptions. In both cases, we break at the throw site (not at the `catch`, |
| or any rethrow via `try`-`finally`). |
| |
| For the former, this is as easy as breaking in the debugger on `Isolate::Throw()`. For the latter, we have to predict whether |
| the thrown exception will be caught or otherwise handled (for example by a `Promise`'s catch handler). |
| |
| ### Affected |
| |
| NLFs that can cause an exception to be thrown, or can call into another function that throws. |
| |
| ### How to test |
| |
| When pause-on-exception is enabled, and throwing inside the NLF or with it on the stack, the script should pause as expected. |
| |
| Repeat with the "pause on caught exception" checkbox checked. |
| |
| ### Test cases |
| |
| Test cases for exception prediction is mandatory, if there is any way the NLF can interact with exceptions, be it by throwing |
| exceptions, or by relying on `try`-`catch` or `try`-`finally` in its implementation. Look for `mjsunit` tests that contain the |
| string `setBreakOnException` or `setBreakOnUncaughtException`. |
| |
| ### Reading material |
| |
| [Design doc for exception prediction for async-await](https://docs.google.com/a/google.com/document/d/1ef1drN6RTRN7iDB8FXJg\_JJZFNObCFbM1UjZWK\_ORUE/edit?usp=drive\_web) |
| |
| |
| ## Breakpoints |
| |
| One of the most important features is setting break points. The semantics should be obvious. |
| |
| Break locations are function calls, return sites, and statements. Special care are necessary for loops: for example, in `for`-loops |
| we do want to be able to break separately at the loop entry, condition evaluation, and increment. |
| |
| When setting a break point at an arbitrary source position, we actually check for the closest breakable source position, and move |
| the break point there. Consecutive debug break events at the same source position are ignored by the debugger. |
| |
| ### Affected |
| |
| NLFs that change generated code, and especially once that introduce new break locations. |
| |
| ### How to test |
| |
| Open DevTools and set break points in parts of script related to the NLF, then run the script. |
| |
| ### Test cases |
| |
| Look for `mjsunit` tests with `debug-break` in their names. |
| |
| |
| ## Stepping |
| |
| Stepping is the logical consequence to breakpoints, and is based on the same mechanism in the debugger. We differentiate between |
| |
| * Step out, which takes us to the next break location in the caller. |
| * Step next, which takes us to the next break location while ignoring calls into other functions. Note that this includes recursive |
| calls. Step next at a return site is equivalent to a step out. |
| * Step in, which takes us to the next break location including calls into another function. |
| * Step frame, which takes us to another function, either a callee or a caller. This is used for framework blackboxing, where the V8 |
| inspector is not interested in stepping in the current function, and wants to be notified once we arrive at another function |
| |
| ### Affected |
| |
| NLFs that are affect breakpoints |
| |
| ### How to test |
| |
| Break inside the part of script related to the NLF, and try stepping in, next, and out. |
| |
| ### Test cases |
| |
| Look for `mjsunit` tests with `debug-step` in their names. |
| |
| ### Reading material |
| |
| [Design doc on stepping in async-await](https://docs.google.com/a/google.com/document/d/1nj3nMlQVEFlq57dA-K8wFxdOB5ovvNuwNbWxBhcBOKE/edit?usp=drive\_web) |
| |
| |
| ## Frame inspector |
| |
| 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, |
| the break location is that of the debug break. For frames below the break location is the call site leading to the frame above. |
| |
| For each frame, we can |
| |
| - inspect the scope chain at the break location with the scope iterator, |
| - find out whether it's called as constructor, |
| - find out whether we are at a return position, |
| - get the function being called, |
| - get the receiver, |
| - get the arguments, and |
| - get the number of stack-allocated locals. |
| |
| For optimized code, we use the deoptimizer to get hold of receiver, arguments and stack locals, but this is often not possible, and we |
| get the `optimzed_out` sentinel value. |
| |
| ### Affected |
| |
| NLFs that affect the way V8 calls functions. |
| |
| ### How to test |
| |
| When paused inside the function affected by the NLF, the Call Stack view in the DevTools' Source panel should show useful information. |
| |
| ### Test cases |
| |
| Take a look at `test/mjsunit/wasm/frame-inspection.js`. |
| |
| |
| ## Scope iterator |
| |
| 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 |
| the current function, but also scopes inside it, for example inner block scopes, catch scopes, with scopes, etc. |
| |
| For each scope inside the current function, we can materialize an object representing local variables belonging to it. For scopes |
| outside the current function this is not possible. |
| |
| We can use the scope iterator to alter the value of local variables, unless we are inside an optimized frame. |
| |
| ### Affected |
| |
| NLFs that introduce new scopes. |
| |
| ### How to test |
| |
| When paused in DevTools inside the scope introduced by the NLF, the "Scope" view on the Sources panel should show useful information. |
| Scopes that are introduced by the NLF for desugaring purposes may better be hidden. |
| |
| ### Test cases |
| |
| Take a look at `test/mjsunit/debug-scopes.js`. |
| |
| ### Reading material |
| |
| [CL that introduces hidden scopes](https://chromium.googlesource.com/v8/v8/+/672983830f36222d90748ff588831b6dae565c38) |
| |
| |
| ## Debug evaluate |
| |
| With debug-evaluate, V8 offers a way to evaluate scripts at a break, attempting to behave as if the script code was executed |
| right at the break location. It is based on the frame inspector and the scope iterator. |
| |
| It works by creating a context chain that not only references contexts on the current context chain, but also contains the |
| materialized stack, including arguments object and the receiver. The script is then compiled and executed inside this context chain. |
| |
| There are some limitations, and special attention has to be paid to variable name shadowing. |
| |
| Side-effect-free debug-evaluate statically determines whether a function should throw. You should check whether to update the |
| whitelist in `src/debug/debug-evaluate.cc`. |
| |
| ### Affected |
| |
| NLFs that are also affected by the scope iterator and frame inspector. |
| |
| ### How to test |
| |
| Use the DevTools console to run scripts at a debug break. In particular the preview shown in the DevTools console by default indicates |
| whether the side-effect detection works correctly (i.e. whether you updated the whitelist correctly). |
| |
| ### Test cases |
| |
| Look for mjsunit tests with "debug-evaluate" in their names. |
| |
| ### Reading material |
| |
| 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) |
| |
| 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) |
| |
| |
| ## Code Coverage |
| |
| Code coverage gathers execution counts and exposes them to developers through the Inspector protocol. |
| |
| ### Affected |
| |
| NLFs that contain control flow (e.g branches, loops, etc.). |
| |
| ### How to test |
| |
| Run `d8` with `--lcov` and check whether the produced coverage information is correct. E.g. like this: |
| |
| ``` |
| ./d8 --lcov=cov.info test.js |
| genhtml cov.info -o coverage |
| ``` |
| |
| Then navigate your browser to `coverage/index.html`. |
| |
| ### Test cases |
| |
| `test/mjsunit/code-coverage-block.js` |
| |
| ### Reading material |
| |
| Design doc: [go/v8-designdoc-block-code-coverage](http://go/v8-designdoc-block-code-coverage) |
| |
| |
| ## Heap profiler |
| |
| 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. |
| |
| ### Affected |
| |
| NLFs that change object layouts or introduce new object types |
| |
| ### How to test |
| |
| 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. |
| |
| ### Test cases |
| |
| Take a look at `test/cctest/test-heap-profiler.cc`. |
| |
| ## Restart frame |
| |
| 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 |
| 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 |
| as that would require rolling back the underlying generator. |
| |
| ### Affected |
| |
| NLFs that change function execution or require a function to carry state (e.g. async functions |
| need a generator that can't be reset). |
| |
| ### How to test |
| |
| 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 |
| the "Restart frame" menu item must be disabled, otherwise the frame must be properly restarted. |
| |
| ### Test cases |
| |
| Take a look in `test/inspector/debugger/restart-frame/*`. |
| |
| ## LiveEdit |
| |
| 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 |
| of editing the function we are paused in and restarting said function afterwards. |
| |
| ### Affected |
| |
| NLFs that affect code execution |
| |
| ### How to test |
| |
| 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. |
| |
| ### Test cases |
| |
| Look for `mjsunit` tests with `liveedit` in their names. |