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