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