1/*
2 * Copyright (C) 2019 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "JSCOptions.h"
22
23#include "Options.h"
24#include <glib/gi18n-lib.h>
25#include <wtf/Vector.h>
26#include <wtf/glib/GUniquePtr.h>
27
28/**
29 * SECTION: JSCOptions
30 * @short_description: JavaScript options
31 * @title: JSCOptions
32 *
33 * JavaScript options allow changing the behavior of the JavaScript engine.
34 * They affect the way the engine works, so it's encouraged to set the options
35 * at the very beginning of the program execution, before any other JavaScript
36 * API call. Most of the options are only useful for testing and debugging.
37 * Only a few of them are documented; you can use the undocumented options at
38 * your own risk. (You can find the list of options in the WebKit source code).
39 *
40 * The API allows to set and get any option using the types defined in #JSCOptionType.
41 * You can also iterate all the available options using jsc_options_foreach() and
42 * passing a #JSCOptionsFunc callback. If your application uses #GOptionContext to handle
43 * command line arguments, you can easily integrate the JSCOptions by adding the
44 * #GOptionGroup returned by jsc_options_get_option_group().
45 *
46 * Since: 2.24
47 */
48
49using namespace JSC;
50
51using int32 = int32_t;
52using size = size_t;
53
54static bool valueFromGValue(const GValue* gValue, bool& value)
55{
56 value = g_value_get_boolean(gValue);
57 return true;
58}
59
60static void valueToGValue(bool value, GValue* gValue)
61{
62 g_value_set_boolean(gValue, value);
63}
64
65static bool valueFromGValue(const GValue* gValue, int32_t& value)
66{
67 value = g_value_get_int(gValue);
68 return true;
69}
70
71static void valueToGValue(int32_t value, GValue* gValue)
72{
73 g_value_set_int(gValue, value);
74}
75
76#if CPU(ADDRESS64)
77static bool valueFromGValue(const GValue* gValue, unsigned& value)
78{
79 value = g_value_get_uint(gValue);
80 return true;
81}
82
83static void valueToGValue(unsigned value, GValue* gValue)
84{
85 g_value_set_uint(gValue, value);
86}
87#endif
88
89static bool valueFromGValue(const GValue* gValue, size_t& value)
90{
91 value = GPOINTER_TO_SIZE(g_value_get_pointer(gValue));
92 return true;
93}
94
95static void valueToGValue(size_t value, GValue* gValue)
96{
97 g_value_set_pointer(gValue, GSIZE_TO_POINTER(value));
98}
99
100static bool valueFromGValue(const GValue* gValue, const char*& value)
101{
102 value = g_value_dup_string(gValue);
103 return true;
104}
105
106static void valueToGValue(const char* value, GValue* gValue)
107{
108 g_value_set_string(gValue, value);
109}
110
111static bool valueFromGValue(const GValue* gValue, double& value)
112{
113 value = g_value_get_double(gValue);
114 return true;
115}
116
117static void valueToGValue(double value, GValue* gValue)
118{
119 g_value_set_double(gValue, value);
120}
121
122static bool valueFromGValue(const GValue* gValue, OptionRange& value)
123{
124 return value.init(g_value_get_string(gValue) ? g_value_get_string(gValue) : "<null>");
125}
126
127static void valueToGValue(const OptionRange& value, GValue* gValue)
128{
129 const char* rangeString = value.rangeString();
130 g_value_set_string(gValue, !g_strcmp0(rangeString, "<null>") ? nullptr : rangeString);
131}
132
133static bool valueFromGValue(const GValue* gValue, GCLogging::Level& value)
134{
135 switch (g_value_get_uint(gValue)) {
136 case 0:
137 value = GCLogging::Level::None;
138 return true;
139 case 1:
140 value = GCLogging::Level::Basic;
141 return true;
142 case 2:
143 value = GCLogging::Level::Verbose;
144 return true;
145 default:
146 break;
147 }
148
149 return false;
150}
151
152static void valueToGValue(GCLogging::Level value, GValue* gValue)
153{
154 switch (value) {
155 case GCLogging::Level::None:
156 g_value_set_uint(gValue, 0);
157 break;
158 case GCLogging::Level::Basic:
159 g_value_set_uint(gValue, 1);
160 break;
161 case GCLogging::Level::Verbose:
162 g_value_set_uint(gValue, 2);
163 break;
164 }
165}
166
167static gboolean jscOptionsSetValue(const char* option, const GValue* value)
168{
169#define FOR_EACH_OPTION(type_, name_, defaultValue_, availability_, description_) \
170 if (!g_strcmp0(#name_, option)) { \
171 type_ valueToSet; \
172 if (!valueFromGValue(value, valueToSet)) \
173 return FALSE; \
174 Options::name_() = valueToSet; \
175 return TRUE; \
176 }
177
178 Options::initialize();
179 JSC_OPTIONS(FOR_EACH_OPTION)
180#undef FOR_EACH_OPTION
181
182 return FALSE;
183}
184
185static gboolean jscOptionsGetValue(const char* option, GValue* value)
186{
187#define FOR_EACH_OPTION(type_, name_, defaultValue_, availability_, description_) \
188 if (!g_strcmp0(#name_, option)) { \
189 type_ valueToGet = Options::name_(); \
190 valueToGValue(valueToGet, value); \
191 return TRUE; \
192 }
193
194 Options::initialize();
195 JSC_OPTIONS(FOR_EACH_OPTION)
196#undef FOR_EACH_OPTION
197
198 return FALSE;
199}
200
201/**
202 * jsc_options_set_boolean:
203 * @option: the option identifier
204 * @value: the value to set
205 *
206 * Set @option as a #gboolean value.
207 *
208 * Returns: %TRUE if option was correctly set or %FALSE otherwise.
209 *
210 * Since: 2.24
211 */
212gboolean jsc_options_set_boolean(const char* option, gboolean value)
213{
214 g_return_val_if_fail(option, FALSE);
215
216 GValue gValue = G_VALUE_INIT;
217 g_value_init(&gValue, G_TYPE_BOOLEAN);
218 g_value_set_boolean(&gValue, value);
219 return jscOptionsSetValue(option, &gValue);
220}
221
222/**
223 * jsc_options_get_boolean:
224 * @option: the option identifier
225 * @value: (out): return location for the option value
226 *
227 * Get @option as a #gboolean value.
228 *
229 * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist
230 *
231 * Since: 2.24
232 */
233gboolean jsc_options_get_boolean(const char* option, gboolean* value)
234{
235 g_return_val_if_fail(option, FALSE);
236 g_return_val_if_fail(value, FALSE);
237
238 GValue gValue = G_VALUE_INIT;
239 g_value_init(&gValue, G_TYPE_BOOLEAN);
240 if (!jscOptionsGetValue(option, &gValue))
241 return FALSE;
242
243 *value = g_value_get_boolean(&gValue);
244 return TRUE;
245}
246
247/**
248 * jsc_options_set_int:
249 * @option: the option identifier
250 * @value: the value to set
251 *
252 * Set @option as a #gint value.
253 *
254 * Returns: %TRUE if option was correctly set or %FALSE otherwise.
255 *
256 * Since: 2.24
257 */
258gboolean jsc_options_set_int(const char* option, gint value)
259{
260 g_return_val_if_fail(option, FALSE);
261
262 GValue gValue = G_VALUE_INIT;
263 g_value_init(&gValue, G_TYPE_INT);
264 g_value_set_int(&gValue, value);
265 return jscOptionsSetValue(option, &gValue);
266}
267
268/**
269 * jsc_options_get_int:
270 * @option: the option identifier
271 * @value: (out): return location for the option value
272 *
273 * Get @option as a #gint value.
274 *
275 * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist
276 *
277 * Since: 2.24
278 */
279gboolean jsc_options_get_int(const char* option, gint* value)
280{
281 g_return_val_if_fail(option, FALSE);
282 g_return_val_if_fail(value, FALSE);
283
284 GValue gValue = G_VALUE_INIT;
285 g_value_init(&gValue, G_TYPE_INT);
286 if (!jscOptionsGetValue(option, &gValue))
287 return FALSE;
288
289 *value = g_value_get_int(&gValue);
290 return TRUE;
291}
292
293/**
294 * jsc_options_set_uint:
295 * @option: the option identifier
296 * @value: the value to set
297 *
298 * Set @option as a #guint value.
299 *
300 * Returns: %TRUE if option was correctly set or %FALSE otherwise.
301 *
302 * Since: 2.24
303 */
304gboolean jsc_options_set_uint(const char* option, guint value)
305{
306 g_return_val_if_fail(option, FALSE);
307
308 GValue gValue = G_VALUE_INIT;
309 g_value_init(&gValue, G_TYPE_UINT);
310 g_value_set_uint(&gValue, value);
311 return jscOptionsSetValue(option, &gValue);
312}
313
314/**
315 * jsc_options_get_uint:
316 * @option: the option identifier
317 * @value: (out): return location for the option value
318 *
319 * Get @option as a #guint value.
320 *
321 * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist
322 *
323 * Since: 2.24
324 */
325gboolean jsc_options_get_uint(const char* option, guint* value)
326{
327 g_return_val_if_fail(option, FALSE);
328 g_return_val_if_fail(value, FALSE);
329
330 GValue gValue = G_VALUE_INIT;
331 g_value_init(&gValue, G_TYPE_UINT);
332 if (!jscOptionsGetValue(option, &gValue))
333 return FALSE;
334
335 *value = g_value_get_uint(&gValue);
336 return TRUE;
337}
338
339/**
340 * jsc_options_set_size:
341 * @option: the option identifier
342 * @value: the value to set
343 *
344 * Set @option as a #gsize value.
345 *
346 * Returns: %TRUE if option was correctly set or %FALSE otherwise.
347 *
348 * Since: 2.24
349 */
350gboolean jsc_options_set_size(const char* option, gsize value)
351{
352 g_return_val_if_fail(option, FALSE);
353
354 GValue gValue = G_VALUE_INIT;
355 g_value_init(&gValue, G_TYPE_POINTER);
356 g_value_set_pointer(&gValue, GSIZE_TO_POINTER(value));
357 return jscOptionsSetValue(option, &gValue);
358}
359
360/**
361 * jsc_options_get_size:
362 * @option: the option identifier
363 * @value: (out): return location for the option value
364 *
365 * Get @option as a #gsize value.
366 *
367 * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist
368 *
369 * Since: 2.24
370 */
371gboolean jsc_options_get_size(const char* option, gsize* value)
372{
373 g_return_val_if_fail(option, FALSE);
374 g_return_val_if_fail(value, FALSE);
375
376 GValue gValue = G_VALUE_INIT;
377 g_value_init(&gValue, G_TYPE_POINTER);
378 if (!jscOptionsGetValue(option, &gValue))
379 return FALSE;
380
381 *value = GPOINTER_TO_SIZE(g_value_get_pointer(&gValue));
382 return TRUE;
383}
384
385/**
386 * jsc_options_set_double:
387 * @option: the option identifier
388 * @value: the value to set
389 *
390 * Set @option as a #gdouble value.
391 *
392 * Returns: %TRUE if option was correctly set or %FALSE otherwise.
393 *
394 * Since: 2.24
395 */
396gboolean jsc_options_set_double(const char* option, gdouble value)
397{
398 g_return_val_if_fail(option, FALSE);
399
400 GValue gValue = G_VALUE_INIT;
401 g_value_init(&gValue, G_TYPE_DOUBLE);
402 g_value_set_double(&gValue, value);
403 return jscOptionsSetValue(option, &gValue);
404}
405
406/**
407 * jsc_options_get_double:
408 * @option: the option identifier
409 * @value: (out): return location for the option value
410 *
411 * Get @option as a #gdouble value.
412 *
413 * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist
414 *
415 * Since: 2.24
416 */
417gboolean jsc_options_get_double(const char* option, gdouble* value)
418{
419 g_return_val_if_fail(option, FALSE);
420 g_return_val_if_fail(value, FALSE);
421
422 GValue gValue = G_VALUE_INIT;
423 g_value_init(&gValue, G_TYPE_DOUBLE);
424 if (!jscOptionsGetValue(option, &gValue))
425 return FALSE;
426
427 *value = g_value_get_double(&gValue);
428 return TRUE;
429}
430
431/**
432 * jsc_options_set_string:
433 * @option: the option identifier
434 * @value: the value to set
435 *
436 * Set @option as a string.
437 *
438 * Returns: %TRUE if option was correctly set or %FALSE otherwise.
439 *
440 * Since: 2.24
441 */
442gboolean jsc_options_set_string(const char* option, const char* value)
443{
444 g_return_val_if_fail(option, FALSE);
445
446 GValue gValue = G_VALUE_INIT;
447 g_value_init(&gValue, G_TYPE_STRING);
448 g_value_set_string(&gValue, value);
449 bool success = jscOptionsSetValue(option, &gValue);
450 g_value_unset(&gValue);
451 return success;
452}
453
454/**
455 * jsc_options_get_string:
456 * @option: the option identifier
457 * @value: (out): return location for the option value
458 *
459 * Get @option as a string.
460 *
461 * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist
462 *
463 * Since: 2.24
464 */
465gboolean jsc_options_get_string(const char* option, char** value)
466{
467 g_return_val_if_fail(option, FALSE);
468 g_return_val_if_fail(value, FALSE);
469
470 GValue gValue = G_VALUE_INIT;
471 g_value_init(&gValue, G_TYPE_STRING);
472 if (!jscOptionsGetValue(option, &gValue))
473 return FALSE;
474
475 *value = g_value_dup_string(&gValue);
476 g_value_unset(&gValue);
477 return TRUE;
478}
479
480/**
481 * jsc_options_set_range_string:
482 * @option: the option identifier
483 * @value: the value to set
484 *
485 * Set @option as a range string. The string must be in the
486 * format <emphasis>[!]&lt;low&gt;[:&lt;high&gt;]</emphasis> where low and high are #guint values.
487 * Values between low and high (both included) will be considered in
488 * the range, unless <emphasis>!</emphasis> is used to invert the range.
489 *
490 * Returns: %TRUE if option was correctly set or %FALSE otherwise.
491 *
492 * Since: 2.24
493 */
494gboolean jsc_options_set_range_string(const char* option, const char* value)
495{
496 g_return_val_if_fail(option, FALSE);
497
498 GValue gValue = G_VALUE_INIT;
499 g_value_init(&gValue, G_TYPE_STRING);
500 g_value_set_string(&gValue, value);
501 bool success = jscOptionsSetValue(option, &gValue);
502 g_value_unset(&gValue);
503 return success;
504}
505
506/**
507 * jsc_options_get_range_string:
508 * @option: the option identifier
509 * @value: (out): return location for the option value
510 *
511 * Get @option as a range string. The string must be in the
512 * format <emphasis>[!]&lt;low&gt;[:&lt;high&gt;]</emphasis> where low and high are #guint values.
513 * Values between low and high (both included) will be considered in
514 * the range, unless <emphasis>!</emphasis> is used to invert the range.
515 *
516 * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist
517 *
518 * Since: 2.24
519 */
520gboolean jsc_options_get_range_string(const char* option, char** value)
521{
522 g_return_val_if_fail(option, FALSE);
523 g_return_val_if_fail(value, FALSE);
524
525 GValue gValue = G_VALUE_INIT;
526 g_value_init(&gValue, G_TYPE_STRING);
527 if (!jscOptionsGetValue(option, &gValue))
528 return FALSE;
529
530 *value = g_value_dup_string(&gValue);
531 g_value_unset(&gValue);
532 return TRUE;
533}
534
535static JSCOptionType jscOptionsType(bool)
536{
537 return JSC_OPTION_BOOLEAN;
538}
539
540static JSCOptionType jscOptionsType(int)
541{
542 return JSC_OPTION_INT;
543}
544
545#if CPU(ADDRESS64)
546static JSCOptionType jscOptionsType(unsigned)
547{
548 return JSC_OPTION_UINT;
549}
550#endif
551
552static JSCOptionType jscOptionsType(size_t)
553{
554 return JSC_OPTION_SIZE;
555}
556
557static JSCOptionType jscOptionsType(double)
558{
559 return JSC_OPTION_DOUBLE;
560}
561
562static JSCOptionType jscOptionsType(const char*)
563{
564 return JSC_OPTION_STRING;
565}
566
567static JSCOptionType jscOptionsType(const OptionRange&)
568{
569 return JSC_OPTION_RANGE_STRING;
570}
571
572/**
573 * JSCOptionType:
574 * @JSC_OPTION_BOOLEAN: A #gboolean option type.
575 * @JSC_OPTION_INT: A #gint option type.
576 * @JSC_OPTION_UINT: A #guint option type.
577 * @JSC_OPTION_SIZE: A #gsize options type.
578 * @JSC_OPTION_DOUBLE: A #gdouble options type.
579 * @JSC_OPTION_STRING: A string option type.
580 * @JSC_OPTION_RANGE_STRING: A range string option type.
581 *
582 * Enum values for options types.
583 *
584 * Since: 2.24
585 */
586
587/**
588 * JSCOptionsFunc:
589 * @option: the option name
590 * @type: the option #JSCOptionType
591 * @description: (nullable): the option description, or %NULL
592 * @user_data: user data
593 *
594 * Function used to iterate options.
595 *
596 * Not that @description string is not localized.
597 *
598 * Returns: %TRUE to stop the iteration, or %FALSE otherwise
599 *
600 * Since: 2.24
601 */
602
603/**
604 * jsc_options_foreach:
605 * @function: (scope call): a #JSCOptionsFunc callback
606 * @user_data: callback user data
607 *
608 * Iterates all available options calling @function for each one. Iteration can
609 * stop early if @function returns %FALSE.
610 *
611 * Since: 2.24
612 */
613void jsc_options_foreach(JSCOptionsFunc function, gpointer userData)
614{
615 g_return_if_fail(function);
616
617#define FOR_EACH_OPTION(type_, name_, defaultValue_, availability_, description_) \
618 if (Options::Availability::availability_ == Options::Availability::Normal \
619 || Options::isAvailable(Options::name_##ID, Options::Availability::availability_)) { \
620 type_ defaultValue { }; \
621 auto optionType = jscOptionsType(defaultValue); \
622 if (function (#name_, optionType, description_, userData)) \
623 return; \
624 }
625
626 Options::initialize();
627 JSC_OPTIONS(FOR_EACH_OPTION)
628#undef FOR_EACH_OPTION
629}
630
631static gboolean setOptionEntry(const char* optionNameFull, const char* value, gpointer, GError** error)
632{
633 const char* optionName = optionNameFull + 6; // Remove the --jsc- prefix.
634 GUniquePtr<char> option(g_strdup_printf("%s=%s", optionName, value));
635 if (!Options::setOption(option.get())) {
636 g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Failed parse value '%s' for %s", value, optionNameFull);
637 return FALSE;
638 }
639 return TRUE;
640}
641
642/**
643 * jsc_options_get_option_group:
644 *
645 * Create a #GOptionGroup to handle JSCOptions as command line arguments.
646 * The options will be exposed as command line arguments with the form
647 * <emphasis>--jsc-&lt;option&gt;=&lt;value&gt;</emphasis>.
648 * Each entry in the returned #GOptionGroup is configured to apply the
649 * corresponding option during command line parsing. Applications only need to
650 * pass the returned group to g_option_context_add_group(), and the rest will
651 * be taken care for automatically.
652 *
653 * Returns: (transfer full): a #GOptionGroup for the JSCOptions
654 *
655 * Since: 2.24
656 */
657GOptionGroup* jsc_options_get_option_group(void)
658{
659 // GOptionEntry works with const strings, so we need to keep the option names around.
660 auto* names = new Vector<GUniquePtr<char>>;
661 GOptionGroup* group = g_option_group_new("jsc", _("JSC Options"), _("Show JSC Options"), names, [] (gpointer data) {
662 delete static_cast<Vector<GUniquePtr<char>>*>(data);
663 });
664 g_option_group_set_translation_domain(group, GETTEXT_PACKAGE);
665
666 GArray* entries = g_array_new(TRUE, TRUE, sizeof(GOptionEntry));
667#define FOR_EACH_OPTION(type_, name_, defaultValue_, availability_, description_) \
668 if (Options::Availability::availability_ == Options::Availability::Normal \
669 || Options::isAvailable(Options::name_##ID, Options::Availability::availability_)) { \
670 GUniquePtr<char> name(g_strdup_printf("jsc-%s", #name_)); \
671 entries = g_array_set_size(entries, entries->len + 1); \
672 GOptionEntry* entry = &g_array_index(entries, GOptionEntry, entries->len - 1); \
673 entry->long_name = name.get(); \
674 entry->arg = G_OPTION_ARG_CALLBACK; \
675 entry->arg_data = reinterpret_cast<gpointer>(setOptionEntry); \
676 entry->description = description_; \
677 names->append(WTFMove(name)); \
678 }
679
680 Options::initialize();
681 JSC_OPTIONS(FOR_EACH_OPTION)
682#undef FOR_EACH_OPTION
683
684 g_option_group_add_entries(group, reinterpret_cast<GOptionEntry*>(entries->data));
685 return group;
686}
687
688/**
689 * JSC_OPTIONS_USE_JIT:
690 *
691 * Allows the executable pages to be allocated for JIT and thunks if %TRUE.
692 * Option type: %JSC_OPTION_BOOLEAN
693 * Default value: %TRUE.
694 *
695 * Since: 2.24
696 */
697
698/**
699 * JSC_OPTIONS_USE_DFG:
700 *
701 * Allows the DFG JIT to be used if %TRUE.
702 * Option type: %JSC_OPTION_BOOLEAN
703 * Default value: %TRUE.
704 *
705 * Since: 2.24
706 */
707
708/**
709 * JSC_OPTIONS_USE_FTL:
710 *
711 * Allows the FTL JIT to be used if %TRUE.
712 * Option type: %JSC_OPTION_BOOLEAN
713 * Default value: %TRUE.
714 *
715 * Since: 2.24
716 */
717
718/**
719 * JSC_OPTIONS_USE_LLINT:
720 *
721 * Allows the LLINT to be used if %TRUE.
722 * Option type: %JSC_OPTION_BOOLEAN
723 * Default value: %TRUE.
724 *
725 * Since: 2.24
726 */
727