mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-09-28 21:24:26 +08:00
The CL adds a dedicated section for "async stack traces" and "restart frame" with updated links. R=bmeurer@chromium.org Fixed: 374892098 Change-Id: I09c6a6edfeac11ef05a0aab775a2f0f03f1f58d6 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5952204 Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Auto-Submit: Simon Zünd <szuend@chromium.org>
377 lines
16 KiB
Markdown
377 lines
16 KiB
Markdown
# 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.
|