Skip to content

Commit

Permalink
Include stack traces in console messages (#44150)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
motiz88 authored and facebook-github-bot committed Apr 18, 2024
1 parent 2c9574e commit 4fbc1f2
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ namespace facebook::react::jsinspector_modern {

#ifdef HERMES_ENABLE_DEBUGGER
class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
using HermesStackTrace = debugger::StackTrace;

class HermesStackTraceWrapper : public StackTrace {
public:
explicit HermesStackTraceWrapper(HermesStackTrace&& hermesStackTrace)
: hermesStackTrace_{std::move(hermesStackTrace)} {}

HermesStackTrace& operator*() {
return hermesStackTrace_;
}

HermesStackTrace* operator->() {
return &hermesStackTrace_;
}

private:
HermesStackTrace hermesStackTrace_;
};

public:
explicit Impl(
HermesRuntimeTargetDelegate& delegate,
Expand Down Expand Up @@ -118,14 +137,36 @@ class HermesRuntimeTargetDelegate::Impl final : public RuntimeTargetDelegate {
default:
throw std::logic_error{"Unknown console message type"};
}
cdpDebugAPI_->addConsoleMessage(
HermesConsoleMessage{message.timestamp, type, std::move(message.args)});
HermesStackTrace hermesStackTrace{};
if (auto hermesStackTraceWrapper =
dynamic_cast<HermesStackTraceWrapper*>(message.stackTrace.get())) {
hermesStackTrace = std::move(**hermesStackTraceWrapper);
}
HermesConsoleMessage hermesConsoleMessage{
message.timestamp, type, std::move(message.args)};
// NOTE: HermesConsoleMessage should really have a constructor that takes a
// stack trace.
hermesConsoleMessage.stackTrace = std::move(hermesStackTrace);
cdpDebugAPI_->addConsoleMessage(std::move(hermesConsoleMessage));
}

bool supportsConsole() const override {
return true;
}

std::unique_ptr<StackTrace> captureStackTrace(
jsi::Runtime& /* runtime */,
size_t /* framesToSkip */) override {
// TODO(moti): Pass framesToSkip to Hermes. Ignoring framesToSkip happens
// to work for our current use case, because the HostFunction frame we want
// to skip is stripped by CDPDebugAPI::addConsoleMessage before being sent
// to the client. This is still conceptually wrong and could block us from
// properly representing the stack trace in other use cases, where native
// frames aren't stripped on serialisation.
return std::make_unique<HermesStackTraceWrapper>(
runtime_->getDebugger().captureStackTrace());
}

private:
HermesRuntimeTargetDelegate& delegate_;
std::shared_ptr<HermesRuntime> runtime_;
Expand Down Expand Up @@ -181,6 +222,12 @@ bool HermesRuntimeTargetDelegate::supportsConsole() const {
return impl_->supportsConsole();
}

std::unique_ptr<StackTrace> HermesRuntimeTargetDelegate::captureStackTrace(
jsi::Runtime& runtime,
size_t framesToSkip) {
return impl_->captureStackTrace(runtime, framesToSkip);
}

#ifdef HERMES_ENABLE_DEBUGGER
CDPDebugAPI& HermesRuntimeTargetDelegate::getCDPDebugAPI() {
return impl_->getCDPDebugAPI();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ class HermesRuntimeTargetDelegate : public RuntimeTargetDelegate {

bool supportsConsole() const override;

std::unique_ptr<StackTrace> captureStackTrace(
jsi::Runtime& runtime,
size_t framesToSkip) override;

private:
// We use the private implementation idiom to ensure this class has the same
// layout regardless of whether HERMES_ENABLE_DEBUGGER is defined. The net
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#pragma once

#include "StackTrace.h"

#include <vector>

#include <jsi/jsi.h>
Expand Down Expand Up @@ -57,18 +59,23 @@ struct SimpleConsoleMessage {
};

/**
* A console message made of JSI values.
* A console message made of JSI values and a captured stack trace.
*/
struct ConsoleMessage {
double timestamp;
ConsoleAPIType type;
std::vector<jsi::Value> args;
std::unique_ptr<StackTrace> stackTrace;

ConsoleMessage(
double timestamp,
ConsoleAPIType type,
std::vector<jsi::Value> args)
: timestamp(timestamp), type(type), args(std::move(args)) {}
std::vector<jsi::Value> args,
std::unique_ptr<StackTrace> stackTrace = StackTrace::empty())
: timestamp(timestamp),
type(type),
args(std::move(args)),
stackTrace(std::move(stackTrace)) {}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,12 @@ bool FallbackRuntimeTargetDelegate::supportsConsole() const {
return false;
}

std::unique_ptr<StackTrace> FallbackRuntimeTargetDelegate::captureStackTrace(
jsi::Runtime& /*runtime*/,
size_t /*framesToSkip*/
) {
// TODO: Parse a JS `Error().stack` as a fallback
return std::make_unique<StackTrace>();
}

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class FallbackRuntimeTargetDelegate : public RuntimeTargetDelegate {

bool supportsConsole() const override;

std::unique_ptr<StackTrace> captureStackTrace(
jsi::Runtime& runtime,
size_t framesToSkip) override;

private:
std::string engineDescription_;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "InspectorInterfaces.h"
#include "RuntimeAgent.h"
#include "ScopedExecutor.h"
#include "StackTrace.h"
#include "WeakList.h"

#include <memory>
Expand Down Expand Up @@ -73,6 +74,22 @@ class RuntimeTargetDelegate {
* \c addConsoleMessage MAY be called even if this method returns false.
*/
virtual bool supportsConsole() const = 0;

/**
* \returns an opaque representation of a stack trace. This may be passed back
* to the `RuntimeTargetDelegate` as part of `addConsoleMessage` or other APIs
* that report stack traces.
* \param framesToSkip The number of call frames to skip. The first call frame
* is the topmost (current) frame on the Runtime's call stack, which will
* typically be the (native) JSI HostFunction that called this method.
* \note The method is called on the JS thread, and receives a valid reference
* to the current \c jsi::Runtime. The callee MAY use its own intrinsic
* Runtime reference, if it has one, without checking it for equivalence with
* the one provided here.
*/
virtual std::unique_ptr<StackTrace> captureStackTrace(
jsi::Runtime& runtime,
size_t framesToSkip = 0) = 0;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ void RuntimeTarget::installConsoleHandler() {
size_t count,
RuntimeTargetDelegate& runtimeTargetDelegate,
ConsoleState& state,
double timestampMs)>&& body) {
double timestampMs,
std::unique_ptr<StackTrace> stackTrace)>&& body) {
console.setProperty(
runtime,
methodName,
Expand All @@ -210,13 +211,17 @@ void RuntimeTarget::installConsoleHandler() {
body = std::move(body),
state,
timestampMs](auto& runtimeTargetDelegate) {
auto stackTrace =
runtimeTargetDelegate.captureStackTrace(
runtime, /* framesToSkip */ 1);
body(
runtime,
args,
count,
runtimeTargetDelegate,
*state,
timestampMs);
timestampMs,
std::move(stackTrace));
});
return jsi::Value::undefined();
})));
Expand All @@ -232,7 +237,8 @@ void RuntimeTarget::installConsoleHandler() {
size_t count,
RuntimeTargetDelegate& runtimeTargetDelegate,
ConsoleState& state,
auto timestampMs) {
auto timestampMs,
std::unique_ptr<StackTrace> stackTrace) {
std::string label = "default";
if (count > 0 && !args[0].isUndefined()) {
label = args[0].toString(runtime).utf8(runtime);
Expand All @@ -247,7 +253,11 @@ void RuntimeTarget::installConsoleHandler() {
vec.emplace_back(jsi::String::createFromUtf8(
runtime, label + ": "s + std::to_string(it->second)));
runtimeTargetDelegate.addConsoleMessage(
runtime, {timestampMs, ConsoleAPIType::kCount, std::move(vec)});
runtime,
{timestampMs,
ConsoleAPIType::kCount,
std::move(vec),
std::move(stackTrace)});
});

/**
Expand All @@ -260,7 +270,8 @@ void RuntimeTarget::installConsoleHandler() {
size_t count,
RuntimeTargetDelegate& runtimeTargetDelegate,
ConsoleState& state,
auto timestampMs) {
auto timestampMs,
std::unique_ptr<StackTrace> stackTrace) {
std::string label = "default";
if (count > 0 && !args[0].isUndefined()) {
label = args[0].toString(runtime).utf8(runtime);
Expand All @@ -272,7 +283,10 @@ void RuntimeTarget::installConsoleHandler() {
runtime, "Count for '"s + label + "' does not exist"));
runtimeTargetDelegate.addConsoleMessage(
runtime,
{timestampMs, ConsoleAPIType::kWarning, std::move(vec)});
{timestampMs,
ConsoleAPIType::kWarning,
std::move(vec),
std::move(stackTrace)});
} else {
it->second = 0;
}
Expand All @@ -288,7 +302,8 @@ void RuntimeTarget::installConsoleHandler() {
size_t count,
RuntimeTargetDelegate& runtimeTargetDelegate,
ConsoleState& state,
auto timestampMs) {
auto timestampMs,
std::unique_ptr<StackTrace> stackTrace) {
std::string label = "default";
if (count > 0 && !args[0].isUndefined()) {
label = args[0].toString(runtime).utf8(runtime);
Expand All @@ -302,7 +317,10 @@ void RuntimeTarget::installConsoleHandler() {
runtime, "Timer '"s + label + "' already exists"));
runtimeTargetDelegate.addConsoleMessage(
runtime,
{timestampMs, ConsoleAPIType::kWarning, std::move(vec)});
{timestampMs,
ConsoleAPIType::kWarning,
std::move(vec),
std::move(stackTrace)});
}
});

Expand All @@ -316,7 +334,8 @@ void RuntimeTarget::installConsoleHandler() {
size_t count,
RuntimeTargetDelegate& runtimeTargetDelegate,
ConsoleState& state,
auto timestampMs) {
auto timestampMs,
std::unique_ptr<StackTrace> stackTrace) {
std::string label = "default";
if (count > 0 && !args[0].isUndefined()) {
label = args[0].toString(runtime).utf8(runtime);
Expand All @@ -328,7 +347,10 @@ void RuntimeTarget::installConsoleHandler() {
runtime, "Timer '"s + label + "' does not exist"));
runtimeTargetDelegate.addConsoleMessage(
runtime,
{timestampMs, ConsoleAPIType::kWarning, std::move(vec)});
{timestampMs,
ConsoleAPIType::kWarning,
std::move(vec),
std::move(stackTrace)});
} else {
std::vector<jsi::Value> vec;
vec.emplace_back(jsi::String::createFromUtf8(
Expand All @@ -338,7 +360,10 @@ void RuntimeTarget::installConsoleHandler() {
state.timerTable.erase(it);
runtimeTargetDelegate.addConsoleMessage(
runtime,
{timestampMs, ConsoleAPIType::kTimeEnd, std::move(vec)});
{timestampMs,
ConsoleAPIType::kTimeEnd,
std::move(vec),
std::move(stackTrace)});
}
});

Expand All @@ -352,7 +377,8 @@ void RuntimeTarget::installConsoleHandler() {
size_t count,
RuntimeTargetDelegate& runtimeTargetDelegate,
ConsoleState& state,
auto timestampMs) {
auto timestampMs,
std::unique_ptr<StackTrace> stackTrace) {
std::string label = "default";
if (count > 0 && !args[0].isUndefined()) {
label = args[0].toString(runtime).utf8(runtime);
Expand All @@ -364,7 +390,10 @@ void RuntimeTarget::installConsoleHandler() {
runtime, "Timer '"s + label + "' does not exist"));
runtimeTargetDelegate.addConsoleMessage(
runtime,
{timestampMs, ConsoleAPIType::kWarning, std::move(vec)});
{timestampMs,
ConsoleAPIType::kWarning,
std::move(vec),
std::move(stackTrace)});
} else {
std::vector<jsi::Value> vec;
vec.emplace_back(jsi::String::createFromUtf8(
Expand All @@ -377,7 +406,11 @@ void RuntimeTarget::installConsoleHandler() {
}
}
runtimeTargetDelegate.addConsoleMessage(
runtime, {timestampMs, ConsoleAPIType::kLog, std::move(vec)});
runtime,
{timestampMs,
ConsoleAPIType::kLog,
std::move(vec),
std::move(stackTrace)});
}
});

Expand All @@ -391,7 +424,8 @@ void RuntimeTarget::installConsoleHandler() {
size_t count,
RuntimeTargetDelegate& runtimeTargetDelegate,
ConsoleState& /*state*/,
auto timestampMs) {
auto timestampMs,
std::unique_ptr<StackTrace> stackTrace) {
if (count >= 1 && toBoolean(runtime, args[0])) {
return;
}
Expand Down Expand Up @@ -420,7 +454,8 @@ void RuntimeTarget::installConsoleHandler() {
ConsoleAPIType::kAssert,
std::vector<jsi::Value>(
make_move_iterator(data.begin()),
make_move_iterator(data.end()))});
make_move_iterator(data.end())),
std::move(stackTrace)});
});

for (auto& [name, type] : kForwardingConsoleMethods) {
Expand All @@ -432,13 +467,15 @@ void RuntimeTarget::installConsoleHandler() {
size_t count,
RuntimeTargetDelegate& runtimeTargetDelegate,
ConsoleState& /*state*/,
auto timestampMs) {
auto timestampMs,
std::unique_ptr<StackTrace> stackTrace) {
std::vector<jsi::Value> argsVec;
for (size_t i = 0; i != count; ++i) {
argsVec.emplace_back(runtime, args[i]);
}
runtimeTargetDelegate.addConsoleMessage(
runtime, {timestampMs, type, std::move(argsVec)});
runtime,
{timestampMs, type, std::move(argsVec), std::move(stackTrace)});
});
}

Expand Down

0 comments on commit 4fbc1f2

Please sign in to comment.