Skip to content

Commit 4fbc1f2

Browse files
motiz88facebook-github-bot
authored andcommitted
Include stack traces in console messages (#44150)
Summary: Pull Request resolved: #44150 Changelog: [Internal] * Adds the `RuntimeTargetDelegate::captureStackTrace` method for capturing stack traces during JS execution. The returned stack traces are opaque to RN, but may be passed back into the `RuntimeTargetDelegate`, particularly through the `addConsoleMessage` method. * Implements `captureStackTrace` for Hermes (based on D55757947). * Integrates `captureStackTrace` into the `console` handler (`RuntimeTargetConsole`) Reviewed By: hoxyq Differential Revision: D55474512 fbshipit-source-id: 3547d756844fa24c24cd9bcdc507b33c6ab673a9
1 parent 2c9574e commit 4fbc1f2

File tree

11 files changed

+261
-23
lines changed

11 files changed

+261
-23
lines changed

packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.cpp

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@ namespace facebook::react::jsinspector_modern {
2929

3030
#ifdef HERMES_ENABLE_DEBUGGER
3131
class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
32+
using HermesStackTrace = debugger::StackTrace;
33+
34+
class HermesStackTraceWrapper : public StackTrace {
35+
public:
36+
explicit HermesStackTraceWrapper(HermesStackTrace&& hermesStackTrace)
37+
: hermesStackTrace_{std::move(hermesStackTrace)} {}
38+
39+
HermesStackTrace& operator*() {
40+
return hermesStackTrace_;
41+
}
42+
43+
HermesStackTrace* operator->() {
44+
return &hermesStackTrace_;
45+
}
46+
47+
private:
48+
HermesStackTrace hermesStackTrace_;
49+
};
50+
3251
public:
3352
explicit Impl(
3453
HermesRuntimeTargetDelegate& delegate,
@@ -118,14 +137,36 @@ class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
118137
default:
119138
throw std::logic_error{"Unknown console message type"};
120139
}
121-
cdpDebugAPI_->addConsoleMessage(
122-
HermesConsoleMessage{message.timestamp, type, std::move(message.args)});
140+
HermesStackTrace hermesStackTrace{};
141+
if (auto hermesStackTraceWrapper =
142+
dynamic_cast<HermesStackTraceWrapper*>(message.stackTrace.get())) {
143+
hermesStackTrace = std::move(**hermesStackTraceWrapper);
144+
}
145+
HermesConsoleMessage hermesConsoleMessage{
146+
message.timestamp, type, std::move(message.args)};
147+
// NOTE: HermesConsoleMessage should really have a constructor that takes a
148+
// stack trace.
149+
hermesConsoleMessage.stackTrace = std::move(hermesStackTrace);
150+
cdpDebugAPI_->addConsoleMessage(std::move(hermesConsoleMessage));
123151
}
124152

125153
bool supportsConsole() const override {
126154
return true;
127155
}
128156

157+
std::unique_ptr<StackTrace> captureStackTrace(
158+
jsi::Runtime& /* runtime */,
159+
size_t /* framesToSkip */) override {
160+
// TODO(moti): Pass framesToSkip to Hermes. Ignoring framesToSkip happens
161+
// to work for our current use case, because the HostFunction frame we want
162+
// to skip is stripped by CDPDebugAPI::addConsoleMessage before being sent
163+
// to the client. This is still conceptually wrong and could block us from
164+
// properly representing the stack trace in other use cases, where native
165+
// frames aren't stripped on serialisation.
166+
return std::make_unique<HermesStackTraceWrapper>(
167+
runtime_->getDebugger().captureStackTrace());
168+
}
169+
129170
private:
130171
HermesRuntimeTargetDelegate& delegate_;
131172
std::shared_ptr<HermesRuntime> runtime_;
@@ -181,6 +222,12 @@ bool HermesRuntimeTargetDelegate::supportsConsole() const {
181222
return impl_->supportsConsole();
182223
}
183224

225+
std::unique_ptr<StackTrace> HermesRuntimeTargetDelegate::captureStackTrace(
226+
jsi::Runtime& runtime,
227+
size_t framesToSkip) {
228+
return impl_->captureStackTrace(runtime, framesToSkip);
229+
}
230+
184231
#ifdef HERMES_ENABLE_DEBUGGER
185232
CDPDebugAPI& HermesRuntimeTargetDelegate::getCDPDebugAPI() {
186233
return impl_->getCDPDebugAPI();

packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeTargetDelegate.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ class HermesRuntimeTargetDelegate : public RuntimeTargetDelegate {
5050

5151
bool supportsConsole() const override;
5252

53+
std::unique_ptr<StackTrace> captureStackTrace(
54+
jsi::Runtime& runtime,
55+
size_t framesToSkip) override;
56+
5357
private:
5458
// We use the private implementation idiom to ensure this class has the same
5559
// layout regardless of whether HERMES_ENABLE_DEBUGGER is defined. The net

packages/react-native/ReactCommon/jsinspector-modern/ConsoleMessage.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#pragma once
99

10+
#include "StackTrace.h"
11+
1012
#include <vector>
1113

1214
#include <jsi/jsi.h>
@@ -57,18 +59,23 @@ struct SimpleConsoleMessage {
5759
};
5860

5961
/**
60-
* A console message made of JSI values.
62+
* A console message made of JSI values and a captured stack trace.
6163
*/
6264
struct ConsoleMessage {
6365
double timestamp;
6466
ConsoleAPIType type;
6567
std::vector<jsi::Value> args;
68+
std::unique_ptr<StackTrace> stackTrace;
6669

6770
ConsoleMessage(
6871
double timestamp,
6972
ConsoleAPIType type,
70-
std::vector<jsi::Value> args)
71-
: timestamp(timestamp), type(type), args(std::move(args)) {}
73+
std::vector<jsi::Value> args,
74+
std::unique_ptr<StackTrace> stackTrace = StackTrace::empty())
75+
: timestamp(timestamp),
76+
type(type),
77+
args(std::move(args)),
78+
stackTrace(std::move(stackTrace)) {}
7279

7380
ConsoleMessage(jsi::Runtime& runtime, SimpleConsoleMessage message);
7481

packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,12 @@ bool FallbackRuntimeTargetDelegate::supportsConsole() const {
3636
return false;
3737
}
3838

39+
std::unique_ptr<StackTrace> FallbackRuntimeTargetDelegate::captureStackTrace(
40+
jsi::Runtime& /*runtime*/,
41+
size_t /*framesToSkip*/
42+
) {
43+
// TODO: Parse a JS `Error().stack` as a fallback
44+
return std::make_unique<StackTrace>();
45+
}
46+
3947
} // namespace facebook::react::jsinspector_modern

packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeTargetDelegate.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ class FallbackRuntimeTargetDelegate : public RuntimeTargetDelegate {
3636

3737
bool supportsConsole() const override;
3838

39+
std::unique_ptr<StackTrace> captureStackTrace(
40+
jsi::Runtime& runtime,
41+
size_t framesToSkip) override;
42+
3943
private:
4044
std::string engineDescription_;
4145
};

packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "InspectorInterfaces.h"
1515
#include "RuntimeAgent.h"
1616
#include "ScopedExecutor.h"
17+
#include "StackTrace.h"
1718
#include "WeakList.h"
1819

1920
#include <memory>
@@ -73,6 +74,22 @@ class RuntimeTargetDelegate {
7374
* \c addConsoleMessage MAY be called even if this method returns false.
7475
*/
7576
virtual bool supportsConsole() const = 0;
77+
78+
/**
79+
* \returns an opaque representation of a stack trace. This may be passed back
80+
* to the `RuntimeTargetDelegate` as part of `addConsoleMessage` or other APIs
81+
* that report stack traces.
82+
* \param framesToSkip The number of call frames to skip. The first call frame
83+
* is the topmost (current) frame on the Runtime's call stack, which will
84+
* typically be the (native) JSI HostFunction that called this method.
85+
* \note The method is called on the JS thread, and receives a valid reference
86+
* to the current \c jsi::Runtime. The callee MAY use its own intrinsic
87+
* Runtime reference, if it has one, without checking it for equivalence with
88+
* the one provided here.
89+
*/
90+
virtual std::unique_ptr<StackTrace> captureStackTrace(
91+
jsi::Runtime& runtime,
92+
size_t framesToSkip = 0) = 0;
7693
};
7794

7895
/**

packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ void RuntimeTarget::installConsoleHandler() {
187187
size_t count,
188188
RuntimeTargetDelegate& runtimeTargetDelegate,
189189
ConsoleState& state,
190-
double timestampMs)>&& body) {
190+
double timestampMs,
191+
std::unique_ptr<StackTrace> stackTrace)>&& body) {
191192
console.setProperty(
192193
runtime,
193194
methodName,
@@ -210,13 +211,17 @@ void RuntimeTarget::installConsoleHandler() {
210211
body = std::move(body),
211212
state,
212213
timestampMs](auto& runtimeTargetDelegate) {
214+
auto stackTrace =
215+
runtimeTargetDelegate.captureStackTrace(
216+
runtime, /* framesToSkip */ 1);
213217
body(
214218
runtime,
215219
args,
216220
count,
217221
runtimeTargetDelegate,
218222
*state,
219-
timestampMs);
223+
timestampMs,
224+
std::move(stackTrace));
220225
});
221226
return jsi::Value::undefined();
222227
})));
@@ -232,7 +237,8 @@ void RuntimeTarget::installConsoleHandler() {
232237
size_t count,
233238
RuntimeTargetDelegate& runtimeTargetDelegate,
234239
ConsoleState& state,
235-
auto timestampMs) {
240+
auto timestampMs,
241+
std::unique_ptr<StackTrace> stackTrace) {
236242
std::string label = "default";
237243
if (count > 0 && !args[0].isUndefined()) {
238244
label = args[0].toString(runtime).utf8(runtime);
@@ -247,7 +253,11 @@ void RuntimeTarget::installConsoleHandler() {
247253
vec.emplace_back(jsi::String::createFromUtf8(
248254
runtime, label + ": "s + std::to_string(it->second)));
249255
runtimeTargetDelegate.addConsoleMessage(
250-
runtime, {timestampMs, ConsoleAPIType::kCount, std::move(vec)});
256+
runtime,
257+
{timestampMs,
258+
ConsoleAPIType::kCount,
259+
std::move(vec),
260+
std::move(stackTrace)});
251261
});
252262

253263
/**
@@ -260,7 +270,8 @@ void RuntimeTarget::installConsoleHandler() {
260270
size_t count,
261271
RuntimeTargetDelegate& runtimeTargetDelegate,
262272
ConsoleState& state,
263-
auto timestampMs) {
273+
auto timestampMs,
274+
std::unique_ptr<StackTrace> stackTrace) {
264275
std::string label = "default";
265276
if (count > 0 && !args[0].isUndefined()) {
266277
label = args[0].toString(runtime).utf8(runtime);
@@ -272,7 +283,10 @@ void RuntimeTarget::installConsoleHandler() {
272283
runtime, "Count for '"s + label + "' does not exist"));
273284
runtimeTargetDelegate.addConsoleMessage(
274285
runtime,
275-
{timestampMs, ConsoleAPIType::kWarning, std::move(vec)});
286+
{timestampMs,
287+
ConsoleAPIType::kWarning,
288+
std::move(vec),
289+
std::move(stackTrace)});
276290
} else {
277291
it->second = 0;
278292
}
@@ -288,7 +302,8 @@ void RuntimeTarget::installConsoleHandler() {
288302
size_t count,
289303
RuntimeTargetDelegate& runtimeTargetDelegate,
290304
ConsoleState& state,
291-
auto timestampMs) {
305+
auto timestampMs,
306+
std::unique_ptr<StackTrace> stackTrace) {
292307
std::string label = "default";
293308
if (count > 0 && !args[0].isUndefined()) {
294309
label = args[0].toString(runtime).utf8(runtime);
@@ -302,7 +317,10 @@ void RuntimeTarget::installConsoleHandler() {
302317
runtime, "Timer '"s + label + "' already exists"));
303318
runtimeTargetDelegate.addConsoleMessage(
304319
runtime,
305-
{timestampMs, ConsoleAPIType::kWarning, std::move(vec)});
320+
{timestampMs,
321+
ConsoleAPIType::kWarning,
322+
std::move(vec),
323+
std::move(stackTrace)});
306324
}
307325
});
308326

@@ -316,7 +334,8 @@ void RuntimeTarget::installConsoleHandler() {
316334
size_t count,
317335
RuntimeTargetDelegate& runtimeTargetDelegate,
318336
ConsoleState& state,
319-
auto timestampMs) {
337+
auto timestampMs,
338+
std::unique_ptr<StackTrace> stackTrace) {
320339
std::string label = "default";
321340
if (count > 0 && !args[0].isUndefined()) {
322341
label = args[0].toString(runtime).utf8(runtime);
@@ -328,7 +347,10 @@ void RuntimeTarget::installConsoleHandler() {
328347
runtime, "Timer '"s + label + "' does not exist"));
329348
runtimeTargetDelegate.addConsoleMessage(
330349
runtime,
331-
{timestampMs, ConsoleAPIType::kWarning, std::move(vec)});
350+
{timestampMs,
351+
ConsoleAPIType::kWarning,
352+
std::move(vec),
353+
std::move(stackTrace)});
332354
} else {
333355
std::vector<jsi::Value> vec;
334356
vec.emplace_back(jsi::String::createFromUtf8(
@@ -338,7 +360,10 @@ void RuntimeTarget::installConsoleHandler() {
338360
state.timerTable.erase(it);
339361
runtimeTargetDelegate.addConsoleMessage(
340362
runtime,
341-
{timestampMs, ConsoleAPIType::kTimeEnd, std::move(vec)});
363+
{timestampMs,
364+
ConsoleAPIType::kTimeEnd,
365+
std::move(vec),
366+
std::move(stackTrace)});
342367
}
343368
});
344369

@@ -352,7 +377,8 @@ void RuntimeTarget::installConsoleHandler() {
352377
size_t count,
353378
RuntimeTargetDelegate& runtimeTargetDelegate,
354379
ConsoleState& state,
355-
auto timestampMs) {
380+
auto timestampMs,
381+
std::unique_ptr<StackTrace> stackTrace) {
356382
std::string label = "default";
357383
if (count > 0 && !args[0].isUndefined()) {
358384
label = args[0].toString(runtime).utf8(runtime);
@@ -364,7 +390,10 @@ void RuntimeTarget::installConsoleHandler() {
364390
runtime, "Timer '"s + label + "' does not exist"));
365391
runtimeTargetDelegate.addConsoleMessage(
366392
runtime,
367-
{timestampMs, ConsoleAPIType::kWarning, std::move(vec)});
393+
{timestampMs,
394+
ConsoleAPIType::kWarning,
395+
std::move(vec),
396+
std::move(stackTrace)});
368397
} else {
369398
std::vector<jsi::Value> vec;
370399
vec.emplace_back(jsi::String::createFromUtf8(
@@ -377,7 +406,11 @@ void RuntimeTarget::installConsoleHandler() {
377406
}
378407
}
379408
runtimeTargetDelegate.addConsoleMessage(
380-
runtime, {timestampMs, ConsoleAPIType::kLog, std::move(vec)});
409+
runtime,
410+
{timestampMs,
411+
ConsoleAPIType::kLog,
412+
std::move(vec),
413+
std::move(stackTrace)});
381414
}
382415
});
383416

@@ -391,7 +424,8 @@ void RuntimeTarget::installConsoleHandler() {
391424
size_t count,
392425
RuntimeTargetDelegate& runtimeTargetDelegate,
393426
ConsoleState& /*state*/,
394-
auto timestampMs) {
427+
auto timestampMs,
428+
std::unique_ptr<StackTrace> stackTrace) {
395429
if (count >= 1 && toBoolean(runtime, args[0])) {
396430
return;
397431
}
@@ -420,7 +454,8 @@ void RuntimeTarget::installConsoleHandler() {
420454
ConsoleAPIType::kAssert,
421455
std::vector<jsi::Value>(
422456
make_move_iterator(data.begin()),
423-
make_move_iterator(data.end()))});
457+
make_move_iterator(data.end())),
458+
std::move(stackTrace)});
424459
});
425460

426461
for (auto& [name, type] : kForwardingConsoleMethods) {
@@ -432,13 +467,15 @@ void RuntimeTarget::installConsoleHandler() {
432467
size_t count,
433468
RuntimeTargetDelegate& runtimeTargetDelegate,
434469
ConsoleState& /*state*/,
435-
auto timestampMs) {
470+
auto timestampMs,
471+
std::unique_ptr<StackTrace> stackTrace) {
436472
std::vector<jsi::Value> argsVec;
437473
for (size_t i = 0; i != count; ++i) {
438474
argsVec.emplace_back(runtime, args[i]);
439475
}
440476
runtimeTargetDelegate.addConsoleMessage(
441-
runtime, {timestampMs, type, std::move(argsVec)});
477+
runtime,
478+
{timestampMs, type, std::move(argsVec), std::move(stackTrace)});
442479
});
443480
}
444481

0 commit comments

Comments
 (0)