1 /*
2  * Copyright (c) 2018 Philip Chimento  <philip.chimento@gmail.com>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to
6  * deal in the Software without restriction, including without limitation the
7  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8  * sell copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20  * IN THE SOFTWARE.
21  *
22  * Authored By: Philip Chimento <philip.chimento@gmail.com>
23  */
24 
25 #include <config.h>  // for HAVE_READLINE_READLINE_H, HAVE_UNISTD_H
26 
27 #include <stdint.h>
28 #include <stdio.h>  // for feof, fflush, fgets, stdin, stdout
29 
30 #ifdef HAVE_READLINE_READLINE_H
31 #    include <readline/history.h>
32 #    include <readline/readline.h>
33 #endif
34 
35 #ifdef HAVE_UNISTD_H
36 #    include <unistd.h>  // for isatty, STDIN_FILENO
37 #elif defined(_WIN32)
38 #    include <io.h>
39 #    ifndef STDIN_FILENO
40 #        define STDIN_FILENO 0
41 #    endif
42 #endif
43 
44 #include <glib.h>
45 
46 #include <js/CallArgs.h>
47 #include <js/PropertySpec.h>
48 #include <js/RootingAPI.h>
49 #include <js/TypeDecls.h>
50 #include <js/Utility.h>  // for UniqueChars
51 #include <js/Value.h>
52 #include <jsapi.h>  // for JS_DefineFunctions, JS_NewStringCopyZ
53 
54 #include "cjs/atoms.h"
55 #include "cjs/context-private.h"
56 #include "cjs/context.h"
57 #include "cjs/global.h"
58 #include "cjs/jsapi-util-args.h"
59 #include "cjs/jsapi-util.h"
60 #include "cjs/macros.h"
61 
62 GJS_JSAPI_RETURN_CONVENTION
quit(JSContext * cx,unsigned argc,JS::Value * vp)63 static bool quit(JSContext* cx, unsigned argc, JS::Value* vp) {
64     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
65 
66     int32_t exitcode;
67     if (!gjs_parse_call_args(cx, "quit", args, "i", "exitcode", &exitcode))
68         return false;
69 
70     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
71     gjs->exit(exitcode);
72     return false;  // without gjs_throw() == "throw uncatchable exception"
73 }
74 
75 GJS_JSAPI_RETURN_CONVENTION
do_readline(JSContext * cx,unsigned argc,JS::Value * vp)76 static bool do_readline(JSContext* cx, unsigned argc, JS::Value* vp) {
77     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
78 
79     JS::UniqueChars prompt;
80     if (!gjs_parse_call_args(cx, "readline", args, "|s", "prompt", &prompt))
81         return false;
82 
83     GjsAutoChar line;
84     do {
85         const char* real_prompt = prompt ? prompt.get() : "db> ";
86 #ifdef HAVE_READLINE_READLINE_H
87         if (isatty(STDIN_FILENO)) {
88             line = readline(real_prompt);
89         } else {
90 #else
91         {
92 #endif  // HAVE_READLINE_READLINE_H
93             char buf[256];
94             g_print("%s", real_prompt);
95             fflush(stdout);
96             if (!fgets(buf, sizeof buf, stdin))
97                 buf[0] = '\0';
98             line.reset(g_strchomp(g_strdup(buf)));
99 
100             if (!isatty(STDIN_FILENO)) {
101                 if (feof(stdin)) {
102                     g_print("[quit due to end of input]\n");
103                     line.reset(g_strdup("quit"));
104                 } else {
105                     g_print("%s\n", line.get());
106                 }
107             }
108         }
109 
110         /* EOF, return null */
111         if (!line) {
112             args.rval().setUndefined();
113             return true;
114         }
115     } while (line && line.get()[0] == '\0');
116 
117     /* Add line to history and convert it to a JSString so that we can pass it
118      * back as the return value */
119 #ifdef HAVE_READLINE_READLINE_H
120     add_history(line);
121 #endif
122     args.rval().setString(JS_NewStringCopyZ(cx, line));
123     return true;
124 }
125 
126 // clang-format off
127 static JSFunctionSpec debugger_funcs[] = {
128     JS_FN("quit", quit, 1, GJS_MODULE_PROP_FLAGS),
129     JS_FN("readline", do_readline, 1, GJS_MODULE_PROP_FLAGS),
130     JS_FS_END
131 };
132 // clang-format on
133 
134 void gjs_context_setup_debugger_console(GjsContext* gjs) {
135     auto cx = static_cast<JSContext*>(gjs_context_get_native_context(gjs));
136 
137     JS::RootedObject debuggee(cx, gjs_get_import_global(cx));
138     JS::RootedObject debugger_global(
139         cx, gjs_create_global_object(cx, GjsGlobalType::DEBUGGER));
140 
141     // Enter realm of the debugger and initialize it with the debuggee
142     JSAutoRealm ar(cx, debugger_global);
143     JS::RootedObject debuggee_wrapper(cx, debuggee);
144     if (!JS_WrapObject(cx, &debuggee_wrapper)) {
145         gjs_log_exception(cx);
146         return;
147     }
148 
149     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
150     JS::RootedValue v_wrapper(cx, JS::ObjectValue(*debuggee_wrapper));
151     if (!JS_SetPropertyById(cx, debugger_global, atoms.debuggee(), v_wrapper) ||
152         !JS_DefineFunctions(cx, debugger_global, debugger_funcs) ||
153         !gjs_define_global_properties(cx, debugger_global,
154                                       GjsGlobalType::DEBUGGER, "GJS debugger",
155                                       "debugger"))
156         gjs_log_exception(cx);
157 }
158