Skip to content

Commit 08b4a3d

Browse files
committed
Add user exception metadata to internal exception
1 parent 2936846 commit 08b4a3d

File tree

5 files changed

+76
-0
lines changed

5 files changed

+76
-0
lines changed

src/workerd/jsg/BUILD.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ wd_cc_library(
118118
tags = ["no-clang-tidy"],
119119
deps = [
120120
":exception",
121+
":exception_metadata_capnp",
121122
":macro-meta",
122123
":memory-tracker",
123124
":meta",
@@ -222,6 +223,12 @@ wd_cc_capnp_library(
222223
visibility = ["//visibility:public"],
223224
)
224225

226+
wd_cc_capnp_library(
227+
name = "exception_metadata_capnp",
228+
srcs = ["exception-metadata.capnp"],
229+
visibility = ["//visibility:public"],
230+
)
231+
225232
js_capnp_library(
226233
name = "rtti_capnp_js",
227234
srcs = ["rtti.capnp"],
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@0xa9ae63464030fcef;
2+
# Schema for JavaScript exception metadata passed through KJ exception details
3+
4+
using Cxx = import "/capnp/c++.capnp";
5+
$Cxx.namespace("workerd::jsg");
6+
$Cxx.allowCancellation;
7+
8+
struct JsExceptionMetadata {
9+
# JavaScript error type (e.g., "Error", "TypeError", "RangeError")
10+
errorType @0 :Text;
11+
12+
# Full stack trace string as produced by V8
13+
# Example: "Error: User-thrown exception\n at Object.fetch (foo:4:11)"
14+
stackTrace @1 :Text;
15+
}

src/workerd/jsg/util.c++

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
#include "ser.h"
88
#include "setup.h"
99

10+
#include <workerd/jsg/exception-metadata.capnp.h>
11+
1012
#include <openssl/rand.h>
1113

14+
#include <capnp/message.h>
15+
#include <capnp/serialize.h>
1216
#include <kj/debug.h>
1317

1418
#include <cstdlib>
@@ -454,6 +458,48 @@ void addExceptionDetail(Lock& js, kj::Exception& exception, v8::Local<v8::Value>
454458
}
455459
}
456460

461+
void addJsExceptionMetadata(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle) {
462+
// Extract JavaScript error type and stack trace
463+
if (!handle->IsObject()) {
464+
return; // Not an error object, nothing to extract
465+
}
466+
467+
auto errorObj = jsg::JsObject(handle.As<v8::Object>());
468+
469+
// Build Cap'n Proto message
470+
capnp::MallocMessageBuilder message;
471+
auto metadata = message.initRoot<JsExceptionMetadata>();
472+
473+
// Limit for user-controlled fields (4KB)
474+
constexpr size_t MAX_FIELD_SIZE = 4096;
475+
476+
// Extract error name (e.g., "Error", "TypeError", "RangeError")
477+
auto nameProp = errorObj.get(js, "name"_kj);
478+
if (nameProp.isString()) {
479+
auto errorType = nameProp.toString(js);
480+
// Truncate to 4KB if needed
481+
if (errorType.size() > MAX_FIELD_SIZE) {
482+
errorType = kj::str(errorType.slice(0, MAX_FIELD_SIZE));
483+
}
484+
metadata.setErrorType(errorType);
485+
}
486+
487+
// Extract stack trace string
488+
auto stackProp = errorObj.get(js, "stack"_kj);
489+
if (stackProp.isString()) {
490+
auto stackTrace = stackProp.toString(js);
491+
// Truncate to 4KB if needed
492+
if (stackTrace.size() > MAX_FIELD_SIZE) {
493+
stackTrace = kj::str(stackTrace.slice(0, MAX_FIELD_SIZE));
494+
}
495+
metadata.setStackTrace(stackTrace);
496+
}
497+
498+
// Serialize to bytes using Cap'n Proto
499+
auto words = capnp::messageToFlatArray(message);
500+
exception.setDetail(JS_EXCEPTION_METADATA_DETAIL_ID, kj::heapArray(words.asBytes()));
501+
}
502+
457503
static kj::String typeErrorMessage(TypeErrorContext c, const char* expectedType) {
458504
kj::String type;
459505

src/workerd/jsg/util.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,16 @@ void throwInternalError(
8989

9090
constexpr kj::Exception::DetailTypeId TUNNELED_EXCEPTION_DETAIL_ID = 0xe8027292171b1646ull;
9191

92+
// Detail type for JavaScript exception metadata (error type and stack trace)
93+
constexpr kj::Exception::DetailTypeId JS_EXCEPTION_METADATA_DETAIL_ID = 0xa9ae63464030fcefull;
94+
9295
// Add a serialized copy of the exception value to the KJ exception, as a "detail".
9396
void addExceptionDetail(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle);
9497

98+
// Extract and add JavaScript exception metadata (error type and stack trace) to the KJ exception.
99+
// Serializes using Cap'n Proto schema defined in exception-metadata.capnp.
100+
void addJsExceptionMetadata(Lock& js, kj::Exception& exception, v8::Local<v8::Value> handle);
101+
95102
struct TypeErrorContext {
96103
enum Kind : uint8_t {
97104
METHOD_ARGUMENT, // has type name, member (method) name, and argument index

src/workerd/jsg/value.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,7 @@ class ExceptionWrapper {
15031503
}();
15041504

15051505
addExceptionDetail(js, result, handle);
1506+
addJsExceptionMetadata(js, result, handle);
15061507
return result;
15071508
}
15081509
};

0 commit comments

Comments
 (0)