Skip to content
File

Blob: src/workerd/jsg/util-test.c++

16.5 KB
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 
9namespace workerd::jsg::test {
10namespace {
11 
12V8System v8System;
13class ContextGlobalObject: public Object, public ContextGlobal {};
14 
15struct 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};
23JSG_DECLARE_ISOLATE_TYPE(FreezeIsolate, FreezeContext);
24 
25KJ_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 
40struct 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};
48JSG_DECLARE_ISOLATE_TYPE(CloneIsolate, CloneContext);
49 
50KJ_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 
65struct 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};
75JSG_DECLARE_ISOLATE_TYPE(TypeErrorIsolate, TypeErrorContext, NumberBox);
76 
77KJ_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 
94struct 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};
113JSG_DECLARE_ISOLATE_TYPE(ThrowIsolate, ThrowContext);
114 
115KJ_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 
152struct 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};
279JSG_DECLARE_ISOLATE_TYPE(TunneledIsolate, TunneledContext);
280 
281KJ_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 
345KJ_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 
351KJ_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