Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2008 litl, LLC
4 : : // SPDX-FileCopyrightText: 2009 Red Hat, Inc.
5 : :
6 : : #include <config.h>
7 : :
8 : : #include <stddef.h> // for size_t
9 : : #include <stdint.h>
10 : :
11 : : #include <string>
12 : : #include <vector>
13 : :
14 : : #include <glib.h>
15 : :
16 : : #include <js/CallArgs.h>
17 : : #include <js/CharacterEncoding.h> // for JS_EncodeStringToUTF8
18 : : #include <js/Conversions.h>
19 : : #include <js/Exception.h>
20 : : #include <js/PropertyAndElement.h> // for JS_DefineFunctions
21 : : #include <js/PropertySpec.h> // for JS_FN, JSFunctionSpec, JS_FS_END
22 : : #include <js/RootingAPI.h>
23 : : #include <js/TypeDecls.h>
24 : : #include <js/Utility.h> // for UniqueChars
25 : : #include <js/Value.h>
26 : : #include <jsapi.h> // for JS_NewPlainObject
27 : :
28 : : #include "gjs/deprecation.h"
29 : : #include "gjs/global.h"
30 : : #include "gjs/jsapi-util.h"
31 : : #include "gjs/macros.h"
32 : : #include "modules/print.h"
33 : :
34 : : GJS_JSAPI_RETURN_CONVENTION
35 : 2 : static bool gjs_log(JSContext* cx, unsigned argc, JS::Value* vp) {
36 : 2 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
37 : :
38 [ - + ]: 2 : if (argc != 1) {
39 : 0 : gjs_throw(cx, "Must pass a single argument to log()");
40 : 0 : return false;
41 : : }
42 : :
43 : : /* JS::ToString might throw, in which case we will only log that the value
44 : : * could not be converted to string */
45 : 2 : JS::AutoSaveExceptionState exc_state(cx);
46 : 2 : JS::RootedString jstr{cx, JS::ToString(cx, args[0])};
47 : 2 : exc_state.restore();
48 : :
49 [ - + ]: 2 : if (!jstr) {
50 : 0 : g_message("JS LOG: <cannot convert value to string>");
51 : 0 : return true;
52 : : }
53 : :
54 : 2 : JS::UniqueChars s(JS_EncodeStringToUTF8(cx, jstr));
55 [ - + ]: 2 : if (!s)
56 : 0 : return false;
57 : :
58 : 2 : g_message("JS LOG: %s", s.get());
59 : :
60 : 2 : args.rval().setUndefined();
61 : 2 : return true;
62 : 2 : }
63 : :
64 : : GJS_JSAPI_RETURN_CONVENTION
65 : 88 : static bool gjs_log_error(JSContext* cx, unsigned argc, JS::Value* vp) {
66 : 88 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
67 : :
68 [ + + + - : 88 : if ((argc != 1 && argc != 2) || !args[0].isObject()) {
- + - + ]
69 : 0 : gjs_throw(
70 : : cx,
71 : : "Must pass an exception and optionally a message to logError()");
72 : 0 : return false;
73 : : }
74 : :
75 : 88 : JS::RootedString jstr(cx);
76 : :
77 [ + + ]: 88 : if (argc == 2) {
78 : : /* JS::ToString might throw, in which case we will only log that the
79 : : * value could not be converted to string */
80 : 35 : JS::AutoSaveExceptionState exc_state(cx);
81 : 35 : jstr = JS::ToString(cx, args[1]);
82 : 35 : exc_state.restore();
83 : 35 : }
84 : :
85 : 88 : gjs_log_exception_full(cx, args[0], jstr, G_LOG_LEVEL_WARNING);
86 : :
87 : 88 : args.rval().setUndefined();
88 : 88 : return true;
89 : 88 : }
90 : :
91 : : GJS_JSAPI_RETURN_CONVENTION
92 : 3245 : static bool gjs_print_parse_args(JSContext* cx, const JS::CallArgs& args,
93 : : std::string* buffer) {
94 : 3245 : g_assert(buffer && "forgot out parameter");
95 : 3245 : buffer->clear();
96 [ + + ]: 6490 : for (unsigned n = 0; n < args.length(); ++n) {
97 : : /* JS::ToString might throw, in which case we will only log that the
98 : : * value could not be converted to string */
99 : 3245 : JS::AutoSaveExceptionState exc_state(cx);
100 : 3245 : JS::RootedString jstr{cx, JS::ToString(cx, args[n])};
101 : 3245 : exc_state.restore();
102 : :
103 [ + - ]: 3245 : if (jstr) {
104 : 3245 : JS::UniqueChars s(JS_EncodeStringToUTF8(cx, jstr));
105 [ - + ]: 3245 : if (!s)
106 : 0 : return false;
107 : :
108 : 3245 : *buffer += s.get();
109 [ - + ]: 3245 : if (n < (args.length() - 1))
110 : 0 : *buffer += ' ';
111 [ + - ]: 3245 : } else {
112 : 0 : *buffer = "<invalid string>";
113 : 0 : return true;
114 : : }
115 [ + - + - ]: 3245 : }
116 : 3245 : return true;
117 : : }
118 : :
119 : : GJS_JSAPI_RETURN_CONVENTION
120 : 3245 : static bool gjs_print(JSContext* cx, unsigned argc, JS::Value* vp) {
121 : 3245 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
122 : :
123 : 3245 : std::string buffer;
124 [ - + ]: 3245 : if (!gjs_print_parse_args(cx, args, &buffer))
125 : 0 : return false;
126 : :
127 : 3245 : g_print("%s\n", buffer.c_str());
128 : :
129 : 3245 : args.rval().setUndefined();
130 : 3245 : return true;
131 : 3245 : }
132 : :
133 : : GJS_JSAPI_RETURN_CONVENTION
134 : 0 : static bool gjs_printerr(JSContext* cx, unsigned argc, JS::Value* vp) {
135 : 0 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
136 : :
137 : 0 : std::string buffer;
138 [ # # ]: 0 : if (!gjs_print_parse_args(cx, args, &buffer))
139 : 0 : return false;
140 : :
141 : 0 : g_printerr("%s\n", buffer.c_str());
142 : :
143 : 0 : args.rval().setUndefined();
144 : 0 : return true;
145 : 0 : }
146 : :
147 : : // The pretty-print functionality is best written in JS, but needs to be used
148 : : // from C++ code. This stores the prettyPrint() function in a slot on the global
149 : : // object so that it can be used internally by the Console module. This function
150 : : // is not available to user code.
151 : : GJS_JSAPI_RETURN_CONVENTION
152 : 255 : static bool set_pretty_print_function(JSContext*, unsigned argc,
153 : : JS::Value* vp) {
154 : 255 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
155 : :
156 : : // can only be called internally, so OK to assert correct arguments
157 : 255 : g_assert(args.length() == 2 && "setPrettyPrintFunction takes 2 arguments");
158 : :
159 : 255 : JS::Value v_global = args[0];
160 : 255 : JS::Value v_func = args[1];
161 : :
162 : 255 : g_assert(v_global.isObject() && "first argument must be an object");
163 : 255 : g_assert(v_func.isObject() && "second argument must be an object");
164 : :
165 : 255 : gjs_set_global_slot(&v_global.toObject(), GjsGlobalSlot::PRETTY_PRINT_FUNC,
166 : : v_func);
167 : :
168 : 255 : args.rval().setUndefined();
169 : 255 : return true;
170 : : }
171 : :
172 : : GJS_JSAPI_RETURN_CONVENTION
173 : 16 : static bool get_pretty_print_function(JSContext*, unsigned argc,
174 : : JS::Value* vp) {
175 : 16 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
176 : :
177 : 16 : g_assert(args.length() == 1 && "getPrettyPrintFunction takes 1 arguments");
178 : :
179 : 16 : JS::Value v_global = args[0];
180 : :
181 : 16 : g_assert(v_global.isObject() && "argument must be an object");
182 : :
183 : 16 : JS::Value pretty_print = gjs_get_global_slot(
184 : 16 : &v_global.toObject(), GjsGlobalSlot::PRETTY_PRINT_FUNC);
185 : :
186 : 16 : args.rval().set(pretty_print);
187 : 16 : return true;
188 : : }
189 : :
190 : : GJS_JSAPI_RETURN_CONVENTION
191 : 6 : static bool warn_deprecated_once_per_callsite(JSContext* cx, unsigned argc,
192 : : JS::Value* vp) {
193 : 6 : JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
194 : :
195 : 6 : g_assert(args.length() >= 1 &&
196 : : "warnDeprecatedOncePerCallsite takes at least 1 argument");
197 : :
198 : 6 : g_assert(
199 : : args[0].isInt32() &&
200 : : "warnDeprecatedOncePerCallsite argument 1 must be a message ID number");
201 : 6 : int32_t message_id = args[0].toInt32();
202 : 6 : g_assert(
203 : : message_id >= 0 &&
204 : : static_cast<uint32_t>(message_id) <
205 : : GjsDeprecationMessageId::LastValue &&
206 : : "warnDeprecatedOncePerCallsite argument 1 must be a message ID number");
207 : :
208 [ - + ]: 6 : if (args.length() == 1) {
209 : 0 : _gjs_warn_deprecated_once_per_callsite(
210 : : cx, GjsDeprecationMessageId(message_id), 2);
211 : 0 : return true;
212 : : }
213 : :
214 : 6 : std::vector<std::string> format_args;
215 [ + + ]: 18 : for (size_t ix = 1; ix < args.length(); ix++) {
216 : 12 : g_assert(args[ix].isString() &&
217 : : "warnDeprecatedOncePerCallsite subsequent arguments must be "
218 : : "strings");
219 : 12 : JS::RootedString v_format_arg{cx, args[ix].toString()};
220 : 12 : JS::UniqueChars format_arg = JS_EncodeStringToUTF8(cx, v_format_arg);
221 [ - + ]: 12 : if (!format_arg)
222 : 0 : return false;
223 : 12 : format_args.emplace_back(format_arg.get());
224 [ + - + - ]: 12 : }
225 : :
226 : 6 : _gjs_warn_deprecated_once_per_callsite(
227 : : cx, GjsDeprecationMessageId(message_id), format_args, 2);
228 : 6 : return true;
229 : 6 : }
230 : :
231 : : static constexpr JSFunctionSpec funcs[] = {
232 : : JS_FN("log", gjs_log, 1, GJS_MODULE_PROP_FLAGS),
233 : : JS_FN("logError", gjs_log_error, 2, GJS_MODULE_PROP_FLAGS),
234 : : JS_FN("print", gjs_print, 0, GJS_MODULE_PROP_FLAGS),
235 : : JS_FN("printerr", gjs_printerr, 0, GJS_MODULE_PROP_FLAGS),
236 : : JS_FN("setPrettyPrintFunction", set_pretty_print_function, 1,
237 : : GJS_MODULE_PROP_FLAGS),
238 : : JS_FN("getPrettyPrintFunction", get_pretty_print_function, 1,
239 : : GJS_MODULE_PROP_FLAGS),
240 : : JS_FN("warnDeprecatedOncePerCallsite", warn_deprecated_once_per_callsite, 1,
241 : : GJS_MODULE_PROP_FLAGS),
242 : : JS_FS_END};
243 : :
244 : : static constexpr JSPropertySpec props[] = {
245 : : JSPropertySpec::int32Value(
246 : : "PLATFORM_SPECIFIC_TYPELIB", GJS_MODULE_PROP_FLAGS,
247 : : GjsDeprecationMessageId::PlatformSpecificTypelib),
248 : : JS_PS_END};
249 : :
250 : 280 : bool gjs_define_print_stuff(JSContext* cx, JS::MutableHandleObject module) {
251 : 280 : module.set(JS_NewPlainObject(cx));
252 [ - + ]: 280 : if (!module)
253 : 0 : return false;
254 [ + - + - ]: 560 : return JS_DefineFunctions(cx, module, funcs) &&
255 : 560 : JS_DefineProperties(cx, module, props);
256 : : }
|