1 // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
2 // SPDX-FileCopyrightText: 2018 Philip Chimento <philip.chimento@gmail.com>
3 // SPDX-FileContributor: Philip Chimento <philip.chimento@gmail.com>
4 
5 #include <config.h>  // for HAVE_READLINE_READLINE_H, HAVE_UNISTD_H
6 
7 #include <stdint.h>
8 #include <stdio.h>  // for feof, fflush, fgets, stdin, stdout
9 
10 #ifdef HAVE_READLINE_READLINE_H
11 #    include <readline/history.h>
12 #    include <readline/readline.h>
13 #endif
14 
15 #include <glib.h>
16 
17 #include <js/CallArgs.h>
18 #include <js/PropertySpec.h>
19 #include <js/RootingAPI.h>
20 #include <js/TypeDecls.h>
21 #include <js/Utility.h>  // for UniqueChars
22 #include <js/Value.h>
23 #include <jsapi.h>  // for JS_DefineFunctions, JS_NewStringCopyZ
24 
25 #include "gjs/atoms.h"
26 #include "gjs/context-private.h"
27 #include "gjs/context.h"
28 #include "gjs/global.h"
29 #include "gjs/jsapi-util-args.h"
30 #include "gjs/jsapi-util.h"
31 #include "gjs/macros.h"
32 
33 #include "util/console.h"
34 
35 GJS_JSAPI_RETURN_CONVENTION
quit(JSContext * cx,unsigned argc,JS::Value * vp)36 static bool quit(JSContext* cx, unsigned argc, JS::Value* vp) {
37     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
38 
39     int32_t exitcode;
40     if (!gjs_parse_call_args(cx, "quit", args, "i", "exitcode", &exitcode))
41         return false;
42 
43     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
44     gjs->exit(exitcode);
45     return false;  // without gjs_throw() == "throw uncatchable exception"
46 }
47 
48 GJS_JSAPI_RETURN_CONVENTION
do_readline(JSContext * cx,unsigned argc,JS::Value * vp)49 static bool do_readline(JSContext* cx, unsigned argc, JS::Value* vp) {
50     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
51 
52     JS::UniqueChars prompt;
53     if (!gjs_parse_call_args(cx, "readline", args, "|s", "prompt", &prompt))
54         return false;
55 
56     GjsAutoChar line;
57     do {
58         const char* real_prompt = prompt ? prompt.get() : "db> ";
59 #ifdef HAVE_READLINE_READLINE_H
60         if (gjs_console_is_tty(stdin_fd)) {
61             line = readline(real_prompt);
62         } else {
63 #else
64         {
65 #endif  // HAVE_READLINE_READLINE_H
66             char buf[256];
67             g_print("%s", real_prompt);
68             fflush(stdout);
69             if (!fgets(buf, sizeof buf, stdin))
70                 buf[0] = '\0';
71             line.reset(g_strdup(g_strchomp(buf)));
72 
73             if (!gjs_console_is_tty(stdin_fd)) {
74                 if (feof(stdin)) {
75                     g_print("[quit due to end of input]\n");
76                     line.reset(g_strdup("quit"));
77                 } else {
78                     g_print("%s\n", line.get());
79                 }
80             }
81         }
82 
83         /* EOF, return null */
84         if (!line) {
85             args.rval().setUndefined();
86             return true;
87         }
88     } while (line && line.get()[0] == '\0');
89 
90     /* Add line to history and convert it to a JSString so that we can pass it
91      * back as the return value */
92 #ifdef HAVE_READLINE_READLINE_H
93     add_history(line);
94 #endif
95     args.rval().setString(JS_NewStringCopyZ(cx, line));
96     return true;
97 }
98 
99 // clang-format off
100 static JSFunctionSpec debugger_funcs[] = {
101     JS_FN("quit", quit, 1, GJS_MODULE_PROP_FLAGS),
102     JS_FN("readline", do_readline, 1, GJS_MODULE_PROP_FLAGS),
103     JS_FS_END
104 };
105 // clang-format on
106 
107 void gjs_context_setup_debugger_console(GjsContext* gjs) {
108     auto cx = static_cast<JSContext*>(gjs_context_get_native_context(gjs));
109 
110     JS::RootedObject debuggee(cx, gjs_get_import_global(cx));
111     JS::RootedObject debugger_global(
112         cx, gjs_create_global_object(cx, GjsGlobalType::DEBUGGER));
113 
114     // Enter realm of the debugger and initialize it with the debuggee
115     JSAutoRealm ar(cx, debugger_global);
116     JS::RootedObject debuggee_wrapper(cx, debuggee);
117     if (!JS_WrapObject(cx, &debuggee_wrapper)) {
118         gjs_log_exception(cx);
119         return;
120     }
121 
122     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
123     JS::RootedValue v_wrapper(cx, JS::ObjectValue(*debuggee_wrapper));
124     if (!JS_SetPropertyById(cx, debugger_global, atoms.debuggee(), v_wrapper) ||
125         !JS_DefineFunctions(cx, debugger_global, debugger_funcs) ||
126         !gjs_define_global_properties(cx, debugger_global,
127                                       GjsGlobalType::DEBUGGER, "GJS debugger",
128                                       "debugger"))
129         gjs_log_exception(cx);
130 }
131