Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : /* vim: set ts=8 sw=4 et tw=78: */
3 : : // SPDX-License-Identifier: MPL-1.1 OR GPL-2.0-or-later OR LGPL-2.1-or-later
4 : : // SPDX-FileCopyrightText: 1998 Netscape Communications Corporation
5 : :
6 : : #include <config.h> // for HAVE_READLINE_READLINE_H
7 : :
8 : : #ifdef HAVE_SIGNAL_H
9 : : # include <setjmp.h>
10 : : # include <signal.h>
11 : : # ifdef _WIN32
12 : : # define sigjmp_buf jmp_buf
13 : : # define siglongjmp(e, v) longjmp (e, v)
14 : : # define sigsetjmp(v, m) setjmp (v)
15 : : # endif
16 : : #endif
17 : :
18 : : #ifdef HAVE_READLINE_READLINE_H
19 : : # include <stdio.h> // include before readline/readline.h
20 : :
21 : : # include <readline/history.h>
22 : : # include <readline/readline.h>
23 : : #endif
24 : :
25 : : #include <string>
26 : :
27 : : #include <glib.h>
28 : : #include <glib/gprintf.h> // for g_fprintf
29 : :
30 : : #include <js/CallAndConstruct.h>
31 : : #include <js/CallArgs.h>
32 : : #include <js/CharacterEncoding.h> // for JS_EncodeStringToUTF8
33 : : #include <js/CompilationAndEvaluation.h>
34 : : #include <js/CompileOptions.h>
35 : : #include <js/ErrorReport.h>
36 : : #include <js/Exception.h>
37 : : #include <js/GlobalObject.h> // for CurrentGlobalOrNull
38 : : #include <js/PropertyAndElement.h>
39 : : #include <js/RootingAPI.h>
40 : : #include <js/SourceText.h>
41 : : #include <js/TypeDecls.h>
42 : : #include <js/Utility.h> // for UniqueChars
43 : : #include <js/Value.h>
44 : : #include <js/ValueArray.h>
45 : : #include <js/Warnings.h>
46 : : #include <jsapi.h> // for JS_NewPlainObject
47 : :
48 : : #include "gjs/atoms.h"
49 : : #include "gjs/context-private.h"
50 : : #include "gjs/global.h"
51 : : #include "gjs/jsapi-util.h"
52 : : #include "gjs/macros.h"
53 : : #include "modules/console.h"
54 : :
55 : : namespace mozilla {
56 : : union Utf8Unit;
57 : : }
58 : :
59 : 0 : static void gjs_console_warning_reporter(JSContext*, JSErrorReport* report) {
60 : 0 : JS::PrintError(stderr, report, /* reportWarnings = */ true);
61 : 0 : }
62 : :
63 : : /* Based on js::shell::AutoReportException from SpiderMonkey. */
64 : : class AutoReportException {
65 : : JSContext *m_cx;
66 : :
67 : : public:
68 : 0 : explicit AutoReportException(JSContext *cx) : m_cx(cx) {}
69 : :
70 : 0 : ~AutoReportException() {
71 [ # # ]: 0 : if (!JS_IsExceptionPending(m_cx))
72 : 0 : return;
73 : :
74 : : /* Get exception object before printing and clearing exception. */
75 : 0 : JS::ExceptionStack exnStack(m_cx);
76 : 0 : JS::ErrorReportBuilder report(m_cx);
77 [ # # # # ]: 0 : if (!JS::StealPendingExceptionStack(m_cx, &exnStack) ||
78 [ # # ]: 0 : !report.init(m_cx, exnStack,
79 : : JS::ErrorReportBuilder::NoSideEffects)) {
80 : 0 : g_printerr("(Unable to print exception)\n");
81 : 0 : JS_ClearPendingException(m_cx);
82 : 0 : return;
83 : : }
84 : :
85 : 0 : g_assert(!report.report()->isWarning());
86 : :
87 : 0 : JS::PrintError(stderr, report, /* reportWarnings = */ false);
88 : :
89 [ # # ]: 0 : if (exnStack.stack()) {
90 : : JS::UniqueChars stack_str{
91 : 0 : format_saved_frame(m_cx, exnStack.stack(), 2)};
92 [ # # ]: 0 : if (!stack_str) {
93 : 0 : g_printerr("(Unable to print stack trace)\n");
94 : : } else {
95 : : GjsAutoChar encoded_stack_str{g_filename_from_utf8(
96 : 0 : stack_str.get(), -1, nullptr, nullptr, nullptr)};
97 [ # # ]: 0 : if (!encoded_stack_str)
98 : 0 : g_printerr("(Unable to print stack trace)\n");
99 : : else
100 : 0 : g_printerr("%s", stack_str.get());
101 : 0 : }
102 : 0 : }
103 : :
104 : 0 : JS_ClearPendingException(m_cx);
105 [ # # # # ]: 0 : }
106 : : };
107 : :
108 : :
109 : : // Adapted from https://stackoverflow.com/a/17035073/172999
110 : : class AutoCatchCtrlC {
111 : : #ifdef HAVE_SIGNAL_H
112 : : void (*m_prev_handler)(int);
113 : :
114 : 0 : static void handler(int signal) {
115 [ # # ]: 0 : if (signal == SIGINT)
116 : 0 : siglongjmp(jump_buffer, 1);
117 : 0 : }
118 : :
119 : : public:
120 : : static sigjmp_buf jump_buffer;
121 : :
122 : 0 : AutoCatchCtrlC() {
123 : 0 : m_prev_handler = signal(SIGINT, &AutoCatchCtrlC::handler);
124 : 0 : }
125 : :
126 : 0 : ~AutoCatchCtrlC() {
127 [ # # ]: 0 : if (m_prev_handler != SIG_ERR)
128 : 0 : signal(SIGINT, m_prev_handler);
129 : 0 : }
130 : :
131 : 0 : void raise_default() {
132 [ # # ]: 0 : if (m_prev_handler != SIG_ERR)
133 : 0 : signal(SIGINT, m_prev_handler);
134 : 0 : raise(SIGINT);
135 : 0 : }
136 : : #endif // HAVE_SIGNAL_H
137 : : };
138 : :
139 : : #ifdef HAVE_SIGNAL_H
140 : : sigjmp_buf AutoCatchCtrlC::jump_buffer;
141 : : #endif // HAVE_SIGNAL_H
142 : :
143 : 0 : [[nodiscard]] static bool gjs_console_readline(char** bufp,
144 : : const char* prompt) {
145 : : #ifdef HAVE_READLINE_READLINE_H
146 : : char *line;
147 : 0 : line = readline(prompt);
148 [ # # ]: 0 : if (!line)
149 : 0 : return false;
150 [ # # ]: 0 : if (line[0] != '\0')
151 : 0 : add_history(line);
152 : 0 : *bufp = line;
153 : : #else // !HAVE_READLINE_READLINE_H
154 : : char line[256];
155 : : fprintf(stdout, "%s", prompt);
156 : : fflush(stdout);
157 : : if (!fgets(line, sizeof line, stdin))
158 : : return false;
159 : : *bufp = g_strdup(line);
160 : : #endif // !HAVE_READLINE_READLINE_H
161 : 0 : return true;
162 : : }
163 : :
164 : 0 : std::string print_string_value(JSContext* cx, JS::HandleValue v_string) {
165 [ # # ]: 0 : if (!v_string.isString())
166 : 0 : return "[unexpected result from printing value]";
167 : :
168 : 0 : JS::RootedString printed_string(cx, v_string.toString());
169 : 0 : JS::AutoSaveExceptionState exc_state(cx);
170 : 0 : JS::UniqueChars chars(JS_EncodeStringToUTF8(cx, printed_string));
171 : 0 : exc_state.restore();
172 [ # # ]: 0 : if (!chars)
173 : 0 : return "[error printing value]";
174 : :
175 : 0 : return chars.get();
176 : 0 : }
177 : :
178 : : /* Return value of false indicates an uncatchable exception, rather than any
179 : : * exception. (This is because the exception should be auto-printed around the
180 : : * invocation of this function.)
181 : : */
182 : 0 : [[nodiscard]] static bool gjs_console_eval_and_print(JSContext* cx,
183 : : JS::HandleObject global,
184 : : const std::string& bytes,
185 : : int lineno) {
186 : 0 : JS::SourceText<mozilla::Utf8Unit> source;
187 [ # # ]: 0 : if (!source.init(cx, bytes.c_str(), bytes.size(),
188 : : JS::SourceOwnership::Borrowed))
189 : 0 : return false;
190 : :
191 : 0 : JS::CompileOptions options(cx);
192 : 0 : options.setFileAndLine("typein", lineno);
193 : :
194 : 0 : JS::RootedValue result(cx);
195 [ # # ]: 0 : if (!JS::Evaluate(cx, options, source, &result)) {
196 [ # # ]: 0 : if (!JS_IsExceptionPending(cx))
197 : 0 : return false;
198 : : }
199 : :
200 : 0 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
201 : 0 : gjs->schedule_gc_if_needed();
202 : :
203 [ # # ]: 0 : if (result.isUndefined())
204 : 0 : return true;
205 : :
206 : 0 : JS::AutoSaveExceptionState exc_state(cx);
207 : 0 : JS::RootedValue v_printed_string(cx);
208 : : JS::RootedValue v_pretty_print(
209 : 0 : cx, gjs_get_global_slot(global, GjsGlobalSlot::PRETTY_PRINT_FUNC));
210 : 0 : bool ok = JS::Call(cx, global, v_pretty_print, JS::HandleValueArray(result),
211 : : &v_printed_string);
212 [ # # ]: 0 : if (!ok)
213 : 0 : gjs_log_exception(cx);
214 : 0 : exc_state.restore();
215 : :
216 [ # # ]: 0 : if (ok) {
217 : 0 : g_fprintf(stdout, "%s\n",
218 : 0 : print_string_value(cx, v_printed_string).c_str());
219 : : } else {
220 : 0 : g_fprintf(stdout, "[error printing value]\n");
221 : : }
222 : :
223 : 0 : return true;
224 : 0 : }
225 : :
226 : : GJS_JSAPI_RETURN_CONVENTION
227 : : static bool
228 : 0 : gjs_console_interact(JSContext *context,
229 : : unsigned argc,
230 : : JS::Value *vp)
231 : : {
232 : 0 : JS::CallArgs argv = JS::CallArgsFromVp(argc, vp);
233 : : volatile bool eof, exit_warning; // accessed after setjmp()
234 : 0 : JS::RootedObject global{context, JS::CurrentGlobalOrNull(context)};
235 : : char* temp_buf;
236 : : volatile int lineno; // accessed after setjmp()
237 : : volatile int startline; // accessed after setjmp()
238 : :
239 : : #ifndef HAVE_READLINE_READLINE_H
240 : : int rl_end = 0; // nonzero if using readline and any text is typed in
241 : : #endif
242 : :
243 : 0 : JS::SetWarningReporter(context, gjs_console_warning_reporter);
244 : :
245 : 0 : AutoCatchCtrlC ctrl_c;
246 : :
247 : : // Separate initialization from declaration because of possible overwriting
248 : : // when siglongjmp() jumps into this function
249 : 0 : eof = exit_warning = false;
250 : 0 : temp_buf = nullptr;
251 : 0 : lineno = 1;
252 : : do {
253 : : /*
254 : : * Accumulate lines until we get a 'compilable unit' - one that either
255 : : * generates an error (before running out of source) or that compiles
256 : : * cleanly. This should be whenever we get a complete statement that
257 : : * coincides with the end of a line.
258 : : */
259 : 0 : startline = lineno;
260 : 0 : std::string buffer;
261 : : do {
262 : : #ifdef HAVE_SIGNAL_H
263 : : // sigsetjmp() returns 0 if control flow encounters it normally, and
264 : : // nonzero if it's been jumped to. In the latter case, use a while
265 : : // loop so that we call sigsetjmp() a second time to reinit the jump
266 : : // buffer.
267 [ # # ]: 0 : while (sigsetjmp(AutoCatchCtrlC::jump_buffer, 1) != 0) {
268 : 0 : g_fprintf(stdout, "\n");
269 [ # # # # : 0 : if (buffer.empty() && rl_end == 0) {
# # ]
270 [ # # ]: 0 : if (!exit_warning) {
271 : 0 : g_fprintf(stdout,
272 : : "(To exit, press Ctrl+C again or Ctrl+D)\n");
273 : 0 : exit_warning = true;
274 : : } else {
275 : 0 : ctrl_c.raise_default();
276 : : }
277 : : } else {
278 : 0 : exit_warning = false;
279 : : }
280 : 0 : buffer.clear();
281 : 0 : startline = lineno = 1;
282 : : }
283 : : #endif // HAVE_SIGNAL_H
284 : :
285 [ # # ]: 0 : if (!gjs_console_readline(
286 [ # # ]: 0 : &temp_buf, startline == lineno ? "gjs> " : ".... ")) {
287 : 0 : eof = true;
288 : 0 : break;
289 : : }
290 : 0 : buffer += temp_buf;
291 : 0 : buffer += "\n";
292 : 0 : g_free(temp_buf);
293 : 0 : lineno++;
294 [ # # ]: 0 : } while (!JS_Utf8BufferIsCompilableUnit(context, global, buffer.c_str(),
295 : : buffer.size()));
296 : :
297 : : bool ok;
298 : : {
299 : 0 : AutoReportException are(context);
300 : 0 : ok = gjs_console_eval_and_print(context, global, buffer, startline);
301 : 0 : }
302 : 0 : exit_warning = false;
303 : :
304 : 0 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
305 [ # # # # ]: 0 : ok = gjs->run_jobs_fallible() && ok;
306 : :
307 [ # # ]: 0 : if (!ok) {
308 : : /* If this was an uncatchable exception, throw another uncatchable
309 : : * exception on up to the surrounding JS::Evaluate() in main(). This
310 : : * happens when you run gjs-console and type imports.system.exit(0);
311 : : * at the prompt. If we don't throw another uncatchable exception
312 : : * here, then it's swallowed and main() won't exit. */
313 : 0 : return false;
314 : : }
315 [ # # # # ]: 0 : } while (!eof);
316 : :
317 : 0 : g_fprintf(stdout, "\n");
318 : :
319 : 0 : argv.rval().setUndefined();
320 : 0 : return true;
321 : 0 : }
322 : :
323 : : bool
324 : 0 : gjs_define_console_stuff(JSContext *context,
325 : : JS::MutableHandleObject module)
326 : : {
327 : 0 : module.set(JS_NewPlainObject(context));
328 : 0 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
329 : 0 : return JS_DefineFunctionById(context, module, atoms.interact(),
330 : : gjs_console_interact, 1,
331 : 0 : GJS_MODULE_PROP_FLAGS);
332 : : }
|