File
Blob: src/workerd/jsg/util-test.c++
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | #include "dom-exception.h" |
| 6 | #include "jsg-test.h" |
| 7 | #include "jsg.h" |
| 8 | |
| 9 | namespace workerd::jsg::test { |
| 10 | namespace { |
| 11 | |
| 12 | V8System v8System; |
| 13 | class ContextGlobalObject: public Object, public ContextGlobal {}; |
| 14 | |
| 15 | struct FreezeContext: public ContextGlobalObject { |
| 16 | void recursivelyFreeze(jsg::Lock& js, v8::Local<v8::Value> value) { |
| 17 | jsg::recursivelyFreeze(js.v8Isolate->GetCurrentContext(), value); |
| 18 | } |
| 19 | JSG_RESOURCE_TYPE(FreezeContext) { |
| 20 | JSG_METHOD(recursivelyFreeze); |
| 21 | } |
| 22 | }; |
| 23 | JSG_DECLARE_ISOLATE_TYPE(FreezeIsolate, FreezeContext); |
| 24 | |
| 25 | KJ_TEST("recursive freezing") { |
| 26 | Evaluator<FreezeContext, FreezeIsolate> e(v8System); |
| 27 | e.expectEval("let obj = { foo: [ { bar: 1 } ] };\n" |
| 28 | "recursivelyFreeze(obj);\n" |
| 29 | // We rely on non-strict mode here to silently discard our mutations. |
| 30 | "obj.foo[0].bar = 2;\n" |
| 31 | "obj.foo[0].baz = 3;\n" |
| 32 | "obj.foo[1] = { qux: 4 };\n" |
| 33 | "obj.bar = {};\n" |
| 34 | "JSON.stringify(obj);\n", |
| 35 | "string", "{\"foo\":[{\"bar\":1}]}"); |
| 36 | } |
| 37 | |
| 38 | // ======================================================================================== |
| 39 | |
| 40 | struct CloneContext: public ContextGlobalObject { |
| 41 | v8::Local<v8::Value> deepClone(jsg::Lock& js, v8::Local<v8::Value> value) { |
| 42 | return jsg::deepClone(js.v8Isolate->GetCurrentContext(), value); |
| 43 | } |
| 44 | JSG_RESOURCE_TYPE(CloneContext) { |
| 45 | JSG_METHOD(deepClone); |
| 46 | } |
| 47 | }; |
| 48 | JSG_DECLARE_ISOLATE_TYPE(CloneIsolate, CloneContext); |
| 49 | |
| 50 | KJ_TEST("deep clone") { |
| 51 | Evaluator<CloneContext, CloneIsolate> e(v8System); |
| 52 | e.expectEval( |
| 53 | "let obj = { foo: [ { bar: 1 } ] };\n" |
| 54 | "let clone = deepClone(obj);\n" |
| 55 | "clone.foo[0].bar = 2;\n" |
| 56 | "if (clone === obj) throw new Error('clone === obj');\n" |
| 57 | "if (clone.foo[0] === obj.foo[0]) throw new Error('clone.foo[0] === obj.foo[0]');\n" |
| 58 | "if (clone.foo[0].bar === obj.foo[0].bar) throw new Error('clone.foo[0].bar === obj.foo[0].bar');\n" |
| 59 | "JSON.stringify(clone);\n", |
| 60 | "string", "{\"foo\":[{\"bar\":2}]}"); |
| 61 | } |
| 62 | |
| 63 | // ======================================================================================== |
| 64 | |
| 65 | struct TypeErrorContext: public ContextGlobalObject { |
| 66 | auto returnFunctionTakingBox(double value) { |
| 67 | return [value](Lock&, Ref<NumberBox> value2) mutable { return value + value2->value; }; |
| 68 | } |
| 69 | |
| 70 | JSG_RESOURCE_TYPE(TypeErrorContext) { |
| 71 | JSG_NESTED_TYPE(NumberBox); |
| 72 | JSG_METHOD(returnFunctionTakingBox); |
| 73 | } |
| 74 | }; |
| 75 | JSG_DECLARE_ISOLATE_TYPE(TypeErrorIsolate, TypeErrorContext, NumberBox); |
| 76 | |
| 77 | KJ_TEST("throw TypeError") { |
| 78 | Evaluator<TypeErrorContext, TypeErrorIsolate> e(v8System); |
| 79 | e.expectEval("new NumberBox(123).addBox(321)", "throws", |
| 80 | "TypeError: Failed to execute 'addBox' on 'NumberBox': parameter 1 is not of " |
| 81 | "type 'NumberBox'."); |
| 82 | e.expectEval("new NumberBox(123).boxed = 321", "throws", |
| 83 | "TypeError: Failed to set the 'boxed' property on 'NumberBox': the provided " |
| 84 | "value is not of type 'NumberBox'."); |
| 85 | e.expectEval("NumberBox(123)", "throws", |
| 86 | "TypeError: Failed to construct 'NumberBox': Please use the 'new' operator, " |
| 87 | "this object constructor cannot be called as a function."); |
| 88 | e.expectEval("returnFunctionTakingBox(123)(321)", "throws", |
| 89 | "TypeError: Failed to execute function: parameter 1 is not of type 'NumberBox'."); |
| 90 | } |
| 91 | |
| 92 | // ======================================================================================== |
| 93 | |
| 94 | struct ThrowContext: public ContextGlobalObject { |
| 95 | auto returnFunctionThatThrows(double value) { |
| 96 | return [](Lock&, double) -> double { KJ_FAIL_ASSERT("thrown from returnFunctionThatThrows"); }; |
| 97 | } |
| 98 | void throwException() { |
| 99 | KJ_FAIL_REQUIRE("thrown from throwException"); |
| 100 | } |
| 101 | double getThrowing() { |
| 102 | KJ_FAIL_REQUIRE("thrown from getThrowing"); |
| 103 | } |
| 104 | void setThrowing(double) { |
| 105 | KJ_FAIL_REQUIRE("thrown from setThrowing"); |
| 106 | } |
| 107 | JSG_RESOURCE_TYPE(ThrowContext) { |
| 108 | JSG_METHOD(returnFunctionThatThrows); |
| 109 | JSG_METHOD(throwException); |
| 110 | JSG_INSTANCE_PROPERTY(throwing, getThrowing, setThrowing); |
| 111 | } |
| 112 | }; |
| 113 | JSG_DECLARE_ISOLATE_TYPE(ThrowIsolate, ThrowContext); |
| 114 | |
| 115 | KJ_TEST("throw internal error") { |
| 116 | setPredictableModeForTest(); |
| 117 | |
| 118 | Evaluator<ThrowContext, ThrowIsolate> e(v8System); |
| 119 | { |
| 120 | KJ_EXPECT_LOG(ERROR, "thrown from throwException"); |
| 121 | e.expectEval("throwException()", "throws", |
| 122 | "Error: internal error; reference = 0123456789abcdefghijklmn"); |
| 123 | } |
| 124 | { |
| 125 | // We also expect the logged internal error to contain the error id. |
| 126 | KJ_EXPECT_LOG(ERROR, "wdErrId = 0123456789abcdefghijklmn"); |
| 127 | e.expectEval("throwException()", "throws", |
| 128 | "Error: internal error; reference = 0123456789abcdefghijklmn"); |
| 129 | } |
| 130 | |
| 131 | { |
| 132 | KJ_EXPECT_LOG(ERROR, "thrown from getThrowing"); |
| 133 | e.expectEval( |
| 134 | "throwing", "throws", "Error: internal error; reference = 0123456789abcdefghijklmn"); |
| 135 | } |
| 136 | |
| 137 | { |
| 138 | KJ_EXPECT_LOG(ERROR, "thrown from setThrowing"); |
| 139 | e.expectEval( |
| 140 | "throwing = 123", "throws", "Error: internal error; reference = 0123456789abcdefghijklmn"); |
| 141 | } |
| 142 | |
| 143 | { |
| 144 | KJ_EXPECT_LOG(ERROR, "thrown from returnFunctionThatThrows"); |
| 145 | e.expectEval("returnFunctionThatThrows(123)(321)", "throws", |
| 146 | "Error: internal error; reference = 0123456789abcdefghijklmn"); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | // ======================================================================================== |
| 151 | |
| 152 | struct TunneledContext: public ContextGlobalObject { |
| 153 | void throwTunneledTypeError() { |
| 154 | JSG_FAIL_REQUIRE(TypeError, "thrown from throwTunneledTypeError"); |
| 155 | } |
| 156 | void throwTunneledTypeErrorWithoutMessage() { |
| 157 | KJ_FAIL_REQUIRE("jsg.TypeError <unseen message>"); |
| 158 | } |
| 159 | void throwTunneledTypeErrorLateColon() { |
| 160 | KJ_FAIL_REQUIRE("jsg.TypeError would be an appropriate error to throw here, but that would " |
| 161 | "cause a problem: We actually don't want this top secret message to be visible " |
| 162 | "to developers!"); |
| 163 | } |
| 164 | void throwTunneledTypeErrorWithExpectation() { |
| 165 | auto s = kj::str("Hello, world!"); |
| 166 | JSG_REQUIRE(s.startsWith(";"), TypeError, "thrown from throwTunneledTypeErrorWithExpectation"); |
| 167 | } |
| 168 | void throwTunneledOperationError() { |
| 169 | JSG_FAIL_REQUIRE(DOMOperationError, "thrown from throwTunneledOperationError"); |
| 170 | } |
| 171 | void throwTunneledOperationErrorWithoutMessage() { |
| 172 | KJ_FAIL_REQUIRE("jsg.DOMException(OperationError) <unseen message>"); |
| 173 | } |
| 174 | void throwTunneledOperationErrorLateColon() { |
| 175 | KJ_FAIL_REQUIRE("jsg.DOMException(OperationError) would be an appropriate error to throw here " |
| 176 | "here, but that would cause a problem: We actually don't want this top secret " |
| 177 | "message to be visible to developers!"); |
| 178 | } |
| 179 | void throwTunneledOperationErrorWithExpectation() { |
| 180 | auto s = kj::str("Hello, world!"); |
| 181 | JSG_REQUIRE(s.startsWith(";"), DOMOperationError, |
| 182 | "thrown from " |
| 183 | "throwTunneledOperationErrorWithExpectation"); |
| 184 | } |
| 185 | void throwTunneledInternalOperationError() { |
| 186 | JSG_FAIL_REQUIRE(InternalDOMOperationError, "thrown from throwTunneledInternalOperationError"); |
| 187 | } |
| 188 | void throwRemoteCpuExceededError() { |
| 189 | kj::throwFatalException(KJ_EXCEPTION(OVERLOADED, |
| 190 | "remote exception: remote exception: worker_do_not_log; script exceeded time limit", |
| 191 | "script exceeded time limit")); |
| 192 | } |
| 193 | void throwBadTunneledError() { |
| 194 | KJ_FAIL_REQUIRE(" jsg.TypeError"); |
| 195 | } |
| 196 | void throwBadTunneledErrorWithExpectation() { |
| 197 | auto s = kj::str("Hello, world!"); |
| 198 | KJ_REQUIRE(s.startsWith(";"), " jsg.TypeError"); |
| 199 | } |
| 200 | void throwRetunneledTypeError(jsg::Lock& js) { |
| 201 | // Not sure what to call this ... |
| 202 | v8::TryCatch tryCatch(js.v8Isolate); |
| 203 | try { |
| 204 | jsg::throwTypeError(js.v8Isolate, "Dummy error message."); |
| 205 | KJ_UNREACHABLE; |
| 206 | } catch (JsExceptionThrown&) { |
| 207 | throwTunneledException(js.v8Isolate, tryCatch.Exception()); |
| 208 | } |
| 209 | } |
| 210 | void throwHugeExternalString(jsg::Lock& js) { |
| 211 | kj::ArrayPtr<const char> fakeBuf(reinterpret_cast<const char*>(1), v8::String::kMaxLength + 1); |
| 212 | jsg::newExternalOneByteString(js, fakeBuf); |
| 213 | } |
| 214 | void throwTunneledMacroTypeError() { |
| 215 | JSG_FAIL_REQUIRE(TypeError, "thrown ", "from ", "throwTunneledMacroTypeError"); |
| 216 | } |
| 217 | void throwTunneledMacroTypeErrorWithExpectation() { |
| 218 | auto s = kj::str("Hello, world!"); |
| 219 | JSG_REQUIRE( |
| 220 | s.startsWith(";"), TypeError, "thrown from throwTunneledMacroTypeErrorWithExpectation"); |
| 221 | } |
| 222 | void throwTunneledMacroOperationError() { |
| 223 | JSG_FAIL_REQUIRE(DOMOperationError, "thrown ", "from throwTunneledMacroOperationError"); |
| 224 | } |
| 225 | void throwTunneledMacroOperationErrorWithExpectation() { |
| 226 | auto s = kj::str("Hello, world!"); |
| 227 | JSG_REQUIRE(s.startsWith(";"), DOMOperationError, "thrown from ", |
| 228 | kj::str("throwTunneledMacroOperationErrorWithExpectation")); |
| 229 | } |
| 230 | // Test that the error types mapped to WasmCompileError are handled correctly |
| 231 | void throwTunneledCompileError() { |
| 232 | KJ_FAIL_REQUIRE("jsg.CompileError: thrown from throwTunneledCompileError"); |
| 233 | } |
| 234 | void throwTunneledLinkError() { |
| 235 | KJ_FAIL_REQUIRE("jsg.LinkError: thrown from throwTunneledLinkError"); |
| 236 | } |
| 237 | void throwTunneledRuntimeError() { |
| 238 | KJ_FAIL_REQUIRE("jsg.RuntimeError: thrown from throwTunneledRuntimeError"); |
| 239 | } |
| 240 | // Test that only valid DOM exceptions are processed |
| 241 | void throwTunneledDOMException() { |
| 242 | KJ_FAIL_REQUIRE("jsg.DOMException(Some error): thrown from throwTunneledDOMException"); |
| 243 | } |
| 244 | void throwTunneledInvalidDOMException() { |
| 245 | KJ_FAIL_REQUIRE("jsg.DOMException: thrown from throwTunneledInvalidDOMException"); |
| 246 | } |
| 247 | void throwTunneledGarbledDOMException() { |
| 248 | KJ_FAIL_REQUIRE("jsg.DOMException(: thrown from throwTunneledGarbledDOMException"); |
| 249 | } |
| 250 | |
| 251 | JSG_RESOURCE_TYPE(TunneledContext) { |
| 252 | JSG_NESTED_TYPE(DOMException); |
| 253 | JSG_METHOD(throwTunneledTypeError); |
| 254 | JSG_METHOD(throwTunneledTypeErrorWithoutMessage); |
| 255 | JSG_METHOD(throwTunneledTypeErrorLateColon); |
| 256 | JSG_METHOD(throwTunneledTypeErrorWithExpectation); |
| 257 | JSG_METHOD(throwTunneledOperationError); |
| 258 | JSG_METHOD(throwTunneledOperationErrorWithoutMessage); |
| 259 | JSG_METHOD(throwTunneledOperationErrorLateColon); |
| 260 | JSG_METHOD(throwTunneledOperationErrorWithExpectation); |
| 261 | JSG_METHOD(throwTunneledInternalOperationError); |
| 262 | JSG_METHOD(throwRemoteCpuExceededError); |
| 263 | JSG_METHOD(throwBadTunneledError); |
| 264 | JSG_METHOD(throwBadTunneledErrorWithExpectation); |
| 265 | JSG_METHOD(throwRetunneledTypeError); |
| 266 | JSG_METHOD(throwHugeExternalString); |
| 267 | JSG_METHOD(throwTunneledMacroTypeError); |
| 268 | JSG_METHOD(throwTunneledMacroTypeErrorWithExpectation); |
| 269 | JSG_METHOD(throwTunneledMacroOperationError); |
| 270 | JSG_METHOD(throwTunneledMacroOperationErrorWithExpectation); |
| 271 | JSG_METHOD(throwTunneledCompileError); |
| 272 | JSG_METHOD(throwTunneledLinkError); |
| 273 | JSG_METHOD(throwTunneledRuntimeError); |
| 274 | JSG_METHOD(throwTunneledDOMException); |
| 275 | JSG_METHOD(throwTunneledInvalidDOMException); |
| 276 | JSG_METHOD(throwTunneledGarbledDOMException); |
| 277 | } |
| 278 | }; |
| 279 | JSG_DECLARE_ISOLATE_TYPE(TunneledIsolate, TunneledContext); |
| 280 | |
| 281 | KJ_TEST("throw tunneled exception") { |
| 282 | setPredictableModeForTest(); |
| 283 | |
| 284 | Evaluator<TunneledContext, TunneledIsolate> e(v8System); |
| 285 | e.expectEval( |
| 286 | "throwTunneledTypeError()", "throws", "TypeError: thrown from throwTunneledTypeError"); |
| 287 | e.expectEval("throwTunneledTypeErrorLateColon()", "throws", "TypeError"); |
| 288 | e.expectEval("throwTunneledTypeErrorWithExpectation()", "throws", |
| 289 | "TypeError: thrown from throwTunneledTypeErrorWithExpectation"); |
| 290 | e.expectEval("throwTunneledOperationError()", "throws", |
| 291 | "OperationError: thrown from throwTunneledOperationError"); |
| 292 | e.expectEval("throwTunneledOperationErrorWithoutMessage()", "throws", "OperationError"); |
| 293 | e.expectEval("throwTunneledOperationErrorLateColon()", "throws", "OperationError"); |
| 294 | e.expectEval("throwTunneledOperationErrorWithExpectation()", "throws", |
| 295 | "OperationError: thrown from throwTunneledOperationErrorWithExpectation"); |
| 296 | { |
| 297 | KJ_EXPECT_LOG(ERROR, "thrown from throwTunneledInternalOperationError"); |
| 298 | e.expectEval("throwTunneledInternalOperationError()", "throws", |
| 299 | "OperationError: internal error; reference = 0123456789abcdefghijklmn"); |
| 300 | } |
| 301 | { |
| 302 | // We also expect the logged internal error to contain the error id. |
| 303 | KJ_EXPECT_LOG(ERROR, "wdErrId = 0123456789abcdefghijklmn"); |
| 304 | e.expectEval("throwTunneledInternalOperationError()", "throws", |
| 305 | "OperationError: internal error; reference = 0123456789abcdefghijklmn"); |
| 306 | } |
| 307 | { |
| 308 | KJ_EXPECT_LOG(ERROR, " jsg.TypeError"); |
| 309 | e.expectEval("throwBadTunneledError()", "throws", |
| 310 | "Error: internal error; reference = 0123456789abcdefghijklmn"); |
| 311 | } |
| 312 | { |
| 313 | KJ_EXPECT_LOG(ERROR, "expected s.startsWith(\";\"); jsg.TypeError"); |
| 314 | e.expectEval("throwBadTunneledErrorWithExpectation()", "throws", |
| 315 | "Error: internal error; reference = 0123456789abcdefghijklmn"); |
| 316 | } |
| 317 | e.expectEval("throwTunneledMacroTypeError()", "throws", |
| 318 | "TypeError: thrown from throwTunneledMacroTypeError"); |
| 319 | e.expectEval("throwTunneledMacroTypeErrorWithExpectation()", "throws", |
| 320 | "TypeError: thrown from throwTunneledMacroTypeErrorWithExpectation"); |
| 321 | e.expectEval("throwTunneledMacroOperationError()", "throws", |
| 322 | "OperationError: thrown from throwTunneledMacroOperationError"); |
| 323 | e.expectEval("throwTunneledMacroOperationErrorWithExpectation()", "throws", |
| 324 | "OperationError: thrown from throwTunneledMacroOperationErrorWithExpectation"); |
| 325 | e.expectEval("throwTunneledCompileError()", "throws", |
| 326 | "CompileError: thrown from throwTunneledCompileError"); |
| 327 | e.expectEval( |
| 328 | "throwTunneledLinkError()", "throws", "LinkError: thrown from throwTunneledLinkError"); |
| 329 | e.expectEval("throwTunneledRuntimeError()", "throws", |
| 330 | "RuntimeError: thrown from throwTunneledRuntimeError"); |
| 331 | e.expectEval( |
| 332 | "throwTunneledDOMException()", "throws", "Some error: thrown from throwTunneledDOMException"); |
| 333 | { |
| 334 | KJ_EXPECT_LOG(ERROR, " thrown from throwTunneledInvalidDOMException"); |
| 335 | e.expectEval("throwTunneledInvalidDOMException()", "throws", |
| 336 | "Error: internal error; reference = 0123456789abcdefghijklmn"); |
| 337 | } |
| 338 | { |
| 339 | KJ_EXPECT_LOG(ERROR, " thrown from throwTunneledGarbledDOMException"); |
| 340 | e.expectEval("throwTunneledGarbledDOMException()", "throws", |
| 341 | "Error: internal error; reference = 0123456789abcdefghijklmn"); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | KJ_TEST("runTunnelingExceptions") { |
| 346 | Evaluator<TunneledContext, TunneledIsolate> e(v8System); |
| 347 | e.expectEval("throwRetunneledTypeError()", "throws", "TypeError: Dummy error message."); |
| 348 | e.expectEval("throwHugeExternalString()", "throws", "Error: String allocation failed"); |
| 349 | } |
| 350 | |
| 351 | KJ_TEST("isTunneledException") { |
| 352 | TunneledContext context; |
| 353 | try { |
| 354 | context.throwTunneledTypeError(); |
| 355 | KJ_UNREACHABLE; |
| 356 | } catch (kj::Exception& e) { |
| 357 | KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription()); |
| 358 | } |
| 359 | try { |
| 360 | context.throwTunneledTypeErrorWithoutMessage(); |
| 361 | KJ_UNREACHABLE; |
| 362 | } catch (kj::Exception& e) { |
| 363 | KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription()); |
| 364 | } |
| 365 | try { |
| 366 | context.throwTunneledTypeErrorLateColon(); |
| 367 | KJ_UNREACHABLE; |
| 368 | } catch (kj::Exception& e) { |
| 369 | KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription()); |
| 370 | } |
| 371 | try { |
| 372 | context.throwTunneledTypeErrorWithExpectation(); |
| 373 | KJ_UNREACHABLE; |
| 374 | } catch (kj::Exception& e) { |
| 375 | KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription()); |
| 376 | } |
| 377 | try { |
| 378 | context.throwTunneledOperationError(); |
| 379 | KJ_UNREACHABLE; |
| 380 | } catch (kj::Exception& e) { |
| 381 | KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription()); |
| 382 | } |
| 383 | try { |
| 384 | context.throwTunneledOperationErrorLateColon(); |
| 385 | KJ_UNREACHABLE; |
| 386 | } catch (kj::Exception& e) { |
| 387 | KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription()); |
| 388 | } |
| 389 | try { |
| 390 | context.throwTunneledOperationErrorWithExpectation(); |
| 391 | KJ_UNREACHABLE; |
| 392 | } catch (kj::Exception& e) { |
| 393 | KJ_EXPECT(isTunneledException(e.getDescription()), e.getDescription()); |
| 394 | } |
| 395 | |
| 396 | try { |
| 397 | context.throwBadTunneledError(); |
| 398 | KJ_UNREACHABLE; |
| 399 | } catch (kj::Exception& e) { |
| 400 | KJ_EXPECT(!isTunneledException(e.getDescription()), e.getDescription()); |
| 401 | } |
| 402 | try { |
| 403 | context.throwBadTunneledErrorWithExpectation(); |
| 404 | KJ_UNREACHABLE; |
| 405 | } catch (kj::Exception& e) { |
| 406 | KJ_EXPECT(!isTunneledException(e.getDescription()), e.getDescription()); |
| 407 | } |
| 408 | |
| 409 | try { |
| 410 | context.throwRemoteCpuExceededError(); |
| 411 | KJ_UNREACHABLE; |
| 412 | } catch (kj::Exception& e) { |
| 413 | KJ_EXPECT(!isTunneledException(e.getDescription()), e.getDescription()); |
| 414 | KJ_EXPECT(isDoNotLogException(e.getDescription()), e.getDescription()); |
| 415 | } |
| 416 | |
| 417 | try { |
| 418 | JSG_FAIL_REQUIRE(InternalDOMOperationError, "foo"); |
| 419 | KJ_UNREACHABLE; |
| 420 | } catch (kj::Exception& e) { |
| 421 | KJ_EXPECT(!isTunneledException(e.getDescription()), e.getDescription()); |
| 422 | } |
| 423 | } |
| 424 | |
| 425 | } // namespace |
| 426 | } // namespace workerd::jsg::test |