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