1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
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 "frontend/BytecodeCompiler.h"
13 #include "gc/HashUtil.h"
14 #include "vm/Debugger.h"
15 #include "vm/GlobalObject.h"
16 #include "vm/JSContext.h"
17 #include "vm/JSONParser.h"
18 
19 #include "vm/Interpreter-inl.h"
20 
21 using namespace js;
22 
23 using mozilla::AddToHash;
24 using mozilla::HashString;
25 using mozilla::RangedPtr;
26 
27 using JS::AutoCheckCannotGC;
28 
29 // We should be able to assert this for *any* fp->environmentChain().
AssertInnerizedEnvironmentChain(JSContext * cx,JSObject & env)30 static void AssertInnerizedEnvironmentChain(JSContext* cx, JSObject& env) {
31 #ifdef DEBUG
32   RootedObject obj(cx);
33   for (obj = &env; obj; obj = obj->enclosingEnvironment())
34     MOZ_ASSERT(!IsWindowProxy(obj));
35 #endif
36 }
37 
IsEvalCacheCandidate(JSScript * script)38 static bool IsEvalCacheCandidate(JSScript* script) {
39   // Make sure there are no inner objects which might use the wrong parent
40   // and/or call scope by reusing the previous eval's script.
41   return script->isDirectEvalInFunction() && !script->hasSingletons() &&
42          !script->hasObjects();
43 }
44 
hash(const EvalCacheLookup & l)45 /* static */ HashNumber EvalCacheHashPolicy::hash(const EvalCacheLookup& l) {
46   AutoCheckCannotGC nogc;
47   uint32_t hash = l.str->hasLatin1Chars()
48                       ? HashString(l.str->latin1Chars(nogc), l.str->length())
49                       : HashString(l.str->twoByteChars(nogc), l.str->length());
50   return AddToHash(hash, l.callerScript.get(), l.pc);
51 }
52 
match(const EvalCacheEntry & cacheEntry,const EvalCacheLookup & l)53 /* static */ bool EvalCacheHashPolicy::match(const EvalCacheEntry& cacheEntry,
54                                              const EvalCacheLookup& l) {
55   MOZ_ASSERT(IsEvalCacheCandidate(cacheEntry.script));
56 
57   return EqualStrings(cacheEntry.str, l.str) &&
58          cacheEntry.callerScript == l.callerScript && cacheEntry.pc == l.pc;
59 }
60 
61 // Add the script to the eval cache when EvalKernel is finished
62 class EvalScriptGuard {
63   JSContext* cx_;
64   Rooted<JSScript*> script_;
65 
66   /* These fields are only valid if lookup_.str is non-nullptr. */
67   EvalCacheLookup lookup_;
68   mozilla::Maybe<DependentAddPtr<EvalCache>> p_;
69 
70   RootedLinearString lookupStr_;
71 
72  public:
EvalScriptGuard(JSContext * cx)73   explicit EvalScriptGuard(JSContext* cx)
74       : cx_(cx), script_(cx), lookup_(cx), lookupStr_(cx) {}
75 
~EvalScriptGuard()76   ~EvalScriptGuard() {
77     if (script_ && !cx_->isExceptionPending()) {
78       script_->cacheForEval();
79       EvalCacheEntry cacheEntry = {lookupStr_, script_, lookup_.callerScript,
80                                    lookup_.pc};
81       lookup_.str = lookupStr_;
82       if (lookup_.str && IsEvalCacheCandidate(script_)) {
83         // Ignore failure to add cache entry.
84         if (!p_->add(cx_, cx_->caches().evalCache, lookup_, cacheEntry))
85           cx_->recoverFromOutOfMemory();
86       }
87     }
88   }
89 
lookupInEvalCache(JSLinearString * str,JSScript * callerScript,jsbytecode * pc)90   void lookupInEvalCache(JSLinearString* str, JSScript* callerScript,
91                          jsbytecode* pc) {
92     lookupStr_ = str;
93     lookup_.str = str;
94     lookup_.callerScript = callerScript;
95     lookup_.pc = pc;
96     p_.emplace(cx_, cx_->caches().evalCache, lookup_);
97     if (*p_) {
98       script_ = (*p_)->script;
99       p_->remove(cx_, cx_->caches().evalCache, lookup_);
100       script_->uncacheForEval();
101     }
102   }
103 
setNewScript(JSScript * script)104   void setNewScript(JSScript* script) {
105     // JSScript::initFromEmitter has already called js_CallNewScriptHook.
106     MOZ_ASSERT(!script_ && script);
107     script_ = script;
108     script_->setActiveEval();
109   }
110 
foundScript()111   bool foundScript() { return !!script_; }
112 
script()113   HandleScript script() {
114     MOZ_ASSERT(script_);
115     return script_;
116   }
117 };
118 
119 enum EvalJSONResult { EvalJSON_Failure, EvalJSON_Success, EvalJSON_NotJSON };
120 
121 template <typename CharT>
EvalStringMightBeJSON(const mozilla::Range<const CharT> chars)122 static bool EvalStringMightBeJSON(const mozilla::Range<const CharT> chars) {
123   // If the eval string starts with '(' or '[' and ends with ')' or ']', it may
124   // be JSON. Try the JSON parser first because it's much faster.  If the eval
125   // string isn't JSON, JSON parsing will probably fail quickly, so little time
126   // will be lost.
127   size_t length = chars.length();
128   if (length > 2 && ((chars[0] == '[' && chars[length - 1] == ']') ||
129                      (chars[0] == '(' && chars[length - 1] == ')'))) {
130     // Remarkably, JavaScript syntax is not a superset of JSON syntax:
131     // strings in JavaScript cannot contain the Unicode line and paragraph
132     // terminator characters U+2028 and U+2029, but strings in JSON can.
133     // Rather than force the JSON parser to handle this quirk when used by
134     // eval, we simply don't use the JSON parser when either character
135     // appears in the provided string.  See bug 657367.
136     if (sizeof(CharT) > 1) {
137       for (RangedPtr<const CharT> cp = chars.begin() + 1, end = chars.end() - 1;
138            cp < end; cp++) {
139         char16_t c = *cp;
140         if (c == 0x2028 || c == 0x2029) return false;
141       }
142     }
143 
144     return true;
145   }
146   return false;
147 }
148 
149 template <typename CharT>
ParseEvalStringAsJSON(JSContext * cx,const mozilla::Range<const CharT> chars,MutableHandleValue rval)150 static EvalJSONResult ParseEvalStringAsJSON(
151     JSContext* cx, const mozilla::Range<const CharT> chars,
152     MutableHandleValue rval) {
153   size_t len = chars.length();
154   MOZ_ASSERT((chars[0] == '(' && chars[len - 1] == ')') ||
155              (chars[0] == '[' && chars[len - 1] == ']'));
156 
157   auto jsonChars = (chars[0] == '[') ? chars
158                                      : mozilla::Range<const CharT>(
159                                            chars.begin().get() + 1U, len - 2);
160 
161   Rooted<JSONParser<CharT>> parser(
162       cx, JSONParser<CharT>(cx, jsonChars, JSONParserBase::NoError));
163   if (!parser.parse(rval)) return EvalJSON_Failure;
164 
165   return rval.isUndefined() ? EvalJSON_NotJSON : EvalJSON_Success;
166 }
167 
TryEvalJSON(JSContext * cx,JSLinearString * str,MutableHandleValue rval)168 static EvalJSONResult TryEvalJSON(JSContext* cx, JSLinearString* str,
169                                   MutableHandleValue rval) {
170   if (str->hasLatin1Chars()) {
171     AutoCheckCannotGC nogc;
172     if (!EvalStringMightBeJSON(str->latin1Range(nogc))) return EvalJSON_NotJSON;
173   } else {
174     AutoCheckCannotGC nogc;
175     if (!EvalStringMightBeJSON(str->twoByteRange(nogc)))
176       return EvalJSON_NotJSON;
177   }
178 
179   AutoStableStringChars linearChars(cx);
180   if (!linearChars.init(cx, str)) return EvalJSON_Failure;
181 
182   return linearChars.isLatin1()
183              ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
184              : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
185 }
186 
187 enum EvalType { DIRECT_EVAL, INDIRECT_EVAL };
188 
189 // Common code implementing direct and indirect eval.
190 //
191 // Evaluate call.argv[2], if it is a string, in the context of the given calling
192 // frame, with the provided scope chain, with the semantics of either a direct
193 // or indirect eval (see ES5 10.4.2).  If this is an indirect eval, env
194 // must be a global object.
195 //
196 // On success, store the completion value in call.rval and return true.
EvalKernel(JSContext * cx,HandleValue v,EvalType evalType,AbstractFramePtr caller,HandleObject env,jsbytecode * pc,MutableHandleValue vp)197 static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
198                        AbstractFramePtr caller, HandleObject env,
199                        jsbytecode* pc, MutableHandleValue vp) {
200   MOZ_ASSERT((evalType == INDIRECT_EVAL) == !caller);
201   MOZ_ASSERT((evalType == INDIRECT_EVAL) == !pc);
202   MOZ_ASSERT_IF(evalType == INDIRECT_EVAL, IsGlobalLexicalEnvironment(env));
203   AssertInnerizedEnvironmentChain(cx, *env);
204 
205   Rooted<GlobalObject*> envGlobal(cx, &env->global());
206   if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
207     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
208                               JSMSG_CSP_BLOCKED_EVAL);
209     return false;
210   }
211 
212   // ES5 15.1.2.1 step 1.
213   if (!v.isString()) {
214     vp.set(v);
215     return true;
216   }
217   RootedString str(cx, v.toString());
218 
219   // ES5 15.1.2.1 steps 2-8.
220 
221   // Per ES5, indirect eval runs in the global scope. (eval is specified this
222   // way so that the compiler can make assumptions about what bindings may or
223   // may not exist in the current frame if it doesn't see 'eval'.)
224   MOZ_ASSERT_IF(evalType != DIRECT_EVAL,
225                 cx->global() == &env->as<LexicalEnvironmentObject>().global());
226 
227   RootedLinearString linearStr(cx, str->ensureLinear(cx));
228   if (!linearStr) return false;
229 
230   RootedScript callerScript(cx, caller ? caller.script() : nullptr);
231   EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
232   if (ejr != EvalJSON_NotJSON) return ejr == EvalJSON_Success;
233 
234   EvalScriptGuard esg(cx);
235 
236   if (evalType == DIRECT_EVAL && caller.isFunctionFrame())
237     esg.lookupInEvalCache(linearStr, callerScript, pc);
238 
239   if (!esg.foundScript()) {
240     RootedScript maybeScript(cx);
241     unsigned lineno;
242     const char* filename;
243     bool mutedErrors;
244     uint32_t pcOffset;
245     DescribeScriptedCallerForCompilation(
246         cx, &maybeScript, &filename, &lineno, &pcOffset, &mutedErrors,
247         evalType == DIRECT_EVAL ? CALLED_FROM_JSOP_EVAL
248                                 : NOT_CALLED_FROM_JSOP_EVAL);
249 
250     const char* introducerFilename = filename;
251     if (maybeScript && maybeScript->scriptSource()->introducerFilename())
252       introducerFilename = maybeScript->scriptSource()->introducerFilename();
253 
254     RootedScope enclosing(cx);
255     if (evalType == DIRECT_EVAL)
256       enclosing = callerScript->innermostScope(pc);
257     else
258       enclosing = &cx->global()->emptyGlobalScope();
259 
260     CompileOptions options(cx);
261     options.setIsRunOnce(true)
262         .setNoScriptRval(false)
263         .setMutedErrors(mutedErrors)
264         .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
265 
266     if (introducerFilename) {
267       options.setFileAndLine(filename, 1);
268       options.setIntroductionInfo(introducerFilename, "eval", lineno,
269                                   maybeScript, pcOffset);
270     } else {
271       options.setFileAndLine("eval", 1);
272       options.setIntroductionType("eval");
273     }
274 
275     AutoStableStringChars linearChars(cx);
276     if (!linearChars.initTwoByte(cx, linearStr)) return false;
277 
278     const char16_t* chars = linearChars.twoByteRange().begin().get();
279     SourceBufferHolder::Ownership ownership =
280         linearChars.maybeGiveOwnershipToCaller()
281             ? SourceBufferHolder::GiveOwnership
282             : SourceBufferHolder::NoOwnership;
283     SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
284     JSScript* compiled = frontend::CompileEvalScript(
285         cx, cx->tempLifoAlloc(), env, enclosing, options, srcBuf);
286     if (!compiled) return false;
287 
288     esg.setNewScript(compiled);
289   }
290 
291   // Look up the newTarget from the frame iterator.
292   Value newTargetVal = NullValue();
293   return ExecuteKernel(cx, esg.script(), *env, newTargetVal,
294                        NullFramePtr() /* evalInFrame */, vp.address());
295 }
296 
DirectEvalStringFromIon(JSContext * cx,HandleObject env,HandleScript callerScript,HandleValue newTargetValue,HandleString str,jsbytecode * pc,MutableHandleValue vp)297 bool js::DirectEvalStringFromIon(JSContext* cx, HandleObject env,
298                                  HandleScript callerScript,
299                                  HandleValue newTargetValue, HandleString str,
300                                  jsbytecode* pc, MutableHandleValue vp) {
301   AssertInnerizedEnvironmentChain(cx, *env);
302 
303   Rooted<GlobalObject*> envGlobal(cx, &env->global());
304   if (!GlobalObject::isRuntimeCodeGenEnabled(cx, envGlobal)) {
305     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
306                               JSMSG_CSP_BLOCKED_EVAL);
307     return false;
308   }
309 
310   // ES5 15.1.2.1 steps 2-8.
311 
312   RootedLinearString linearStr(cx, str->ensureLinear(cx));
313   if (!linearStr) return false;
314 
315   EvalJSONResult ejr = TryEvalJSON(cx, linearStr, vp);
316   if (ejr != EvalJSON_NotJSON) return ejr == EvalJSON_Success;
317 
318   EvalScriptGuard esg(cx);
319 
320   esg.lookupInEvalCache(linearStr, callerScript, pc);
321 
322   if (!esg.foundScript()) {
323     RootedScript maybeScript(cx);
324     const char* filename;
325     unsigned lineno;
326     bool mutedErrors;
327     uint32_t pcOffset;
328     DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno,
329                                          &pcOffset, &mutedErrors,
330                                          CALLED_FROM_JSOP_EVAL);
331 
332     const char* introducerFilename = filename;
333     if (maybeScript && maybeScript->scriptSource()->introducerFilename())
334       introducerFilename = maybeScript->scriptSource()->introducerFilename();
335 
336     RootedScope enclosing(cx, callerScript->innermostScope(pc));
337 
338     CompileOptions options(cx);
339     options.setIsRunOnce(true)
340         .setNoScriptRval(false)
341         .setMutedErrors(mutedErrors)
342         .maybeMakeStrictMode(IsStrictEvalPC(pc));
343 
344     if (introducerFilename) {
345       options.setFileAndLine(filename, 1);
346       options.setIntroductionInfo(introducerFilename, "eval", lineno,
347                                   maybeScript, pcOffset);
348     } else {
349       options.setFileAndLine("eval", 1);
350       options.setIntroductionType("eval");
351     }
352 
353     AutoStableStringChars linearChars(cx);
354     if (!linearChars.initTwoByte(cx, linearStr)) return false;
355 
356     const char16_t* chars = linearChars.twoByteRange().begin().get();
357     SourceBufferHolder::Ownership ownership =
358         linearChars.maybeGiveOwnershipToCaller()
359             ? SourceBufferHolder::GiveOwnership
360             : SourceBufferHolder::NoOwnership;
361     SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
362     JSScript* compiled = frontend::CompileEvalScript(
363         cx, cx->tempLifoAlloc(), env, enclosing, options, srcBuf);
364     if (!compiled) return false;
365 
366     esg.setNewScript(compiled);
367   }
368 
369   return ExecuteKernel(cx, esg.script(), *env, newTargetValue,
370                        NullFramePtr() /* evalInFrame */, vp.address());
371 }
372 
IndirectEval(JSContext * cx,unsigned argc,Value * vp)373 bool js::IndirectEval(JSContext* cx, unsigned argc, Value* vp) {
374   CallArgs args = CallArgsFromVp(argc, vp);
375 
376   Rooted<GlobalObject*> global(cx, &args.callee().global());
377   RootedObject globalLexical(cx, &global->lexicalEnvironment());
378 
379   // Note we'll just pass |undefined| here, then return it directly (or throw
380   // if runtime codegen is disabled), if no argument is provided.
381   return EvalKernel(cx, args.get(0), INDIRECT_EVAL, NullFramePtr(),
382                     globalLexical, nullptr, args.rval());
383 }
384 
DirectEval(JSContext * cx,HandleValue v,MutableHandleValue vp)385 bool js::DirectEval(JSContext* cx, HandleValue v, MutableHandleValue vp) {
386   // Direct eval can assume it was called from an interpreted or baseline frame.
387   ScriptFrameIter iter(cx);
388   AbstractFramePtr caller = iter.abstractFramePtr();
389 
390   MOZ_ASSERT(JSOp(*iter.pc()) == JSOP_EVAL ||
391              JSOp(*iter.pc()) == JSOP_STRICTEVAL ||
392              JSOp(*iter.pc()) == JSOP_SPREADEVAL ||
393              JSOp(*iter.pc()) == JSOP_STRICTSPREADEVAL);
394   MOZ_ASSERT(caller.compartment() == caller.script()->compartment());
395 
396   RootedObject envChain(cx, caller.environmentChain());
397   return EvalKernel(cx, v, DIRECT_EVAL, caller, envChain, iter.pc(), vp);
398 }
399 
IsAnyBuiltinEval(JSFunction * fun)400 bool js::IsAnyBuiltinEval(JSFunction* fun) {
401   return fun->maybeNative() == IndirectEval;
402 }
403 
ExecuteInExtensibleLexicalEnvironment(JSContext * cx,HandleScript scriptArg,HandleObject env)404 static bool ExecuteInExtensibleLexicalEnvironment(JSContext* cx,
405                                                   HandleScript scriptArg,
406                                                   HandleObject env) {
407   CHECK_REQUEST(cx);
408   assertSameCompartment(cx, env);
409   MOZ_ASSERT(IsExtensibleLexicalEnvironment(env));
410   MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
411 
412   RootedScript script(cx, scriptArg);
413   if (script->compartment() != cx->compartment()) {
414     script = CloneGlobalScript(cx, ScopeKind::NonSyntactic, script);
415     if (!script) return false;
416 
417     Debugger::onNewScript(cx, script);
418   }
419 
420   RootedValue rval(cx);
421   return ExecuteKernel(cx, script, *env, UndefinedValue(),
422                        NullFramePtr() /* evalInFrame */, rval.address());
423 }
424 
ExecuteInGlobalAndReturnScope(JSContext * cx,HandleObject global,HandleScript scriptArg,MutableHandleObject envArg)425 JS_FRIEND_API bool js::ExecuteInGlobalAndReturnScope(
426     JSContext* cx, HandleObject global, HandleScript scriptArg,
427     MutableHandleObject envArg) {
428   RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
429   if (!varEnv) return false;
430 
431   // Create lexical environment with |this| == global.
432   // NOTE: This is required behavior for Gecko FrameScriptLoader
433   RootedObject lexEnv(
434       cx, LexicalEnvironmentObject::createNonSyntactic(cx, varEnv, global));
435   if (!lexEnv) return false;
436 
437   if (!ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, lexEnv))
438     return false;
439 
440   envArg.set(lexEnv);
441   return true;
442 }
443 
NewJSMEnvironment(JSContext * cx)444 JS_FRIEND_API JSObject* js::NewJSMEnvironment(JSContext* cx) {
445   RootedObject varEnv(cx, NonSyntacticVariablesObject::create(cx));
446   if (!varEnv) return nullptr;
447 
448   // Force LexicalEnvironmentObject to be created
449   MOZ_ASSERT(!cx->compartment()->getNonSyntacticLexicalEnvironment(varEnv));
450   if (!cx->compartment()->getOrCreateNonSyntacticLexicalEnvironment(cx, varEnv))
451     return nullptr;
452 
453   return varEnv;
454 }
455 
ExecuteInJSMEnvironment(JSContext * cx,HandleScript scriptArg,HandleObject varEnv)456 JS_FRIEND_API bool js::ExecuteInJSMEnvironment(JSContext* cx,
457                                                HandleScript scriptArg,
458                                                HandleObject varEnv) {
459   AutoObjectVector emptyChain(cx);
460   return ExecuteInJSMEnvironment(cx, scriptArg, varEnv, emptyChain);
461 }
462 
ExecuteInJSMEnvironment(JSContext * cx,HandleScript scriptArg,HandleObject varEnv,AutoObjectVector & targetObj)463 JS_FRIEND_API bool js::ExecuteInJSMEnvironment(JSContext* cx,
464                                                HandleScript scriptArg,
465                                                HandleObject varEnv,
466                                                AutoObjectVector& targetObj) {
467   assertSameCompartment(cx, varEnv);
468   MOZ_ASSERT(cx->compartment()->getNonSyntacticLexicalEnvironment(varEnv));
469   MOZ_DIAGNOSTIC_ASSERT(scriptArg->noScriptRval());
470 
471   RootedObject env(cx, JS_ExtensibleLexicalEnvironment(varEnv));
472 
473   // If the Gecko subscript loader specifies target objects, we need to add
474   // them to the environment. These are added after the NSVO environment.
475   if (!targetObj.empty()) {
476     // The environment chain will be as follows:
477     //      GlobalObject / BackstagePass
478     //      LexicalEnvironmentObject[this=global]
479     //      NonSyntacticVariablesObject (the JSMEnvironment)
480     //      LexicalEnvironmentObject[this=nsvo]
481     //      WithEnvironmentObject[target=targetObj]
482     //      LexicalEnvironmentObject[this=targetObj] (*)
483     //
484     //  (*) This environment intentionally intercepts JSOP_GLOBALTHIS, but
485     //  not JSOP_FUNCTIONTHIS (which instead will fallback to the NSVO). I
486     //  don't make the rules, I just record them.
487 
488     // Wrap the target objects in WithEnvironments.
489     if (!js::CreateObjectsForEnvironmentChain(cx, targetObj, env, &env))
490       return false;
491 
492     // See CreateNonSyntacticEnvironmentChain
493     if (!JSObject::setQualifiedVarObj(cx, env)) return false;
494 
495     // Create an extensible LexicalEnvironmentObject for target object
496     env = cx->compartment()->getOrCreateNonSyntacticLexicalEnvironment(cx, env);
497     if (!env) return false;
498   }
499 
500   return ExecuteInExtensibleLexicalEnvironment(cx, scriptArg, env);
501 }
502 
GetJSMEnvironmentOfScriptedCaller(JSContext * cx)503 JS_FRIEND_API JSObject* js::GetJSMEnvironmentOfScriptedCaller(JSContext* cx) {
504   FrameIter iter(cx);
505   if (iter.done()) return nullptr;
506 
507   // WASM frames don't always provide their environment, but we also shouldn't
508   // expect to see any calling into here.
509   MOZ_RELEASE_ASSERT(!iter.isWasm());
510 
511   RootedObject env(cx, iter.environmentChain(cx));
512   while (env && !env->is<NonSyntacticVariablesObject>())
513     env = env->enclosingEnvironment();
514 
515   return env;
516 }
517 
IsJSMEnvironment(JSObject * obj)518 JS_FRIEND_API bool js::IsJSMEnvironment(JSObject* obj) {
519   // NOTE: This also returns true if the NonSyntacticVariablesObject was
520   // created for reasons other than the JSM loader.
521   return obj->is<NonSyntacticVariablesObject>();
522 }
523