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