1 #include <config.h>
2
3 #include <stdint.h>
4 #include <string.h> // for strlen, strstr
5
6 #include <glib.h>
7
8 #include <js/CallArgs.h>
9 #include <js/CompilationAndEvaluation.h>
10 #include <js/CompileOptions.h>
11 #include <js/PropertySpec.h>
12 #include <js/RootingAPI.h>
13 #include <js/SourceText.h>
14 #include <js/TypeDecls.h>
15 #include <js/Utility.h> // for UniqueChars
16 #include <jsapi.h> // for JS_DefineFunctions
17
18 #include "cjs/jsapi-util-args.h"
19 #include "cjs/jsapi-util.h"
20 #include "test/gjs-test-common.h"
21 #include "test/gjs-test-utils.h"
22
23 namespace mozilla {
24 union Utf8Unit;
25 }
26
27 // COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553
28 #ifdef __clang_analyzer__
29 void g_assertion_message(const char*, const char*, int, const char*,
30 const char*) __attribute__((analyzer_noreturn));
31 #endif
32
33 #define assert_match(str, pattern) \
34 G_STMT_START { \
35 const char *__s1 = (str), *__s2 = (pattern); \
36 if (!g_pattern_match_simple(__s2, __s1)) { \
37 g_assertion_message(G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
38 "assertion failed (\"" #str \
39 "\" matches \"" #pattern "\")"); \
40 } \
41 } \
42 G_STMT_END
43
44 typedef enum _test_enum {
45 ZERO,
46 ONE,
47 TWO,
48 THREE
49 } test_enum_t;
50
51 typedef enum _test_signed_enum {
52 MINUS_THREE = -3,
53 MINUS_TWO,
54 MINUS_ONE
55 } test_signed_enum_t;
56
57 #define JSNATIVE_TEST_FUNC_BEGIN(name) \
58 static bool \
59 name(JSContext *cx, \
60 unsigned argc, \
61 JS::Value *vp) \
62 { \
63 JS::CallArgs args = JS::CallArgsFromVp(argc, vp); \
64 bool retval;
65
66 #define JSNATIVE_TEST_FUNC_END \
67 if (retval) \
68 args.rval().setUndefined(); \
69 return retval; \
70 }
71
72 JSNATIVE_TEST_FUNC_BEGIN(no_args)
73 retval = gjs_parse_call_args(cx, "noArgs", args, "");
74 JSNATIVE_TEST_FUNC_END
75
76 JSNATIVE_TEST_FUNC_BEGIN(no_args_ignore_trailing)
77 retval = gjs_parse_call_args(cx, "noArgsIgnoreTrailing", args, "!");
78 JSNATIVE_TEST_FUNC_END
79
80 #define JSNATIVE_NO_ASSERT_TYPE_TEST_FUNC(type, fmt) \
81 JSNATIVE_TEST_FUNC_BEGIN(type##_arg_no_assert) \
82 type val; \
83 retval = gjs_parse_call_args(cx, #type "ArgNoAssert", args, fmt, \
84 "val", &val); \
85 JSNATIVE_TEST_FUNC_END
86
87 JSNATIVE_NO_ASSERT_TYPE_TEST_FUNC(bool, "b");
88 JSNATIVE_NO_ASSERT_TYPE_TEST_FUNC(int, "i");
89
90 #undef JSNATIVE_NO_ASSERT_TYPE_TEST_FUNC
91
92 JSNATIVE_TEST_FUNC_BEGIN(object_arg_no_assert)
93 JS::RootedObject val(cx);
94 retval = gjs_parse_call_args(cx, "objectArgNoAssert", args, "o",
95 "val", &val);
96 JSNATIVE_TEST_FUNC_END
97
98 JSNATIVE_TEST_FUNC_BEGIN(optional_int_args_no_assert)
99 int val1, val2;
100 retval = gjs_parse_call_args(cx, "optionalIntArgsNoAssert", args, "i|i",
101 "val1", &val1,
102 "val2", &val2);
103 JSNATIVE_TEST_FUNC_END
104
105 JSNATIVE_TEST_FUNC_BEGIN(args_ignore_trailing)
106 int val;
107 retval = gjs_parse_call_args(cx, "argsIgnoreTrailing", args, "!i",
108 "val", &val);
109 JSNATIVE_TEST_FUNC_END
110
111 JSNATIVE_TEST_FUNC_BEGIN(one_of_each_type)
112 bool boolval;
113 JS::UniqueChars strval;
114 GjsAutoChar fileval;
115 int intval;
116 unsigned uintval;
117 int64_t int64val;
118 double dblval;
119 JS::RootedObject objval(cx);
120 retval = gjs_parse_call_args(cx, "oneOfEachType", args, "bsFiutfo",
121 "bool", &boolval,
122 "str", &strval,
123 "file", &fileval,
124 "int", &intval,
125 "uint", &uintval,
126 "int64", &int64val,
127 "dbl", &dblval,
128 "obj", &objval);
129 g_assert_cmpint(boolval, ==, true);
130 g_assert_cmpstr(strval.get(), ==, "foo");
131 g_assert_cmpstr(fileval, ==, "foo");
132 g_assert_cmpint(intval, ==, 1);
133 g_assert_cmpint(uintval, ==, 1);
134 g_assert_cmpint(int64val, ==, 1);
135 g_assert_cmpfloat(dblval, ==, 1.0);
136 g_assert_true(objval);
137 JSNATIVE_TEST_FUNC_END
138
139 JSNATIVE_TEST_FUNC_BEGIN(optional_args_all)
140 bool val1, val2, val3;
141 retval = gjs_parse_call_args(cx, "optionalArgsAll", args, "b|bb",
142 "val1", &val1,
143 "val2", &val2,
144 "val3", &val3);
145 g_assert_cmpint(val1, ==, true);
146 g_assert_cmpint(val2, ==, true);
147 g_assert_cmpint(val3, ==, true);
148 JSNATIVE_TEST_FUNC_END
149
150 JSNATIVE_TEST_FUNC_BEGIN(optional_args_only_required)
151 bool val1 = false, val2 = false, val3 = false;
152 retval = gjs_parse_call_args(cx, "optionalArgsOnlyRequired", args, "b|bb",
153 "val1", &val1,
154 "val2", &val2,
155 "val3", &val3);
156 g_assert_cmpint(val1, ==, true);
157 g_assert_cmpint(val2, ==, false);
158 g_assert_cmpint(val3, ==, false);
159 JSNATIVE_TEST_FUNC_END
160
161 JSNATIVE_TEST_FUNC_BEGIN(only_optional_args)
162 int val1, val2;
163 retval = gjs_parse_call_args(cx, "onlyOptionalArgs", args, "|ii",
164 "val1", &val1,
165 "val2", &val2);
166 JSNATIVE_TEST_FUNC_END
167
168 JSNATIVE_TEST_FUNC_BEGIN(unsigned_enum_arg)
169 test_enum_t val;
170 retval = gjs_parse_call_args(cx, "unsignedEnumArg", args, "i",
171 "enum_param", &val);
172 g_assert_cmpint(val, ==, ONE);
173 JSNATIVE_TEST_FUNC_END
174
175 JSNATIVE_TEST_FUNC_BEGIN(signed_enum_arg)
176 test_signed_enum_t val;
177 retval = gjs_parse_call_args(cx, "signedEnumArg", args, "i",
178 "enum_param", &val);
179 g_assert_cmpint(val, ==, MINUS_ONE);
180 JSNATIVE_TEST_FUNC_END
181
182 JSNATIVE_TEST_FUNC_BEGIN(one_of_each_nullable_type)
183 JS::UniqueChars strval;
184 GjsAutoChar fileval;
185 JS::RootedObject objval(cx);
186 retval = gjs_parse_call_args(cx, "oneOfEachNullableType", args, "?s?F?o",
187 "strval", &strval,
188 "fileval", &fileval,
189 "objval", &objval);
190 g_assert_null(strval);
191 g_assert_null(fileval);
192 g_assert_false(objval);
193 JSNATIVE_TEST_FUNC_END
194
195 JSNATIVE_TEST_FUNC_BEGIN(unwind_free_test)
196 int intval;
197 unsigned uval;
198 JS::RootedObject objval(cx);
199 retval = gjs_parse_call_args(cx, "unwindFreeTest", args, "oiu",
200 "objval", &objval,
201 "intval", &intval,
202 "error", &uval);
203 g_assert_false(objval);
204 JSNATIVE_TEST_FUNC_END
205
206 #define JSNATIVE_BAD_NULLABLE_TEST_FUNC(type, fmt) \
207 JSNATIVE_TEST_FUNC_BEGIN(type##_invalid_nullable) \
208 type val; \
209 retval = gjs_parse_call_args(cx, #type "InvalidNullable", \
210 args, "?" fmt, \
211 "val", &val); \
212 JSNATIVE_TEST_FUNC_END
213
214 JSNATIVE_BAD_NULLABLE_TEST_FUNC(bool, "b");
215 JSNATIVE_BAD_NULLABLE_TEST_FUNC(int, "i");
216 JSNATIVE_BAD_NULLABLE_TEST_FUNC(unsigned, "u");
217 JSNATIVE_BAD_NULLABLE_TEST_FUNC(int64_t, "t");
218 JSNATIVE_BAD_NULLABLE_TEST_FUNC(double, "f");
219
220 #undef JSNATIVE_BAD_NULLABLE_TEST_FUNC
221
222 #define JSNATIVE_BAD_TYPE_TEST_FUNC(type, ch) \
223 JSNATIVE_TEST_FUNC_BEGIN(type##_invalid_type) \
224 type val; \
225 retval = gjs_parse_call_args(cx, #type "InvalidType", args, ch, \
226 "val", &val); \
227 JSNATIVE_TEST_FUNC_END
228
229 JSNATIVE_BAD_TYPE_TEST_FUNC(bool, "i");
230 JSNATIVE_BAD_TYPE_TEST_FUNC(int, "u");
231 JSNATIVE_BAD_TYPE_TEST_FUNC(unsigned, "t");
232 JSNATIVE_BAD_TYPE_TEST_FUNC(int64_t, "f");
233 JSNATIVE_BAD_TYPE_TEST_FUNC(double, "b");
234 JSNATIVE_BAD_TYPE_TEST_FUNC(GjsAutoChar, "i");
235
236 #undef JSNATIVE_BAD_TYPE_TEST_FUNC
237
238 JSNATIVE_TEST_FUNC_BEGIN(UniqueChars_invalid_type)
239 JS::UniqueChars value;
240 retval = gjs_parse_call_args(cx, "UniqueCharsInvalidType", args, "i",
241 "value", &value);
242 JSNATIVE_TEST_FUNC_END
243
244 JSNATIVE_TEST_FUNC_BEGIN(object_invalid_type)
245 JS::RootedObject val(cx);
246 retval = gjs_parse_call_args(cx, "objectInvalidType", args, "i",
247 "val", &val);
248 JSNATIVE_TEST_FUNC_END
249
250 static JSFunctionSpec native_test_funcs[] = {
251 JS_FN("noArgs", no_args, 0, 0),
252 JS_FN("noArgsIgnoreTrailing", no_args_ignore_trailing, 0, 0),
253 JS_FN("boolArgNoAssert", bool_arg_no_assert, 0, 0),
254 JS_FN("intArgNoAssert", int_arg_no_assert, 0, 0),
255 JS_FN("objectArgNoAssert", object_arg_no_assert, 0, 0),
256 JS_FN("optionalIntArgsNoAssert", optional_int_args_no_assert, 0, 0),
257 JS_FN("argsIgnoreTrailing", args_ignore_trailing, 0, 0),
258 JS_FN("oneOfEachType", one_of_each_type, 0, 0),
259 JS_FN("optionalArgsAll", optional_args_all, 0, 0),
260 JS_FN("optionalArgsOnlyRequired", optional_args_only_required, 0, 0),
261 JS_FN("onlyOptionalArgs", only_optional_args, 0, 0),
262 JS_FN("unsignedEnumArg", unsigned_enum_arg, 0, 0),
263 JS_FN("signedEnumArg", signed_enum_arg, 0, 0),
264 JS_FN("oneOfEachNullableType", one_of_each_nullable_type, 0, 0),
265 JS_FN("unwindFreeTest", unwind_free_test, 0, 0),
266 JS_FN("boolInvalidNullable", bool_invalid_nullable, 0, 0),
267 JS_FN("intInvalidNullable", int_invalid_nullable, 0, 0),
268 JS_FN("unsignedInvalidNullable", unsigned_invalid_nullable, 0, 0),
269 JS_FN("int64_tInvalidNullable", int64_t_invalid_nullable, 0, 0),
270 JS_FN("doubleInvalidNullable", double_invalid_nullable, 0, 0),
271 JS_FN("boolInvalidType", bool_invalid_type, 0, 0),
272 JS_FN("intInvalidType", int_invalid_type, 0, 0),
273 JS_FN("unsignedInvalidType", unsigned_invalid_type, 0, 0),
274 JS_FN("int64_tInvalidType", int64_t_invalid_type, 0, 0),
275 JS_FN("doubleInvalidType", double_invalid_type, 0, 0),
276 JS_FN("GjsAutoCharInvalidType", GjsAutoChar_invalid_type, 0, 0),
277 JS_FN("UniqueCharsInvalidType", UniqueChars_invalid_type, 0, 0),
278 JS_FN("objectInvalidType", object_invalid_type, 0, 0),
279 JS_FS_END};
280
281 static void
setup(GjsUnitTestFixture * fx,gconstpointer unused)282 setup(GjsUnitTestFixture *fx,
283 gconstpointer unused)
284 {
285 gjs_unit_test_fixture_setup(fx, unused);
286
287 JS::RootedObject global(fx->cx, gjs_get_import_global(fx->cx));
288 bool success = JS_DefineFunctions(fx->cx, global, native_test_funcs);
289 g_assert_true(success);
290 }
291
292 static void
run_code(GjsUnitTestFixture * fx,gconstpointer code)293 run_code(GjsUnitTestFixture *fx,
294 gconstpointer code)
295 {
296 const char *script = (const char *) code;
297
298 JS::SourceText<mozilla::Utf8Unit> source;
299 bool ok = source.init(fx->cx, script, strlen(script),
300 JS::SourceOwnership::Borrowed);
301 g_assert_true(ok);
302
303 JS::CompileOptions options(fx->cx);
304 options.setFileAndLine("unit test", 1);
305
306 JS::RootedValue ignored(fx->cx);
307 ok = JS::Evaluate(fx->cx, options, source, &ignored);
308
309 g_assert_null(gjs_test_get_exception_message(fx->cx));
310 g_assert_true(ok);
311 }
312
313 static void
run_code_expect_exception(GjsUnitTestFixture * fx,gconstpointer code)314 run_code_expect_exception(GjsUnitTestFixture *fx,
315 gconstpointer code)
316 {
317 const char *script = (const char *) code;
318
319 JS::SourceText<mozilla::Utf8Unit> source;
320 bool ok = source.init(fx->cx, script, strlen(script),
321 JS::SourceOwnership::Borrowed);
322 g_assert_true(ok);
323
324 JS::CompileOptions options(fx->cx);
325 options.setFileAndLine("unit test", 1);
326
327 JS::RootedValue ignored(fx->cx);
328 ok = JS::Evaluate(fx->cx, options, source, &ignored);
329 g_assert_false(ok);
330 GjsAutoChar message = gjs_test_get_exception_message(fx->cx);
331 g_assert_nonnull(message);
332
333 /* Cheap way to shove an expected exception message into the data argument */
334 const char *expected_msg = strstr((const char *) code, "//");
335 if (expected_msg != NULL) {
336 expected_msg += 2;
337 assert_match(message, expected_msg);
338 }
339 }
340
341 void
gjs_test_add_tests_for_parse_call_args(void)342 gjs_test_add_tests_for_parse_call_args(void)
343 {
344 #define ADD_CALL_ARGS_TEST_BASE(path, code, f) \
345 g_test_add("/callargs/" path, GjsUnitTestFixture, code, setup, f, \
346 gjs_unit_test_fixture_teardown)
347 #define ADD_CALL_ARGS_TEST(path, code) \
348 ADD_CALL_ARGS_TEST_BASE(path, code, run_code)
349 #define ADD_CALL_ARGS_TEST_XFAIL(path, code) \
350 ADD_CALL_ARGS_TEST_BASE(path, code, run_code_expect_exception)
351
352 ADD_CALL_ARGS_TEST("no-args-works", "noArgs()");
353 ADD_CALL_ARGS_TEST_XFAIL("no-args-fails-on-extra-args",
354 "noArgs(1, 2, 3)//*Expected 0 arguments, got 3");
355 ADD_CALL_ARGS_TEST("no-args-ignores-trailing",
356 "noArgsIgnoreTrailing(1, 2, 3)");
357 ADD_CALL_ARGS_TEST_XFAIL("too-many-args-fails",
358 "intArgNoAssert(1, 2)"
359 "//*Expected 1 arguments, got 2");
360 ADD_CALL_ARGS_TEST_XFAIL("too-many-args-fails-when-more-than-optional",
361 "optionalIntArgsNoAssert(1, 2, 3)"
362 "//*Expected minimum 1 arguments (and 1 optional), got 3");
363 ADD_CALL_ARGS_TEST_XFAIL("too-few-args-fails",
364 "intArgNoAssert()//*At least 1 argument required, "
365 "but only 0 passed");
366 ADD_CALL_ARGS_TEST_XFAIL("too-few-args-fails-with-optional",
367 "optionalIntArgsNoAssert()//*At least 1 argument "
368 "required, but only 0 passed");
369 ADD_CALL_ARGS_TEST("args-ignores-trailing", "argsIgnoreTrailing(1, 2, 3)");
370 ADD_CALL_ARGS_TEST("one-of-each-type-works",
371 "oneOfEachType(true, 'foo', 'foo', 1, 1, 1, 1, {})");
372 ADD_CALL_ARGS_TEST("optional-args-work-when-passing-all-args",
373 "optionalArgsAll(true, true, true)");
374 ADD_CALL_ARGS_TEST("optional-args-work-when-passing-only-required-args",
375 "optionalArgsOnlyRequired(true)");
376 ADD_CALL_ARGS_TEST("enum-types-work", "unsignedEnumArg(1)");
377 ADD_CALL_ARGS_TEST("signed-enum-types-work", "signedEnumArg(-1)");
378 ADD_CALL_ARGS_TEST("one-of-each-nullable-type-works",
379 "oneOfEachNullableType(null, null, null)");
380 ADD_CALL_ARGS_TEST("passing-no-arguments-when-all-optional",
381 "onlyOptionalArgs()");
382 ADD_CALL_ARGS_TEST("passing-some-arguments-when-all-optional",
383 "onlyOptionalArgs(1)");
384 ADD_CALL_ARGS_TEST("passing-all-arguments-when-all-optional",
385 "onlyOptionalArgs(1, 1)");
386 ADD_CALL_ARGS_TEST_XFAIL("allocated-args-are-freed-on-error",
387 "unwindFreeTest({}, 1, -1)"
388 "//*Value * is out of range");
389 ADD_CALL_ARGS_TEST_XFAIL("nullable-bool-is-invalid",
390 "boolInvalidNullable(true)"
391 "//*Invalid format string combination ?b");
392 ADD_CALL_ARGS_TEST_XFAIL("nullable-int-is-invalid",
393 "intInvalidNullable(1)"
394 "//*Invalid format string combination ?i");
395 ADD_CALL_ARGS_TEST_XFAIL("nullable-unsigned-is-invalid",
396 "unsignedInvalidNullable(1)"
397 "//*Invalid format string combination ?u");
398 ADD_CALL_ARGS_TEST_XFAIL("nullable-int64-is-invalid",
399 "int64_tInvalidNullable(1)"
400 "//*Invalid format string combination ?t");
401 ADD_CALL_ARGS_TEST_XFAIL("nullable-double-is-invalid",
402 "doubleInvalidNullable(1)"
403 "//*Invalid format string combination ?f");
404 ADD_CALL_ARGS_TEST_XFAIL("invalid-bool-type",
405 "boolInvalidType(1)"
406 "//*Wrong type for i, got bool?");
407 ADD_CALL_ARGS_TEST_XFAIL("invalid-int-type",
408 "intInvalidType(1)"
409 "//*Wrong type for u, got int32_t?");
410 ADD_CALL_ARGS_TEST_XFAIL("invalid-unsigned-type",
411 "unsignedInvalidType(1)"
412 "//*Wrong type for t, got uint32_t?");
413 ADD_CALL_ARGS_TEST_XFAIL("invalid-int64-type",
414 "int64_tInvalidType(1)"
415 "//*Wrong type for f, got int64_t?");
416 ADD_CALL_ARGS_TEST_XFAIL("invalid-double-type",
417 "doubleInvalidType(false)"
418 "//*Wrong type for b, got double?");
419 ADD_CALL_ARGS_TEST_XFAIL("invalid-autochar-type",
420 "GjsAutoCharInvalidType(1)"
421 "//*Wrong type for i, got GjsAutoChar?");
422 ADD_CALL_ARGS_TEST_XFAIL("invalid-autojschar-type",
423 "UniqueCharsInvalidType(1)"
424 "//*Wrong type for i, got JS::UniqueChars?");
425 ADD_CALL_ARGS_TEST_XFAIL("invalid-object-type",
426 "objectInvalidType(1)"
427 "//*Wrong type for i, got JS::MutableHandleObject");
428 ADD_CALL_ARGS_TEST_XFAIL("invalid-boolean",
429 "boolArgNoAssert({})//*Not a boolean");
430 ADD_CALL_ARGS_TEST_XFAIL("invalid-object",
431 "objectArgNoAssert(3)//*Not an object");
432
433 #undef ADD_CALL_ARGS_TEST_XFAIL
434 #undef ADD_CALL_ARGS_TEST
435 #undef ADD_CALL_ARGS_TEST_BASE
436 }
437