1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 /*
3  * Copyright (c) 2008  litl, LLC
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy
6  * of this software and associated documentation files (the "Software"), to
7  * deal in the Software without restriction, including without limitation the
8  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9  * sell copies of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  */
23 
24 #include <config.h>
25 
26 #include <stdint.h>
27 #include <string.h>  // for size_t, strlen
28 
29 #include <string>  // for u16string, u32string
30 
31 #include <glib-object.h>
32 #include <glib.h>
33 #include <glib/gstdio.h>  // for g_unlink
34 
35 #include <js/Array.h>
36 #include <js/CharacterEncoding.h>
37 #include <js/RootingAPI.h>
38 #include <js/TypeDecls.h>
39 #include <js/Utility.h>  // for UniqueChars
40 #include <js/Value.h>
41 #include <js/ValueArray.h>
42 #include <jsapi.h>
43 #include <jspubtd.h>  // for JSProto_Number
44 
45 #include "gi/arg-inl.h"
46 #include "cjs/context.h"
47 #include "cjs/error-types.h"
48 #include "cjs/jsapi-util.h"
49 #include "cjs/profiler.h"
50 #include "test/gjs-test-no-introspection-object.h"
51 #include "test/gjs-test-utils.h"
52 #include "util/misc.h"
53 
54 // COMPAT: https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1553
55 #ifdef __clang_analyzer__
56 void g_assertion_message(const char*, const char*, int, const char*,
57                          const char*) __attribute__((analyzer_noreturn));
58 #endif
59 
60 #define VALID_UTF8_STRING "\303\211\303\226 foobar \343\203\237"
61 
62 static void
gjstest_test_func_gjs_context_construct_destroy(void)63 gjstest_test_func_gjs_context_construct_destroy(void)
64 {
65     GjsContext *context;
66 
67     /* Construct twice just to possibly a case where global state from
68      * the first leaks.
69      */
70     context = gjs_context_new ();
71     g_object_unref (context);
72 
73     context = gjs_context_new ();
74     g_object_unref (context);
75 }
76 
77 static void
gjstest_test_func_gjs_context_construct_eval(void)78 gjstest_test_func_gjs_context_construct_eval(void)
79 {
80     GjsContext *context;
81     int estatus;
82     GError *error = NULL;
83 
84     context = gjs_context_new ();
85     if (!gjs_context_eval (context, "1+1", -1, "<input>", &estatus, &error))
86         g_error ("%s", error->message);
87     g_object_unref (context);
88 }
89 
gjstest_test_func_gjs_context_eval_non_zero_terminated(void)90 static void gjstest_test_func_gjs_context_eval_non_zero_terminated(void) {
91     GjsAutoUnref<GjsContext> gjs = gjs_context_new();
92     GError* error = NULL;
93     int status;
94 
95     // This string is invalid JS if it is treated as zero-terminated
96     bool ok = gjs_context_eval(gjs, "77!", 2, "<input>", &status, &error);
97 
98     g_assert_true(ok);
99     g_assert_no_error(error);
100     g_assert_cmpint(status, ==, 77);
101 }
102 
103 static void
gjstest_test_func_gjs_context_exit(void)104 gjstest_test_func_gjs_context_exit(void)
105 {
106     GjsContext *context = gjs_context_new();
107     GError *error = NULL;
108     int status;
109 
110     bool ok = gjs_context_eval(context, "imports.system.exit(0);", -1,
111                                "<input>", &status, &error);
112     g_assert_false(ok);
113     g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT);
114     g_assert_cmpuint(status, ==, 0);
115 
116     g_clear_error(&error);
117 
118     ok = gjs_context_eval(context, "imports.system.exit(42);", -1, "<input>",
119                           &status, &error);
120     g_assert_false(ok);
121     g_assert_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT);
122     g_assert_cmpuint(status, ==, 42);
123 
124     g_clear_error(&error);
125     g_object_unref(context);
126 }
127 
128 #define JS_CLASS "\
129 const GObject = imports.gi.GObject; \
130 const FooBar = GObject.registerClass(class FooBar extends GObject.Object {}); \
131 "
132 
133 static void
gjstest_test_func_gjs_gobject_js_defined_type(void)134 gjstest_test_func_gjs_gobject_js_defined_type(void)
135 {
136     GjsContext *context = gjs_context_new();
137     GError *error = NULL;
138     int status;
139     bool ok = gjs_context_eval(context, JS_CLASS, -1, "<input>", &status, &error);
140     g_assert_no_error(error);
141     g_assert_true(ok);
142 
143     GType foo_type = g_type_from_name("Gjs_FooBar");
144     g_assert_cmpuint(foo_type, !=, G_TYPE_INVALID);
145 
146     gpointer foo = g_object_new(foo_type, NULL);
147     g_assert_true(G_IS_OBJECT(foo));
148 
149     g_object_unref(foo);
150     g_object_unref(context);
151 }
152 
gjstest_test_func_gjs_gobject_without_introspection(void)153 static void gjstest_test_func_gjs_gobject_without_introspection(void) {
154     GjsAutoUnref<GjsContext> context = gjs_context_new();
155     GError* error = nullptr;
156     int status;
157 
158     /* Ensure class */
159     g_type_class_ref(GJSTEST_TYPE_NO_INTROSPECTION_OBJECT);
160 
161 #define TESTJS                                                         \
162     "const {GObject} = imports.gi;"                                    \
163     "var obj = GObject.Object.newv("                                   \
164     "    GObject.type_from_name('GjsTestNoIntrospectionObject'), []);" \
165     "obj.a_int = 1234;"
166 
167     bool ok = gjs_context_eval(context, TESTJS, -1, "<input>", &status, &error);
168     g_assert_true(ok);
169     g_assert_no_error(error);
170 
171     GjsTestNoIntrospectionObject* obj = gjstest_no_introspection_object_peek();
172     g_assert_nonnull(obj);
173 
174     int val = 0;
175     g_object_get(obj, "a-int", &val, NULL);
176     g_assert_cmpint(val, ==, 1234);
177 
178 #undef TESTJS
179 }
180 
gjstest_test_func_gjs_jsapi_util_string_js_string_utf8(GjsUnitTestFixture * fx,const void *)181 static void gjstest_test_func_gjs_jsapi_util_string_js_string_utf8(
182     GjsUnitTestFixture* fx, const void*) {
183     JS::RootedValue js_string(fx->cx);
184     g_assert_true(gjs_string_from_utf8(fx->cx, VALID_UTF8_STRING, &js_string));
185     g_assert_true(js_string.isString());
186 
187     JS::UniqueChars utf8_result = gjs_string_to_utf8(fx->cx, js_string);
188     g_assert_nonnull(utf8_result);
189     g_assert_cmpstr(VALID_UTF8_STRING, ==, utf8_result.get());
190 }
191 
gjstest_test_func_gjs_jsapi_util_error_throw(GjsUnitTestFixture * fx,const void *)192 static void gjstest_test_func_gjs_jsapi_util_error_throw(GjsUnitTestFixture* fx,
193                                                          const void*) {
194     JS::RootedValue exc(fx->cx), value(fx->cx);
195 
196     /* Test that we can throw */
197 
198     gjs_throw(fx->cx, "This is an exception %d", 42);
199 
200     g_assert_true(JS_IsExceptionPending(fx->cx));
201 
202     JS_GetPendingException(fx->cx, &exc);
203     g_assert_false(exc.isUndefined());
204 
205     JS::RootedObject exc_obj(fx->cx, &exc.toObject());
206     JS_GetProperty(fx->cx, exc_obj, "message", &value);
207 
208     g_assert_true(value.isString());
209 
210     JS::UniqueChars s = gjs_string_to_utf8(fx->cx, value);
211     g_assert_nonnull(s);
212     g_assert_cmpstr(s.get(), ==, "This is an exception 42");
213 
214     /* keep this around before we clear it */
215     JS::RootedValue previous(fx->cx, exc);
216 
217     JS_ClearPendingException(fx->cx);
218 
219     g_assert_false(JS_IsExceptionPending(fx->cx));
220 
221     /* Check that we don't overwrite a pending exception */
222     JS_SetPendingException(fx->cx, previous);
223 
224     g_assert_true(JS_IsExceptionPending(fx->cx));
225 
226     gjs_throw(fx->cx, "Second different exception %s", "foo");
227 
228     g_assert_true(JS_IsExceptionPending(fx->cx));
229 
230     exc = JS::UndefinedValue();
231     JS_GetPendingException(fx->cx, &exc);
232     g_assert_false(exc.isUndefined());
233     g_assert_true(&exc.toObject() == &previous.toObject());
234 }
235 
test_jsapi_util_string_utf8_nchars_to_js(GjsUnitTestFixture * fx,const void *)236 static void test_jsapi_util_string_utf8_nchars_to_js(GjsUnitTestFixture* fx,
237                                                      const void*) {
238     JS::RootedValue v_out(fx->cx);
239     bool ok = gjs_string_from_utf8_n(fx->cx, VALID_UTF8_STRING,
240                                      strlen(VALID_UTF8_STRING), &v_out);
241     g_assert_true(ok);
242     g_assert_true(v_out.isString());
243 }
244 
test_jsapi_util_string_char16_data(GjsUnitTestFixture * fx,const void *)245 static void test_jsapi_util_string_char16_data(GjsUnitTestFixture* fx,
246                                                const void*) {
247     char16_t *chars;
248     size_t len;
249 
250     JS::ConstUTF8CharsZ jschars(VALID_UTF8_STRING, strlen(VALID_UTF8_STRING));
251     JS::RootedString str(fx->cx, JS_NewStringCopyUTF8Z(fx->cx, jschars));
252     g_assert_true(gjs_string_get_char16_data(fx->cx, str, &chars, &len));
253     std::u16string result(chars, len);
254     g_assert_true(result == u"\xc9\xd6 foobar \u30df");
255     g_free(chars);
256 
257     /* Try with a string that is likely to be stored as Latin-1 */
258     str = JS_NewStringCopyZ(fx->cx, "abcd");
259     g_assert_true(gjs_string_get_char16_data(fx->cx, str, &chars, &len));
260 
261     result.assign(chars, len);
262     g_assert_true(result == u"abcd");
263     g_free(chars);
264 }
265 
test_jsapi_util_string_to_ucs4(GjsUnitTestFixture * fx,const void *)266 static void test_jsapi_util_string_to_ucs4(GjsUnitTestFixture* fx,
267                                            const void*) {
268     gunichar *chars;
269     size_t len;
270 
271     JS::ConstUTF8CharsZ jschars(VALID_UTF8_STRING, strlen(VALID_UTF8_STRING));
272     JS::RootedString str(fx->cx, JS_NewStringCopyUTF8Z(fx->cx, jschars));
273     g_assert_true(gjs_string_to_ucs4(fx->cx, str, &chars, &len));
274 
275     std::u32string result(chars, chars + len);
276     g_assert_true(result == U"\xc9\xd6 foobar \u30df");
277     g_free(chars);
278 
279     /* Try with a string that is likely to be stored as Latin-1 */
280     str = JS_NewStringCopyZ(fx->cx, "abcd");
281     g_assert_true(gjs_string_to_ucs4(fx->cx, str, &chars, &len));
282 
283     result.assign(chars, chars + len);
284     g_assert_true(result == U"abcd");
285     g_free(chars);
286 }
287 
test_jsapi_util_debug_string_valid_utf8(GjsUnitTestFixture * fx,const void *)288 static void test_jsapi_util_debug_string_valid_utf8(GjsUnitTestFixture* fx,
289                                                     const void*) {
290     JS::RootedValue v_string(fx->cx);
291     g_assert_true(gjs_string_from_utf8(fx->cx, VALID_UTF8_STRING, &v_string));
292 
293     char *debug_output = gjs_value_debug_string(fx->cx, v_string);
294 
295     g_assert_nonnull(debug_output);
296     g_assert_cmpstr("\"" VALID_UTF8_STRING "\"", ==, debug_output);
297 
298     g_free(debug_output);
299 }
300 
test_jsapi_util_debug_string_invalid_utf8(GjsUnitTestFixture * fx,const void *)301 static void test_jsapi_util_debug_string_invalid_utf8(GjsUnitTestFixture* fx,
302                                                       const void*) {
303     g_test_skip("SpiderMonkey doesn't validate UTF-8 after encoding it");
304 
305     JS::RootedValue v_string(fx->cx);
306     const char16_t invalid_unicode[] = { 0xffff, 0xffff };
307     v_string.setString(JS_NewUCStringCopyN(fx->cx, invalid_unicode, 2));
308 
309     char *debug_output = gjs_value_debug_string(fx->cx, v_string);
310 
311     g_assert_nonnull(debug_output);
312     /* g_assert_cmpstr("\"\\xff\\xff\\xff\\xff\"", ==, debug_output); */
313 
314     g_free(debug_output);
315 }
316 
test_jsapi_util_debug_string_object_with_complicated_to_string(GjsUnitTestFixture * fx,const void *)317 static void test_jsapi_util_debug_string_object_with_complicated_to_string(
318     GjsUnitTestFixture* fx, const void*) {
319     const char16_t desserts[] = {
320         0xd83c, 0xdf6a,  /* cookie */
321         0xd83c, 0xdf69,  /* doughnut */
322     };
323     JS::RootedValueArray<2> contents(fx->cx);
324     contents[0].setString(JS_NewUCStringCopyN(fx->cx, desserts, 2));
325     contents[1].setString(JS_NewUCStringCopyN(fx->cx, desserts + 2, 2));
326     JS::RootedObject array(fx->cx, JS::NewArrayObject(fx->cx, contents));
327     JS::RootedValue v_array(fx->cx, JS::ObjectValue(*array));
328     char *debug_output = gjs_value_debug_string(fx->cx, v_array);
329 
330     g_assert_nonnull(debug_output);
331     g_assert_cmpstr(u8"��,��", ==, debug_output);
332 
333     g_free(debug_output);
334 }
335 
336 static void
gjstest_test_func_util_misc_strv_concat_null(void)337 gjstest_test_func_util_misc_strv_concat_null(void)
338 {
339     char **ret;
340 
341     ret = gjs_g_strv_concat(NULL, 0);
342     g_assert_nonnull(ret);
343     g_assert_null(ret[0]);
344 
345     g_strfreev(ret);
346 }
347 
348 static void
gjstest_test_func_util_misc_strv_concat_pointers(void)349 gjstest_test_func_util_misc_strv_concat_pointers(void)
350 {
351     char  *strv0[2] = {(char*)"foo", NULL};
352     char  *strv1[1] = {NULL};
353     char **strv2    = NULL;
354     char  *strv3[2] = {(char*)"bar", NULL};
355     char **stuff[4];
356     char **ret;
357 
358     stuff[0] = strv0;
359     stuff[1] = strv1;
360     stuff[2] = strv2;
361     stuff[3] = strv3;
362 
363     ret = gjs_g_strv_concat(stuff, 4);
364     g_assert_nonnull(ret);
365     g_assert_cmpstr(ret[0], ==, strv0[0]);  /* same string */
366     g_assert_true(ret[0] != strv0[0]);      // different pointer
367     g_assert_cmpstr(ret[1], ==, strv3[0]);
368     g_assert_true(ret[1] != strv3[0]);
369     g_assert_null(ret[2]);
370 
371     g_strfreev(ret);
372 }
373 
374 static void
gjstest_test_profiler_start_stop(void)375 gjstest_test_profiler_start_stop(void)
376 {
377     GjsAutoUnref<GjsContext> context =
378         static_cast<GjsContext *>(g_object_new(GJS_TYPE_CONTEXT,
379                                                "profiler-enabled", TRUE,
380                                                nullptr));
381     GjsProfiler *profiler = gjs_context_get_profiler(context);
382 
383     gjs_profiler_set_filename(profiler, "dont-conflict-with-other-test.syscap");
384     gjs_profiler_start(profiler);
385 
386     for (size_t ix = 0; ix < 100; ix++) {
387         GError *error = nullptr;
388         int estatus;
389 
390 #define TESTJS "[1,5,7,1,2,3,67,8].sort()"
391 
392         if (!gjs_context_eval(context, TESTJS, -1, "<input>", &estatus, &error))
393             g_printerr("ERROR: %s", error->message);
394 
395 #undef TESTJS
396     }
397 
398     gjs_profiler_stop(profiler);
399 
400     if (g_unlink("dont-conflict-with-other-test.syscap") != 0)
401         g_message("Temp profiler file not deleted");
402 }
403 
gjstest_test_safe_integer_max(GjsUnitTestFixture * fx,const void *)404 static void gjstest_test_safe_integer_max(GjsUnitTestFixture* fx, const void*) {
405     JS::RootedObject number_class_object(fx->cx);
406     JS::RootedValue safe_value(fx->cx);
407 
408     g_assert_true(
409         JS_GetClassObject(fx->cx, JSProto_Number, &number_class_object));
410     g_assert_true(JS_GetProperty(fx->cx, number_class_object,
411                                  "MAX_SAFE_INTEGER", &safe_value));
412 
413     g_assert_cmpint(safe_value.toNumber(), ==, max_safe_big_number<int64_t>());
414 }
415 
gjstest_test_safe_integer_min(GjsUnitTestFixture * fx,const void *)416 static void gjstest_test_safe_integer_min(GjsUnitTestFixture* fx, const void*) {
417     JS::RootedObject number_class_object(fx->cx);
418     JS::RootedValue safe_value(fx->cx);
419 
420     g_assert_true(
421         JS_GetClassObject(fx->cx, JSProto_Number, &number_class_object));
422     g_assert_true(JS_GetProperty(fx->cx, number_class_object,
423                                  "MIN_SAFE_INTEGER", &safe_value));
424 
425     g_assert_cmpint(safe_value.toNumber(), ==, min_safe_big_number<int64_t>());
426 }
427 
428 int
main(int argc,char ** argv)429 main(int    argc,
430      char **argv)
431 {
432     /* Avoid interference in the tests from stray environment variable */
433     g_unsetenv("GJS_ENABLE_PROFILER");
434     g_unsetenv("GJS_TRACE_FD");
435 
436     g_test_init(&argc, &argv, NULL);
437 
438     g_test_add_func("/gjs/context/construct/destroy", gjstest_test_func_gjs_context_construct_destroy);
439     g_test_add_func("/gjs/context/construct/eval", gjstest_test_func_gjs_context_construct_eval);
440     g_test_add_func("/gjs/context/eval/non-zero-terminated",
441                     gjstest_test_func_gjs_context_eval_non_zero_terminated);
442     g_test_add_func("/gjs/context/exit", gjstest_test_func_gjs_context_exit);
443     g_test_add_func("/gjs/gobject/js_defined_type", gjstest_test_func_gjs_gobject_js_defined_type);
444     g_test_add_func("/gjs/gobject/without_introspection",
445                     gjstest_test_func_gjs_gobject_without_introspection);
446     g_test_add_func("/gjs/profiler/start_stop", gjstest_test_profiler_start_stop);
447     g_test_add_func("/util/misc/strv/concat/null",
448                     gjstest_test_func_util_misc_strv_concat_null);
449     g_test_add_func("/util/misc/strv/concat/pointers",
450                     gjstest_test_func_util_misc_strv_concat_pointers);
451 
452 #define ADD_JSAPI_UTIL_TEST(path, func)                            \
453     g_test_add("/gjs/jsapi/util/" path, GjsUnitTestFixture, NULL,  \
454                gjs_unit_test_fixture_setup, func,                  \
455                gjs_unit_test_fixture_teardown)
456 
457     ADD_JSAPI_UTIL_TEST("error/throw",
458                         gjstest_test_func_gjs_jsapi_util_error_throw);
459     ADD_JSAPI_UTIL_TEST("string/js/string/utf8",
460                         gjstest_test_func_gjs_jsapi_util_string_js_string_utf8);
461     ADD_JSAPI_UTIL_TEST("string/utf8-nchars-to-js",
462                         test_jsapi_util_string_utf8_nchars_to_js);
463     ADD_JSAPI_UTIL_TEST("string/char16_data",
464                         test_jsapi_util_string_char16_data);
465     ADD_JSAPI_UTIL_TEST("string/to_ucs4",
466                         test_jsapi_util_string_to_ucs4);
467     ADD_JSAPI_UTIL_TEST("debug_string/valid-utf8",
468                         test_jsapi_util_debug_string_valid_utf8);
469     ADD_JSAPI_UTIL_TEST("debug_string/invalid-utf8",
470                         test_jsapi_util_debug_string_invalid_utf8);
471     ADD_JSAPI_UTIL_TEST("debug_string/object-with-complicated-to-string",
472                         test_jsapi_util_debug_string_object_with_complicated_to_string);
473 
474     ADD_JSAPI_UTIL_TEST("gi/args/safe-integer/max",
475                         gjstest_test_safe_integer_max);
476     ADD_JSAPI_UTIL_TEST("gi/args/safe-integer/min",
477                         gjstest_test_safe_integer_min);
478 
479 #undef ADD_JSAPI_UTIL_TEST
480 
481     gjs_test_add_tests_for_coverage ();
482     gjs_test_add_tests_for_parse_call_args();
483     gjs_test_add_tests_for_rooting();
484 
485     g_test_run();
486 
487     return 0;
488 }
489