Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "builtin/Eval.h"
8
9
#include "mozilla/HashFunctions.h"
10
#include "mozilla/Range.h"
11
12
#include "ds/LifoAlloc.h"
13
#include "frontend/BytecodeCompilation.h"
14
#include "frontend/ParseInfo.h"
15
#include "gc/HashUtil.h"
16
#include "js/SourceText.h"
17
#include "js/StableStringChars.h"
18
#include "vm/GlobalObject.h"
19
#include "vm/JSContext.h"
20
#include "vm/JSONParser.h"
21
22
#include "debugger/DebugAPI-inl.h"
23
#include "vm/Interpreter-inl.h"
24
25
using namespace js;
26
27
using mozilla::AddToHash;
28
using mozilla::HashString;
29
using mozilla::RangedPtr;
30
31
using JS::AutoCheckCannotGC;
32
using JS::AutoStableStringChars;
33
using JS::CompileOptions;
34
using JS::SourceOwnership;
35
using JS::SourceText;
36
37
// We should be able to assert this for *any* fp->environmentChain().
38
static void AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env) {
39
#ifdef DEBUG
40
RootedObject obj(cx);
41
for (obj = &env; obj; obj = obj->enclosingEnvironment()) {
42
MOZ_ASSERT(!IsWindowProxy(obj));
43
}
44
#endif
45
}
46
47
static bool IsEvalCacheCandidate(JSScript* script) {
48
if (!script->isDirectEvalInFunction()) {
49
return false;
50
}
51
52
// Make sure there are no inner objects (which may be used directly by script
53
// and clobbered) or inner functions (which may have wrong scope).
54
for (JS::GCCellPtr gcThing : script->gcthings()) {
55
if (gcThing.is<JSObject>()) {
56
return false;
57
}
58
}
59
60
return true;
61
}
62
63
/* static */
64
HashNumber EvalCacheHashPolicy::hash(const EvalCacheLookup& l) {
65
HashNumber hash = HashStringChars(l.str);
66
return AddToHash(hash, l.callerScript.get(), l.pc);
67
}
68
69
/* static */
70
bool EvalCacheHashPolicy::match(const EvalCacheEntry& cacheEntry,
71
const EvalCacheLookup& l) {
72
MOZ_ASSERT(IsEvalCacheCandidate(cacheEntry.script));
73
74
return EqualStrings(cacheEntry.str, l.str) &&
75
cacheEntry.callerScript == l.callerScript && cacheEntry.pc == l.pc;
76
}
77
78
// Add the script to the eval cache when EvalKernel is finished
79
class EvalScriptGuard {
80
JSContext* cx_;
81
Rooted<JSScript*> script_;
82
83
/* These fields are only valid if lookup_.str is non-nullptr. */
84
EvalCacheLookup lookup_;
85
mozilla::Maybe<DependentAddPtr<EvalCache>> p_;
86
87
RootedLinearString lookupStr_;
88
89
public:
90
explicit EvalScriptGuard(JSContext* cx)
91
: cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
92
93
~EvalScriptGuard() {
94
if (script_ && !cx_->isExceptionPending()) {
95
script_->cacheForEval();
96
EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup_.callerScript,
97
lookup_.pc};
98
lookup_.str = lookupStr_;
99
if (lookup_.str && IsEvalCacheCandidate(script_)) {
100
// Ignore failure to add cache entry.
101
if (!p_->add(cx_, cx_->caches().evalCache, lookup_, cacheEntry)) {
102
cx_->recoverFromOutOfMemory();
103
}
104
}
105
}
106
}
107
108
void lookupInEvalCache(JSLinearString* str, JSScript* callerScript,
109
jsbytecode* pc) {
110
lookupStr_ = str;
111
lookup_.str = str;
112
lookup_.callerScript = callerScript;
113
lookup_.pc = pc;
114
p_.emplace(cx_, cx_->caches().evalCache, lookup_);
115
if (*p_) {
116
script_ = (*p_)->script;
117
p_->remove(cx_, cx_->caches().evalCache, lookup_);
118
}
119
}
120
121
void setNewScript(JSScript* script) {
122
// JSScript::initFromEmitter has already called js_CallNewScriptHook.
123
MOZ_ASSERT(!script_ && script);
124
script_ = script;
125
}
126
127
bool foundScript() { return !!script_; }
128
129
HandleScript script() {
130
MOZ_ASSERT(script_);
131
return script_;
132
}
133
};
134
135
enum EvalJSONResult { EvalJSON_Failure, EvalJSON_Success, EvalJSON_NotJSON };
136
137
template <typename CharT>
138
static bool EvalStringMightBeJSON(const mozilla::Range<const CharT> chars) {
139
// If the eval string starts with '(' or '[' and ends with ')' or ']', it
140
// may be JSON. Try the JSON parser first because it's much faster. If
141
// the eval string isn't JSON, JSON parsing will probably fail quickly, so
142
// little time will be lost.
143
size_t length = chars.length();
144
if (length < 2) {
145
return false;
146
}
147
148
// It used to be that strings in JavaScript forbid U+2028 LINE SEPARATOR
149
// and U+2029 PARAGRAPH SEPARATOR, so something like
150
//
151
// eval("['" + "\u2028" + "']");
152
//
153
// i.e. an array containing a string with a line separator in it, *would*
154
// be JSON but *would not* be valid JavaScript. Handing such a string to
155
// the JSON parser would then fail to recognize a syntax error. As of
156
// <https://tc39.github.io/proposal-json-superset/> JavaScript strings may
157
// contain these two code points, so it's safe to JSON-parse eval strings
158
// that contain them.
159
160
CharT first = chars[0], last = chars[length - 1];
161
return (first == '[' && last == ']') || (first == '(' && last == ')');
162
}
163
164
template <typename CharT>
165
static EvalJSONResult ParseEvalStringAsJSON(
166
JSContext* cx, const mozilla::Range<const CharT> chars,
167
MutableHandleValue rval) {
168
size_t len = chars.length();
169
MOZ_ASSERT((chars[0] == '(' && chars[len - 1] == ')') ||
170
(chars[0] == '[' && chars[len - 1] == ']'));
171
172
auto jsonChars = (chars[0] == '[') ? chars
173
: mozilla::Range<const CharT>(
174
chars.begin().get() + 1U, len - 2);
175
176
Rooted<JSONParser<CharT>> parser(
177
cx, JSONParser<CharT>(cx, jsonChars,
178
JSONParserBase::ParseType::AttemptForEval));
179
if (!parser.parse(rval)) {
180
return EvalJSON_Failure;
181
}
182
183
return rval.isUndefined() ? EvalJSON_NotJSON : EvalJSON_Success;
184
}
185
186
static EvalJSONResult TryEvalJSON(JSContext* cx, JSLinearString* str,
187
MutableHandleValue rval) {
188
if (str->hasLatin1Chars()) {
189
AutoCheckCannotGC nogc;
190
if (!EvalStringMightBeJSON(str->latin1Range(nogc))) {
191
return EvalJSON_NotJSON;
192
}
193
} else {
194
AutoCheckCannotGC nogc;
195
if (!EvalStringMightBeJSON(str->twoByteRange(nogc))) {
196
return EvalJSON_NotJSON;
197
}
198
}
199
200
AutoStableStringChars linearChars(cx);
201
if (!linearChars.init(cx, str)) {
202
return EvalJSON_Failure;
203
}
204
205
return linearChars.isLatin1()
206
? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
207
: ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
208
}
209
210
enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
211
212
// Common code implementing direct and indirect eval.
213
//
214
// Evaluate call.argv[2], if it is a string, in the context of the given calling
215
// frame, with the provided scope chain, with the semantics of either a direct
216
// or indirect eval (see ES5 10.4.2). If this is an indirect eval, env
217
// must be a global object.
218
//
219
// On success, store the completion value in call.rval and return true.
220
static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
221
AbstractFramePtr caller, HandleObject env,
222
jsbytecode* pc, MutableHandleValue vp) {
223
MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
224
MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
225
MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
226
AssertInnerizedEnvironmentChain(cx, *env);
227
228
if (!GlobalObject::isRuntimeCodeGenEnabled(cx, v, cx->global())) {
229
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
230
JSMSG_CSP_BLOCKED_EVAL);
231
return false;
232
}
233
234
// ES5 15.1.2.1 step 1.
235
if (!v.isString()) {
236
vp.set(v);
237
return true;
238
}
239
RootedString str(cx, v.toString());
240
241
// ES5 15.1.2.1 steps 2-8.
242
243
// Per ES5, indirect eval runs in the global scope. (eval is specified this
244
// way so that the compiler can make assumptions about what bindings may or
245
// may not exist in the current frame if it doesn't see 'eval'.)
246
MOZ_ASSERT_IF(evalType != DIRECT_EVAL,
247
cx->global() == &env->as<LexicalEnvironmentObject>().global());
248
249
RootedLinearString linearStr(cx, str->ensureLinear(cx));
250
if (!linearStr) {
251
return false;
252
}
253
254
RootedScript callerScript(cx, caller ? caller.script() : nullptr);
255
EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
256
if (ejr != EvalJSON_NotJSON) {
257
return ejr == EvalJSON_Success;
258
}
259
260
EvalScriptGuard esg(cx);
261
262
if (evalType == DIRECT_EVAL && caller.isFunctionFrame()) {
263
esg.lookupInEvalCache(linearStr, callerScript, pc);
264
}
265
266
if (!esg.foundScript()) {
267
RootedScript maybeScript(cx);
268
unsigned lineno;
269
const char* filename;
270
bool mutedErrors;
271
uint32_t pcOffset;
272
if (evalType == DIRECT_EVAL) {
273
DescribeScriptedCallerForDirectEval(cx, callerScript, pc, &filename,
274
&lineno, &pcOffset, &mutedErrors);
275
maybeScript = callerScript;
276
} else {
277
DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno,
278
&pcOffset, &mutedErrors);
279
}
280
281
const char* introducerFilename = filename;
282
if (maybeScript && maybeScript->scriptSource()->introducerFilename()) {
283
introducerFilename = maybeScript->scriptSource()->introducerFilename();
284
}
285
286
RootedScope enclosing(cx);
287
if (evalType == DIRECT_EVAL) {
288
enclosing = callerScript->innermostScope(pc);
289
} else {
290
enclosing = &cx->global()->emptyGlobalScope();
291
}
292
293
CompileOptions options(cx);
294
options.setIsRunOnce(true)
295
.setNoScriptRval(false)
296
.setMutedErrors(mutedErrors)
297
.setScriptOrModule(maybeScript);
298
299
if (evalType == DIRECT_EVAL && IsStrictEvalPC(pc)) {
300
options.setForceStrictMode();
301
}
302
303
if (introducerFilename) {
304
options.setFileAndLine(filename, 1);
305
options.setIntroductionInfo(introducerFilename, "eval", lineno,
306
maybeScript, pcOffset);
307
} else {
308
options.setFileAndLine("eval", 1);
309
options.setIntroductionType("eval");
310
}
311
312
AutoStableStringChars linearChars(cx);
313
if (!linearChars.initTwoByte(cx, linearStr)) {
314
return false;
315
}
316
317
SourceText<char16_t> srcBuf;
318
319
const char16_t* chars = linearChars.twoByteRange().begin().get();
320
SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller()
321
? SourceOwnership::TakeOwnership
322
: SourceOwnership::Borrowed;
323
if (!srcBuf.init(cx, chars, linearStr->length(), ownership)) {
324
return false;
325
}
326
327
LifoAllocScope allocScope(&cx->tempLifoAlloc());
328
frontend::ParseInfo parseInfo(cx, allocScope);
329
330
frontend::EvalScriptInfo info(cx, parseInfo, options, env, enclosing);
331
RootedScript compiled(cx, frontend::CompileEvalScript(info, srcBuf));
332
if (!compiled) {
333
return false;
334
}
335
336
esg.setNewScript(compiled);
337
}
338
339
// Look up the newTarget from the frame iterator.
340
Value newTargetVal = NullValue();
341
return ExecuteKernel(cx, esg.script(), *env, newTargetVal,
342
NullFramePtr() /* evalInFrame */, vp.address());
343
}
344
345
bool js::DirectEvalStringFromIon(JSContext* cx, HandleObject env,
346
HandleScript callerScript,
347
HandleValue newTargetValue, HandleString str,
348
jsbytecode* pc, MutableHandleValue vp) {
349
AssertInnerizedEnvironmentChain(cx, *env);
350
351
RootedValue v(cx, StringValue(str));
352
if (!GlobalObject::isRuntimeCodeGenEnabled(cx, v, cx->global())) {
353
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
354
JSMSG_CSP_BLOCKED_EVAL);
355
return false;
356
}
357
358
// ES5 15.1.2.1 steps 2-8.
359
360
RootedLinearString linearStr(cx, str->ensureLinear(cx));
361
if (!linearStr) {
362
return false;
363
}
364
365
EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
366
if (ejr != EvalJSON_NotJSON) {
367
return ejr == EvalJSON_Success;
368
}
369
370
EvalScriptGuard esg(cx);
371
372
esg.lookupInEvalCache(linearStr, callerScript, pc);
373
374
if (!esg.foundScript()) {
375
const char* filename;
376
unsigned lineno;
377
bool mutedErrors;
378
uint32_t pcOffset;
379
DescribeScriptedCallerForDirectEval(cx, callerScript, pc, &filename,
380
&lineno, &pcOffset, &mutedErrors);
381
382
const char* introducerFilename = filename;
383
if (callerScript->scriptSource()->introducerFilename()) {
384
introducerFilename = callerScript->scriptSource()->introducerFilename();
385
}
386
387
RootedScope enclosing(cx, callerScript->innermostScope(pc));
388
389
CompileOptions options(cx);
390
options.setIsRunOnce(true);
391
options.setNoScriptRval(false);
392
options.setMutedErrors(mutedErrors);
393
394
if (IsStrictEvalPC(pc)) {
395
options.setForceStrictMode();
396
}
397
398
if (introducerFilename) {
399
options.setFileAndLine(filename, 1);
400
options.setIntroductionInfo(introducerFilename, "eval", lineno,
401
callerScript, pcOffset);
402
} else {
403
options.setFileAndLine("eval", 1);
404
options.setIntroductionType("eval");
405
}
406
407
AutoStableStringChars linearChars(cx);
408
if (!linearChars.initTwoByte(cx, linearStr)) {
409
return false;
410
}
411
412
SourceText<char16_t> srcBuf;
413
414
const char16_t* chars = linearChars.twoByteRange().begin().get();
415
SourceOwnership ownership = linearChars.maybeGiveOwnershipToCaller()
416
? SourceOwnership::TakeOwnership
417
: SourceOwnership::Borrowed;
418
if (!srcBuf.init(cx, chars, linearStr->length(), ownership)) {
419
return false;
420
}
421
422
LifoAllocScope allocScope(&cx->tempLifoAlloc());
423
frontend::ParseInfo parseInfo(cx, allocScope);
424
425
frontend::EvalScriptInfo info(cx, parseInfo, options, env, enclosing);
426
JSScript* compiled = frontend::CompileEvalScript(info, srcBuf);
427
if (!compiled) {
428
return false;
429
}
430
431
esg.setNewScript(compiled);
432
}
433
434
return ExecuteKernel(cx, esg.script(), *env, newTargetValue,
435
NullFramePtr() /* evalInFrame */, vp.address());
436
}
437
438
bool js::IndirectEval(JSContext* cx, unsigned argc, Value* vp) {
439
CallArgs args = CallArgsFromVp(argc, vp);
440
441
RootedObject globalLexical(cx, &cx->global()->lexicalEnvironment());
442
443
// Note we'll just pass |undefined| here, then return it directly (or throw
444
// if runtime codegen is disabled), if no argument is provided.
445
return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(),
446
globalLexical, nullptr, args.rval());
447
}
448
449
bool js::DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp) {
450
// Direct eval can assume it was called from an interpreted or baseline frame.
451
ScriptFrameIter iter(cx);
452
AbstractFramePtr caller = iter.abstractFramePtr();
453
454
MOZ_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
455
JSOp(*iter.pc()) == JSOP_STRICTEVAL ||
456
JSOp(*iter.pc()) == JSOP_SPREADEVAL ||
457
JSOp(*iter.pc()) == JSOP_STRICTSPREADEVAL);
458
MOZ_ASSERT(caller.realm() == caller.script()->realm());
459
460
RootedObject envChain(cx, caller.environmentChain());
461
return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp);
462
}
463
464
bool js::IsAnyBuiltinEval(JSFunction* fun) {
465
return fun->maybeNative() == IndirectEval;
466
}
467
468
static bool ExecuteInExtensibleLexicalEnvironment(JSContext* cx,
469
HandleScript scriptArg,
470
HandleObject env) {
471
CHECK_THREAD(cx);
472
cx->check(env);
473
MOZ_ASSERT(IsExtensibleLexicalEnvironment(env));
474
MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
475
476
RootedScript script(cx, scriptArg);
477
if (script->realm() != cx->realm()) {
478
script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script);
479
if (!script) {
480
return false;
481
}
482
483
DebugAPI::onNewScript(cx, script);
484
}
485
486
RootedValue rval(cx);
487
return ExecuteKernel(cx, script, *env, UndefinedValue(),
488
NullFramePtr() /* evalInFrame */, rval.address());
489
}
490
491
JS_FRIEND_API bool js::ExecuteInFrameScriptEnvironment(
492
JSContext* cx, HandleObject objArg, HandleScript scriptArg,
493
MutableHandleObject envArg) {
494
RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
495
if (!varEnv) {
496
return false;
497
}
498
499
RootedObjectVector envChain(cx);
500
if (!envChain.append(objArg)) {
501
return false;
502
}
503
504
RootedObject env(cx);
505
if (!js::CreateObjectsForEnvironmentChain(cx, envChain, varEnv, &env)) {
506
return false;
507
}
508
509
// Create lexical environment with |this| == objArg, which should be a Gecko
510
// MessageManager.
511
// NOTE: This is required behavior for Gecko FrameScriptLoader, where some
512
// callers try to bind methods from the message manager in their scope chain
513
// to |this|, and will fail if it is not bound to a message manager.
514
ObjectRealm& realm = ObjectRealm::get(varEnv);
515
env =
516
realm.getOrCreateNonSyntacticLexicalEnvironment(cx, env, varEnv, objArg);
517
if (!env) {
518
return false;
519
}
520
521
if (!ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env)) {
522
return false;
523
}
524
525
envArg.set(env);
526
return true;
527
}
528
529
JS_FRIEND_API JSObject* js::NewJSMEnvironment(JSContext* cx) {
530
RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
531
if (!varEnv) {
532
return nullptr;
533
}
534
535
// Force LexicalEnvironmentObject to be created.
536
ObjectRealm& realm = ObjectRealm::get(varEnv);
537
MOZ_ASSERT(!realm.getNonSyntacticLexicalEnvironment(varEnv));
538
if (!realm.getOrCreateNonSyntacticLexicalEnvironment(cx, varEnv)) {
539
return nullptr;
540
}
541
542
return varEnv;
543
}
544
545
JS_FRIEND_API bool js::ExecuteInJSMEnvironment(JSContext* cx,
546
HandleScript scriptArg,
547
HandleObject varEnv) {
548
RootedObjectVector emptyChain(cx);
549
return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain);
550
}
551
552
JS_FRIEND_API bool js::ExecuteInJSMEnvironment(JSContext* cx,
553
HandleScript scriptArg,
554
HandleObject varEnv,
555
HandleObjectVector targetObj) {
556
cx->check(varEnv);
557
MOZ_ASSERT(
558
ObjectRealm::get(varEnv).getNonSyntacticLexicalEnvironment(varEnv));
559
MOZ_DIAGNOSTIC_ASSERT(scriptArg->noScriptRval());
560
561
RootedObject env(cx, JS_ExtensibleLexicalEnvironment(varEnv));
562
563
// If the Gecko subscript loader specifies target objects, we need to add
564
// them to the environment. These are added after the NSVO environment.
565
if (!targetObj.empty()) {
566
// The environment chain will be as follows:
567
// GlobalObject / BackstagePass
568
// LexicalEnvironmentObject[this=global]
569
// NonSyntacticVariablesObject (the JSMEnvironment)
570
// LexicalEnvironmentObject[this=nsvo]
571
// WithEnvironmentObject[target=targetObj]
572
// LexicalEnvironmentObject[this=targetObj] (*)
573
//
574
// (*) This environment intentionally intercepts JSOP_GLOBALTHIS, but
575
// not JSOP_FUNCTIONTHIS (which instead will fallback to the NSVO). I
576
// don't make the rules, I just record them.
577
578
// Wrap the target objects in WithEnvironments.
579
if (!js::CreateObjectsForEnvironmentChain(cx, targetObj, env, &env)) {
580
return false;
581
}
582
583
// See CreateNonSyntacticEnvironmentChain
584
if (!JSObject::setQualifiedVarObj(cx, env)) {
585
return false;
586
}
587
588
// Create an extensible LexicalEnvironmentObject for target object
589
env = ObjectRealm::get(env).getOrCreateNonSyntacticLexicalEnvironment(cx,
590
env);
591
if (!env) {
592
return false;
593
}
594
}
595
596
return ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env);
597
}
598
599
JS_FRIEND_API JSObject* js::GetJSMEnvironmentOfScriptedCaller(JSContext* cx) {
600
FrameIter iter(cx);
601
if (iter.done()) {
602
return nullptr;
603
}
604
605
// WASM frames don't always provide their environment, but we also shouldn't
606
// expect to see any calling into here.
607
MOZ_RELEASE_ASSERT(!iter.isWasm());
608
609
RootedObject env(cx, iter.environmentChain(cx));
610
while (env && !env->is<NonSyntacticVariablesObject>()) {
611
env = env->enclosingEnvironment();
612
}
613
614
return env;
615
}
616
617
JS_FRIEND_API bool js::IsJSMEnvironment(JSObject* obj) {
618
// NOTE: This also returns true if the NonSyntacticVariablesObject was
619
// created for reasons other than the JSM loader.
620
return obj->is<NonSyntacticVariablesObject>();
621
}