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/CompilationAndEvaluation.h>
26 #include <js/Conversions.h>
27 #include <js/Initialization.h>
28 #include <js/SourceText.h>
29 #include <js/Warnings.h>
30 #include <js/Wrapper.h>
31 
32 #include "config.h"
33 #include "util.h"
34 
35 static bool enableSharedMemory = true;
36 
37 static JSClassOps global_ops = {
38     nullptr,
39     nullptr,
40     nullptr,
41     nullptr,
42     nullptr,
43     nullptr,
44     nullptr,
45     nullptr,
46     nullptr,
47     nullptr,
48     JS_GlobalObjectTraceHook
49 };
50 
51 /* The class of the global object. */
52 static JSClass global_class = {
53     "global",
54     JSCLASS_GLOBAL_FLAGS,
55     &global_ops
56 };
57 
58 static JSObject*
NewSandbox(JSContext * cx,bool lazy)59 NewSandbox(JSContext* cx, bool lazy)
60 {
61     JS::RealmOptions options;
62     options.creationOptions().setSharedMemoryAndAtomicsEnabled(enableSharedMemory);
63     options.creationOptions().setNewCompartmentAndZone();
64     JS::RootedObject obj(cx, JS_NewGlobalObject(cx, &global_class, nullptr,
65                                             JS::DontFireOnNewGlobalHook, options));
66     if (!obj)
67         return nullptr;
68 
69     {
70         JSAutoRealm ac(cx, obj);
71         if (!lazy && !JS::InitRealmStandardClasses(cx))
72             return nullptr;
73 
74         JS::RootedValue value(cx, JS::BooleanValue(lazy));
75         if (!JS_DefineProperty(cx, obj, "lazy", value, JSPROP_PERMANENT | JSPROP_READONLY))
76             return nullptr;
77 
78         JS_FireOnNewGlobalObject(cx, obj);
79     }
80 
81     if (!JS_WrapObject(cx, &obj))
82         return nullptr;
83     return obj;
84 }
85 
86 static bool
evalcx(JSContext * cx,unsigned int argc,JS::Value * vp)87 evalcx(JSContext *cx, unsigned int argc, JS::Value* vp)
88 {
89     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
90     bool ret = false;
91 
92     JS::RootedString str(cx, args[0].toString());
93     if (!str)
94         return false;
95 
96     JS::RootedObject sandbox(cx);
97     if (args.hasDefined(1)) {
98         sandbox = JS::ToObject(cx, args[1]);
99         if (!sandbox)
100             return false;
101     }
102 
103     if (!sandbox) {
104         sandbox = NewSandbox(cx, false);
105         if (!sandbox)
106             return false;
107     }
108 
109     JS::AutoStableStringChars strChars(cx);
110     if (!strChars.initTwoByte(cx, str))
111         return false;
112 
113     mozilla::Range<const char16_t> chars = strChars.twoByteRange();
114     JS::SourceText<char16_t> srcBuf;
115     if (!srcBuf.init(cx, chars.begin().get(), chars.length(),
116                      JS::SourceOwnership::Borrowed)) {
117         return false;
118     }
119 
120     if(srcBuf.length() == 0) {
121         args.rval().setObject(*sandbox);
122     } else {
123         mozilla::Maybe<JSAutoRealm> ar;
124         unsigned flags;
125         JSObject* unwrapped = UncheckedUnwrap(sandbox, true, &flags);
126         if (flags & js::Wrapper::CROSS_COMPARTMENT) {
127             sandbox = unwrapped;
128             ar.emplace(cx, sandbox);
129         }
130 
131         JS::CompileOptions opts(cx);
132         JS::RootedValue rval(cx);
133         opts.setFileAndLine("<unknown>", 1);
134 
135         if (!JS::Evaluate(cx, opts, srcBuf, 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,JS::HandleValue code)237 csp_allows(JSContext* cx, JS::HandleValue code)
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     int i;
259 
260     couch_args* args = couch_parse_args(argc, argv);
261 
262     JS_Init();
263     cx = JS_NewContext(args->stack_size, 8L * 1024L);
264     if(cx == NULL)
265         return 1;
266 
267     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE, 0);
268     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, 0);
269 
270     if (!JS::InitSelfHostedCode(cx))
271         return 1;
272 
273     JS::SetWarningReporter(cx, couch_error);
274     JS::SetOutOfMemoryCallback(cx, couch_oom, NULL);
275     JS_SetContextPrivate(cx, args);
276     JS_SetSecurityCallbacks(cx, &security_callbacks);
277 
278     JS::RealmOptions options;
279     JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr,
280                                                    JS::FireOnNewGlobalHook, options));
281     if (!global)
282         return 1;
283 
284     JSAutoRealm ar(cx, global);
285 
286     if(!JS::InitRealmStandardClasses(cx))
287         return 1;
288 
289     if(couch_load_funcs(cx, global, global_functions) != true)
290         return 1;
291 
292     for(i = 0 ; args->scripts[i] ; i++) {
293         const char* filename = args->scripts[i];
294 
295         // Compile and run
296         JS::CompileOptions options(cx);
297         options.setFileAndLine(filename, 1);
298         JS::RootedScript script(cx);
299         FILE* fp;
300 
301         fp = fopen(args->scripts[i], "r");
302         if(fp == NULL) {
303             fprintf(stderr, "Failed to read file: %s\n", filename);
304             return 3;
305         }
306         script = JS::CompileUtf8File(cx, options, fp);
307         fclose(fp);
308         if (!script) {
309             JS::RootedValue exc(cx);
310             if(!JS_GetPendingException(cx, &exc)) {
311                 fprintf(stderr, "Failed to compile file: %s\n", filename);
312             } else {
313                 JS::RootedObject exc_obj(cx, &exc.toObject());
314                 JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
315                 couch_error(cx, report);
316             }
317             return 1;
318         }
319 
320         JS::RootedValue result(cx);
321         if(JS_ExecuteScript(cx, script, &result) != true) {
322             JS::RootedValue exc(cx);
323             if(!JS_GetPendingException(cx, &exc)) {
324                 fprintf(stderr, "Failed to execute script.\n");
325             } else {
326                 JS::RootedObject exc_obj(cx, &exc.toObject());
327                 JSErrorReport* report = JS_ErrorFromException(cx, exc_obj);
328                 couch_error(cx, report);
329             }
330         }
331 
332         // Give the GC a chance to run.
333         JS_MaybeGC(cx);
334     }
335 
336     return 0;
337 }
338