1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
2 // use this file except in compliance with the License. You may obtain a copy of
3 // the License at
4 //
5 //   http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 // License for the specific language governing permissions and limitations under
11 // the License.
12 
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <string.h>
16 
17 #ifdef XP_WIN
18 #define NOMINMAX
19 #include <windows.h>
20 #else
21 #include <unistd.h>
22 #endif
23 
24 #include <jsapi.h>
25 #include <js/Initialization.h>
26 #include <js/Conversions.h>
27 #include <js/Wrapper.h>
28 
29 #include "config.h"
30 #include "util.h"
31 
32 static bool enableSharedMemory = true;
33 
34 static JSClassOps global_ops = {
35     nullptr,
36     nullptr,
37     nullptr,
38     nullptr,
39     nullptr,
40     nullptr,
41     nullptr,
42     nullptr,
43     nullptr,
44     nullptr,
45     JS_GlobalObjectTraceHook
46 };
47 
48 /* The class of the global object. */
49 static JSClass global_class = {
50     "global",
51     JSCLASS_GLOBAL_FLAGS,
52     &global_ops
53 };
54 
55 static void
SetStandardCompartmentOptions(JS::CompartmentOptions & options)56 SetStandardCompartmentOptions(JS::CompartmentOptions& options)
57 {
58     options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory);
59 }
60 
61 static JSObject*
NewSandbox(JSContext * cx,bool lazy)62 NewSandbox(JSContext* cx, bool lazy)
63 {
64     JS::CompartmentOptions options;
65     SetStandardCompartmentOptions(options);
66     JS::RootedObject obj(cx, JS_NewGlobalObject(cx, &global_class, nullptr,
67                                             JS::DontFireOnNewGlobalHook, options));
68     if (!obj)
69         return nullptr;
70 
71     {
72         JSAutoCompartment ac(cx, obj);
73         if (!lazy && !JS_InitStandardClasses(cx, obj))
74             return nullptr;
75 
76         JS::RootedValue value(cx, JS::BooleanValue(lazy));
77         if (!JS_DefineProperty(cx, obj, "lazy", value, JSPROP_PERMANENT | JSPROP_READONLY))
78             return nullptr;
79 
80         JS_FireOnNewGlobalObject(cx, obj);
81     }
82 
83     if (!JS_WrapObject(cx, &obj))
84         return nullptr;
85     return obj;
86 }
87 
88 static bool
evalcx(JSContext * cx,unsigned int argc,JS::Value * vp)89 evalcx(JSContext *cx, unsigned int argc, JS::Value* vp)
90 {
91     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
92     bool ret = false;
93 
94     JS::RootedString str(cx, JS::ToString(cx, args[0]));
95     if (!str)
96         return false;
97 
98     JS::RootedObject sandbox(cx);
99     if (args.hasDefined(1)) {
100         sandbox = JS::ToObject(cx, args[1]);
101         if (!sandbox)
102             return false;
103     }
104 
105     JSAutoRequest ar(cx);
106 
107     if (!sandbox) {
108         sandbox = NewSandbox(cx, false);
109         if (!sandbox)
110             return false;
111     }
112 
113     js::AutoStableStringChars strChars(cx);
114     if (!strChars.initTwoByte(cx, str))
115         return false;
116 
117     mozilla::Range<const char16_t> chars = strChars.twoByteRange();
118     size_t srclen = chars.length();
119     const char16_t* src = chars.begin().get();
120 
121     if(srclen == 0) {
122         args.rval().setObject(*sandbox);
123     } else {
124         mozilla::Maybe<JSAutoCompartment> ac;
125         unsigned flags;
126         JSObject* unwrapped = UncheckedUnwrap(sandbox, true, &flags);
127         if (flags & js::Wrapper::CROSS_COMPARTMENT) {
128             sandbox = unwrapped;
129             ac.emplace(cx, sandbox);
130         }
131 
132         JS::CompileOptions opts(cx);
133         JS::RootedValue rval(cx);
134         opts.setFileAndLine("<unknown>", 1);
135         if (!JS::Evaluate(cx, opts, src, srclen, args.rval())) {
136              return false;
137          }
138     }
139     ret = true;
140     if (!JS_WrapValue(cx, args.rval()))
141         return false;
142 
143     return ret;
144 }
145 
146 
147 static bool
gc(JSContext * cx,unsigned int argc,JS::Value * vp)148 gc(JSContext* cx, unsigned int argc, JS::Value* vp)
149 {
150     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
151     JS_GC(cx);
152     args.rval().setUndefined();
153     return true;
154 }
155 
156 
157 static bool
print(JSContext * cx,unsigned int argc,JS::Value * vp)158 print(JSContext* cx, unsigned int argc, JS::Value* vp)
159 {
160     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
161 
162     bool use_stderr = false;
163     if(argc > 1 && args[1].isTrue()) {
164         use_stderr = true;
165     }
166 
167     if(!args[0].isString()) {
168         JS_ReportErrorUTF8(cx, "Unable to print non-string value.");
169         return false;
170     }
171 
172     couch_print(cx, args[0], use_stderr);
173 
174     args.rval().setUndefined();
175     return true;
176 }
177 
178 
179 static bool
quit(JSContext * cx,unsigned int argc,JS::Value * vp)180 quit(JSContext* cx, unsigned int argc, JS::Value* vp)
181 {
182     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
183 
184     int exit_code = args[0].toInt32();;
185     exit(exit_code);
186 }
187 
188 
189 static bool
readline(JSContext * cx,unsigned int argc,JS::Value * vp)190 readline(JSContext* cx, unsigned int argc, JS::Value* vp)
191 {
192     JSString* line;
193     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
194 
195     /* GC Occasionally */
196     JS_MaybeGC(cx);
197 
198     line = couch_readline(cx, stdin);
199     if(line == NULL) return false;
200 
201     // return with JSString* instead of JSValue in the past
202     args.rval().setString(line);
203     return true;
204 }
205 
206 
207 static bool
seal(JSContext * cx,unsigned int argc,JS::Value * vp)208 seal(JSContext* cx, unsigned int argc, JS::Value* vp)
209 {
210     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
211     JS::RootedObject target(cx);
212     target = JS::ToObject(cx, args[0]);
213     if (!target) {
214         args.rval().setUndefined();
215         return true;
216     }
217     bool deep = false;
218     deep = args[1].toBoolean();
219     bool ret = deep ? JS_DeepFreezeObject(cx, target) : JS_FreezeObject(cx, target);
220     args.rval().setUndefined();
221     return ret;
222 }
223 
224 
225 static JSFunctionSpec global_functions[] = {
226     JS_FN("evalcx", evalcx, 0, 0),
227     JS_FN("gc", gc, 0, 0),
228     JS_FN("print", print, 0, 0),
229     JS_FN("quit", quit, 0, 0),
230     JS_FN("readline", readline, 0, 0),
231     JS_FN("seal", seal, 0, 0),
232     JS_FS_END
233 };
234 
235 
236 static bool
csp_allows(JSContext * cx)237 csp_allows(JSContext* cx)
238 {
239     couch_args* args = static_cast<couch_args*>(JS_GetContextPrivate(cx));
240     if(args->eval) {
241         return true;
242     } else {
243         return false;
244     }
245 }
246 
247 
248 static JSSecurityCallbacks security_callbacks = {
249     csp_allows,
250     nullptr
251 };
252 
253 
254 int
main(int argc,const char * argv[])255 main(int argc, const char* argv[])
256 {
257     JSContext* cx = NULL;
258     char* scriptsrc;
259     size_t slen;
260     int i;
261 
262     couch_args* args = couch_parse_args(argc, argv);
263 
264     JS_Init();
265     cx = JS_NewContext(args->stack_size, 8L * 1024L);
266     if(cx == NULL)
267         return 1;
268 
269     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, 0);
270     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, 0);
271 
272     if (!JS::InitSelfHostedCode(cx))
273         return 1;
274 
275     JS::SetWarningReporter(cx, couch_error);
276     JS::SetOutOfMemoryCallback(cx, couch_oom, NULL);
277     JS_SetContextPrivate(cx, args);
278     JS_SetSecurityCallbacks(cx, &security_callbacks);
279 
280     JSAutoRequest ar(cx);
281     JS::CompartmentOptions options;
282     JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr,
283                                                    JS::FireOnNewGlobalHook, options));
284     if (!global)
285         return 1;
286 
287     JSAutoCompartment ac(cx, global);
288 
289     if(!JS_InitStandardClasses(cx, global))
290         return 1;
291 
292     if(couch_load_funcs(cx, global, global_functions) != true)
293         return 1;
294 
295     for(i = 0 ; args->scripts[i] ; i++) {
296         slen = couch_readfile(args->scripts[i], &scriptsrc);
297 
298         // Compile and run
299         JS::CompileOptions options(cx);
300         options.setFileAndLine(args->scripts[i], 1);
301         options.setUTF8(true);
302         JS::RootedScript script(cx);
303 
304         if(!JS_CompileScript(cx, scriptsrc, slen, options, &script)) {
305             JS::RootedValue exc(cx);
306             if(!JS_GetPendingException(cx, &exc)) {
307                 fprintf(stderr, "Failed to compile script.\n");
308             } else {
309                 JS::RootedObject exc_obj(cx, &exc.toObject());
310                 JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
311                 couch_error(cx, report);
312             }
313             return 1;
314         }
315 
316         free(scriptsrc);
317 
318         JS::RootedValue result(cx);
319         if(JS_ExecuteScript(cx, script, &result) != true) {
320             JS::RootedValue exc(cx);
321             if(!JS_GetPendingException(cx, &exc)) {
322                 fprintf(stderr, "Failed to execute script.\n");
323             } else {
324                 JS::RootedObject exc_obj(cx, &exc.toObject());
325                 JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
326                 couch_error(cx, report);
327             }
328             return 1;
329         }
330 
331         // Give the GC a chance to run.
332         JS_MaybeGC(cx);
333     }
334 
335     return 0;
336 }
337