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