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