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 #ifndef vm_Interpreter_inl_h
8 #define vm_Interpreter_inl_h
9 
10 #include "vm/Interpreter.h"
11 
12 #include "jscompartment.h"
13 #include "jsnum.h"
14 #include "jsstr.h"
15 
16 #include "jit/Ion.h"
17 #include "vm/ArgumentsObject.h"
18 
19 #include "jsatominlines.h"
20 #include "jsobjinlines.h"
21 
22 #include "vm/ScopeObject-inl.h"
23 #include "vm/Stack-inl.h"
24 #include "vm/String-inl.h"
25 #include "vm/UnboxedObject-inl.h"
26 
27 namespace js {
28 
29 /*
30  * Every possible consumer of MagicValue(JS_OPTIMIZED_ARGUMENTS) (as determined
31  * by ScriptAnalysis::needsArgsObj) must check for these magic values and, when
32  * one is received, act as if the value were the function's ArgumentsObject.
33  * Additionally, it is possible that, after 'arguments' was copied into a
34  * temporary, the arguments object has been created a some other failed guard
35  * that called JSScript::argumentsOptimizationFailed. In this case, it is
36  * always valid (and necessary) to replace JS_OPTIMIZED_ARGUMENTS with the real
37  * arguments object.
38  */
39 static inline bool
IsOptimizedArguments(AbstractFramePtr frame,MutableHandleValue vp)40 IsOptimizedArguments(AbstractFramePtr frame, MutableHandleValue vp)
41 {
42     if (vp.isMagic(JS_OPTIMIZED_ARGUMENTS) && frame.script()->needsArgsObj())
43         vp.setObject(frame.argsObj());
44     return vp.isMagic(JS_OPTIMIZED_ARGUMENTS);
45 }
46 
47 /*
48  * One optimized consumer of MagicValue(JS_OPTIMIZED_ARGUMENTS) is f.apply.
49  * However, this speculation must be guarded before calling 'apply' in case it
50  * is not the builtin Function.prototype.apply.
51  */
52 static inline bool
GuardFunApplyArgumentsOptimization(JSContext * cx,AbstractFramePtr frame,CallArgs & args)53 GuardFunApplyArgumentsOptimization(JSContext* cx, AbstractFramePtr frame, CallArgs& args)
54 {
55     if (args.length() == 2 && IsOptimizedArguments(frame, args[1])) {
56         if (!IsNativeFunction(args.calleev(), js::fun_apply)) {
57             RootedScript script(cx, frame.script());
58             if (!JSScript::argumentsOptimizationFailed(cx, script))
59                 return false;
60             args[1].setObject(frame.argsObj());
61         }
62     }
63 
64     return true;
65 }
66 
67 /*
68  * Per ES6, lexical declarations may not be accessed in any fashion until they
69  * are initialized (i.e., until the actual declaring statement is
70  * executed). The various LEXICAL opcodes need to check if the slot is an
71  * uninitialized let declaration, represented by the magic value
72  * JS_UNINITIALIZED_LEXICAL.
73  */
74 static inline bool
IsUninitializedLexical(const Value & val)75 IsUninitializedLexical(const Value& val)
76 {
77     // Use whyMagic here because JS_OPTIMIZED_ARGUMENTS could flow into here.
78     return val.isMagic() && val.whyMagic() == JS_UNINITIALIZED_LEXICAL;
79 }
80 
81 static inline bool
IsUninitializedLexicalSlot(HandleObject obj,HandleShape shape)82 IsUninitializedLexicalSlot(HandleObject obj, HandleShape shape)
83 {
84     if (obj->is<DynamicWithObject>())
85         return false;
86     // We check for IsImplicitDenseOrTypedArrayElement even though the shape
87     // is always a non-indexed property because proxy hooks may return a
88     // "non-native property found" shape, which happens to be encoded in the
89     // same way as the "dense element" shape. See MarkNonNativePropertyFound.
90     if (!shape ||
91         IsImplicitDenseOrTypedArrayElement(shape) ||
92         !shape->hasSlot() ||
93         !shape->hasDefaultGetter() ||
94         !shape->hasDefaultSetter())
95     {
96         return false;
97     }
98     MOZ_ASSERT(obj->as<NativeObject>().containsPure(shape));
99     return IsUninitializedLexical(obj->as<NativeObject>().getSlot(shape->slot()));
100 }
101 
102 static inline void
ReportUninitializedLexical(JSContext * cx,HandlePropertyName name)103 ReportUninitializedLexical(JSContext* cx, HandlePropertyName name)
104 {
105     ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, name);
106 }
107 
108 static inline void
ReportUninitializedLexical(JSContext * cx,HandleScript script,jsbytecode * pc)109 ReportUninitializedLexical(JSContext* cx, HandleScript script, jsbytecode* pc)
110 {
111     ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, script, pc);
112 }
113 
114 static inline bool
CheckUninitializedLexical(JSContext * cx,PropertyName * name_,HandleValue val)115 CheckUninitializedLexical(JSContext* cx, PropertyName* name_, HandleValue val)
116 {
117     if (IsUninitializedLexical(val)) {
118         RootedPropertyName name(cx, name_);
119         ReportUninitializedLexical(cx, name);
120         return false;
121     }
122     return true;
123 }
124 
125 static inline bool
CheckUninitializedLexical(JSContext * cx,HandleScript script,jsbytecode * pc,HandleValue val)126 CheckUninitializedLexical(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue val)
127 {
128     if (IsUninitializedLexical(val)) {
129         ReportUninitializedLexical(cx, script, pc);
130         return false;
131     }
132     return true;
133 }
134 
135 static inline void
ReportRuntimeConstAssignment(JSContext * cx,HandlePropertyName name)136 ReportRuntimeConstAssignment(JSContext* cx, HandlePropertyName name)
137 {
138     ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, name);
139 }
140 
141 static inline void
ReportRuntimeConstAssignment(JSContext * cx,HandleScript script,jsbytecode * pc)142 ReportRuntimeConstAssignment(JSContext* cx, HandleScript script, jsbytecode* pc)
143 {
144     ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, script, pc);
145 }
146 
147 inline bool
GetLengthProperty(const Value & lval,MutableHandleValue vp)148 GetLengthProperty(const Value& lval, MutableHandleValue vp)
149 {
150     /* Optimize length accesses on strings, arrays, and arguments. */
151     if (lval.isString()) {
152         vp.setInt32(lval.toString()->length());
153         return true;
154     }
155     if (lval.isObject()) {
156         JSObject* obj = &lval.toObject();
157         if (obj->is<ArrayObject>()) {
158             vp.setNumber(obj->as<ArrayObject>().length());
159             return true;
160         }
161 
162         if (obj->is<ArgumentsObject>()) {
163             ArgumentsObject* argsobj = &obj->as<ArgumentsObject>();
164             if (!argsobj->hasOverriddenLength()) {
165                 uint32_t length = argsobj->initialLength();
166                 MOZ_ASSERT(length < INT32_MAX);
167                 vp.setInt32(int32_t(length));
168                 return true;
169             }
170         }
171     }
172 
173     return false;
174 }
175 
176 template <bool TypeOf> inline bool
FetchName(JSContext * cx,HandleObject obj,HandleObject obj2,HandlePropertyName name,HandleShape shape,MutableHandleValue vp)177 FetchName(JSContext* cx, HandleObject obj, HandleObject obj2, HandlePropertyName name,
178           HandleShape shape, MutableHandleValue vp)
179 {
180     if (!shape) {
181         if (TypeOf) {
182             vp.setUndefined();
183             return true;
184         }
185         return ReportIsNotDefined(cx, name);
186     }
187 
188     /* Take the slow path if shape was not found in a native object. */
189     if (!obj->isNative() || !obj2->isNative()) {
190         Rooted<jsid> id(cx, NameToId(name));
191         if (!GetProperty(cx, obj, obj, id, vp))
192             return false;
193     } else {
194         RootedObject normalized(cx, obj);
195         if (normalized->is<DynamicWithObject>() && !shape->hasDefaultGetter())
196             normalized = &normalized->as<DynamicWithObject>().object();
197         if (shape->isDataDescriptor() && shape->hasDefaultGetter()) {
198             /* Fast path for Object instance properties. */
199             MOZ_ASSERT(shape->hasSlot());
200             vp.set(obj2->as<NativeObject>().getSlot(shape->slot()));
201         } else {
202             if (!NativeGetExistingProperty(cx, normalized, obj2.as<NativeObject>(), shape, vp))
203                 return false;
204         }
205     }
206 
207     // We do our own explicit checking for |this|
208     if (name == cx->names().dotThis)
209         return true;
210 
211     // NAME operations are the slow paths already, so unconditionally check
212     // for uninitialized lets.
213     return CheckUninitializedLexical(cx, name, vp);
214 }
215 
216 inline bool
FetchNameNoGC(JSObject * pobj,Shape * shape,MutableHandleValue vp)217 FetchNameNoGC(JSObject* pobj, Shape* shape, MutableHandleValue vp)
218 {
219     if (!shape || !pobj->isNative() || !shape->isDataDescriptor() || !shape->hasDefaultGetter())
220         return false;
221 
222     vp.set(pobj->as<NativeObject>().getSlot(shape->slot()));
223     return !IsUninitializedLexical(vp);
224 }
225 
226 inline bool
GetIntrinsicOperation(JSContext * cx,jsbytecode * pc,MutableHandleValue vp)227 GetIntrinsicOperation(JSContext* cx, jsbytecode* pc, MutableHandleValue vp)
228 {
229     RootedPropertyName name(cx, cx->currentScript()->getName(pc));
230     return GlobalObject::getIntrinsicValue(cx, cx->global(), name, vp);
231 }
232 
233 inline bool
SetIntrinsicOperation(JSContext * cx,JSScript * script,jsbytecode * pc,HandleValue val)234 SetIntrinsicOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleValue val)
235 {
236     RootedPropertyName name(cx, script->getName(pc));
237     return GlobalObject::setIntrinsicValue(cx, cx->global(), name, val);
238 }
239 
240 inline void
SetAliasedVarOperation(JSContext * cx,JSScript * script,jsbytecode * pc,ScopeObject & obj,ScopeCoordinate sc,const Value & val,MaybeCheckLexical checkLexical)241 SetAliasedVarOperation(JSContext* cx, JSScript* script, jsbytecode* pc,
242                        ScopeObject& obj, ScopeCoordinate sc, const Value& val,
243                        MaybeCheckLexical checkLexical)
244 {
245     MOZ_ASSERT_IF(checkLexical, !IsUninitializedLexical(obj.aliasedVar(sc)));
246 
247     // Avoid computing the name if no type updates are needed, as this may be
248     // expensive on scopes with large numbers of variables.
249     PropertyName* name = obj.isSingleton()
250                          ? ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc)
251                          : nullptr;
252 
253     obj.setAliasedVar(cx, sc, name, val);
254 }
255 
256 inline bool
SetNameOperation(JSContext * cx,JSScript * script,jsbytecode * pc,HandleObject scope,HandleValue val)257 SetNameOperation(JSContext* cx, JSScript* script, jsbytecode* pc, HandleObject scope,
258                  HandleValue val)
259 {
260     MOZ_ASSERT(*pc == JSOP_SETNAME ||
261                *pc == JSOP_STRICTSETNAME ||
262                *pc == JSOP_SETGNAME ||
263                *pc == JSOP_STRICTSETGNAME);
264     MOZ_ASSERT_IF((*pc == JSOP_SETGNAME || *pc == JSOP_STRICTSETGNAME) &&
265                   !script->hasNonSyntacticScope(),
266                   scope == cx->global() ||
267                   scope == &cx->global()->lexicalScope() ||
268                   scope->is<RuntimeLexicalErrorObject>());
269 
270     bool strict = *pc == JSOP_STRICTSETNAME || *pc == JSOP_STRICTSETGNAME;
271     RootedPropertyName name(cx, script->getName(pc));
272 
273     // In strict mode, assigning to an undeclared global variable is an
274     // error. To detect this, we call NativeSetProperty directly and pass
275     // Unqualified. It stores the error, if any, in |result|.
276     bool ok;
277     ObjectOpResult result;
278     RootedId id(cx, NameToId(name));
279     RootedValue receiver(cx, ObjectValue(*scope));
280     if (scope->isUnqualifiedVarObj()) {
281         MOZ_ASSERT(!scope->getOps()->setProperty);
282         ok = NativeSetProperty(cx, scope.as<NativeObject>(), id, val, receiver, Unqualified,
283                                result);
284     } else {
285         ok = SetProperty(cx, scope, id, val, receiver, result);
286     }
287     return ok && result.checkStrictErrorOrWarning(cx, scope, id, strict);
288 }
289 
290 inline bool
DefLexicalOperation(JSContext * cx,Handle<ClonedBlockObject * > lexicalScope,HandleObject varObj,HandlePropertyName name,unsigned attrs)291 DefLexicalOperation(JSContext* cx, Handle<ClonedBlockObject*> lexicalScope,
292                     HandleObject varObj, HandlePropertyName name, unsigned attrs)
293 {
294     // Redeclaration checks should have already been done.
295     MOZ_ASSERT(CheckLexicalNameConflict(cx, lexicalScope, varObj, name));
296     RootedId id(cx, NameToId(name));
297     RootedValue uninitialized(cx, MagicValue(JS_UNINITIALIZED_LEXICAL));
298     return NativeDefineProperty(cx, lexicalScope, id, uninitialized, nullptr, nullptr, attrs);
299 }
300 
301 inline bool
DefLexicalOperation(JSContext * cx,ClonedBlockObject * lexicalScopeArg,JSObject * varObjArg,JSScript * script,jsbytecode * pc)302 DefLexicalOperation(JSContext* cx, ClonedBlockObject* lexicalScopeArg,
303                     JSObject* varObjArg, JSScript* script, jsbytecode* pc)
304 {
305     MOZ_ASSERT(*pc == JSOP_DEFLET || *pc == JSOP_DEFCONST);
306     RootedPropertyName name(cx, script->getName(pc));
307 
308     unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT;
309     if (*pc == JSOP_DEFCONST)
310         attrs |= JSPROP_READONLY;
311 
312     Rooted<ClonedBlockObject*> lexicalScope(cx, lexicalScopeArg);
313     RootedObject varObj(cx, varObjArg);
314     MOZ_ASSERT_IF(!script->hasNonSyntacticScope(),
315                   lexicalScope == &cx->global()->lexicalScope() && varObj == cx->global());
316 
317     return DefLexicalOperation(cx, lexicalScope, varObj, name, attrs);
318 }
319 
320 inline void
InitGlobalLexicalOperation(JSContext * cx,ClonedBlockObject * lexicalScopeArg,JSScript * script,jsbytecode * pc,HandleValue value)321 InitGlobalLexicalOperation(JSContext* cx, ClonedBlockObject* lexicalScopeArg,
322                            JSScript* script, jsbytecode* pc, HandleValue value)
323 {
324     MOZ_ASSERT_IF(!script->hasNonSyntacticScope(),
325                   lexicalScopeArg == &cx->global()->lexicalScope());
326     MOZ_ASSERT(*pc == JSOP_INITGLEXICAL);
327     Rooted<ClonedBlockObject*> lexicalScope(cx, lexicalScopeArg);
328     RootedShape shape(cx, lexicalScope->lookup(cx, script->getName(pc)));
329     MOZ_ASSERT(shape);
330     lexicalScope->setSlot(shape->slot(), value);
331 }
332 
333 inline bool
InitPropertyOperation(JSContext * cx,JSOp op,HandleObject obj,HandleId id,HandleValue rhs)334 InitPropertyOperation(JSContext* cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs)
335 {
336     if (obj->is<PlainObject>() || obj->is<JSFunction>()) {
337         unsigned propAttrs = GetInitDataPropAttrs(op);
338         return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs, nullptr, nullptr,
339                                     propAttrs);
340     }
341 
342     MOZ_ASSERT(obj->as<UnboxedPlainObject>().layout().lookup(id));
343     return PutProperty(cx, obj, id, rhs, false);
344 }
345 
346 inline bool
DefVarOperation(JSContext * cx,HandleObject varobj,HandlePropertyName dn,unsigned attrs)347 DefVarOperation(JSContext* cx, HandleObject varobj, HandlePropertyName dn, unsigned attrs)
348 {
349     MOZ_ASSERT(varobj->isQualifiedVarObj());
350 
351 #ifdef DEBUG
352     // Per spec, it is an error to redeclare a lexical binding. This should
353     // have already been checked.
354     if (JS_HasExtensibleLexicalScope(varobj)) {
355         Rooted<ClonedBlockObject*> lexicalScope(cx);
356         lexicalScope = &JS_ExtensibleLexicalScope(varobj)->as<ClonedBlockObject>();
357         MOZ_ASSERT(CheckVarNameConflict(cx, lexicalScope, dn));
358     }
359 #endif
360 
361     RootedShape prop(cx);
362     RootedObject obj2(cx);
363     if (!LookupProperty(cx, varobj, dn, &obj2, &prop))
364         return false;
365 
366     /* Steps 8c, 8d. */
367     if (!prop || (obj2 != varobj && varobj->is<GlobalObject>())) {
368         if (!DefineProperty(cx, varobj, dn, UndefinedHandleValue, nullptr, nullptr, attrs))
369             return false;
370     }
371 
372     return true;
373 }
374 
375 static MOZ_ALWAYS_INLINE bool
NegOperation(JSContext * cx,HandleScript script,jsbytecode * pc,HandleValue val,MutableHandleValue res)376 NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue val,
377              MutableHandleValue res)
378 {
379     /*
380      * When the operand is int jsval, INT32_FITS_IN_JSVAL(i) implies
381      * INT32_FITS_IN_JSVAL(-i) unless i is 0 or INT32_MIN when the
382      * results, -0.0 or INT32_MAX + 1, are double values.
383      */
384     int32_t i;
385     if (val.isInt32() && (i = val.toInt32()) != 0 && i != INT32_MIN) {
386         res.setInt32(-i);
387     } else {
388         double d;
389         if (!ToNumber(cx, val, &d))
390             return false;
391         res.setNumber(-d);
392     }
393 
394     return true;
395 }
396 
397 static MOZ_ALWAYS_INLINE bool
ToIdOperation(JSContext * cx,HandleScript script,jsbytecode * pc,HandleValue idval,MutableHandleValue res)398 ToIdOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue idval,
399               MutableHandleValue res)
400 {
401     if (idval.isInt32()) {
402         res.set(idval);
403         return true;
404     }
405 
406     RootedId id(cx);
407     if (!ToPropertyKey(cx, idval, &id))
408         return false;
409 
410     res.set(IdToValue(id));
411     return true;
412 }
413 
414 static MOZ_ALWAYS_INLINE bool
GetObjectElementOperation(JSContext * cx,JSOp op,JS::HandleObject obj,JS::HandleObject receiver,HandleValue key,MutableHandleValue res)415 GetObjectElementOperation(JSContext* cx, JSOp op, JS::HandleObject obj, JS::HandleObject receiver,
416                           HandleValue key, MutableHandleValue res)
417 {
418     MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM || op == JSOP_GETELEM_SUPER);
419     MOZ_ASSERT_IF(op == JSOP_GETELEM || op == JSOP_CALLELEM, obj == receiver);
420 
421     do {
422         uint32_t index;
423         if (IsDefinitelyIndex(key, &index)) {
424             if (GetElementNoGC(cx, obj, receiver, index, res.address()))
425                 break;
426 
427             if (!GetElement(cx, obj, receiver, index, res))
428                 return false;
429             break;
430         }
431 
432         if (key.isString()) {
433             JSString* str = key.toString();
434             JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str);
435             if (!name)
436                 return false;
437             if (name->isIndex(&index)) {
438                 if (GetElementNoGC(cx, obj, receiver, index, res.address()))
439                     break;
440             } else {
441                 if (GetPropertyNoGC(cx, obj, receiver, name->asPropertyName(), res.address()))
442                     break;
443             }
444         }
445 
446         RootedId id(cx);
447         if (!ToPropertyKey(cx, key, &id))
448             return false;
449         if (!GetProperty(cx, obj, receiver, id, res))
450             return false;
451     } while (false);
452 
453     assertSameCompartmentDebugOnly(cx, res);
454     return true;
455 }
456 
457 static MOZ_ALWAYS_INLINE bool
GetPrimitiveElementOperation(JSContext * cx,JSOp op,JS::HandleValue receiver_,HandleValue key,MutableHandleValue res)458 GetPrimitiveElementOperation(JSContext* cx, JSOp op, JS::HandleValue receiver_,
459                              HandleValue key, MutableHandleValue res)
460 {
461     MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM);
462 
463     // FIXME: We shouldn't be boxing here or exposing the boxed object as
464     //        receiver anywhere below (bug 603201).
465     RootedObject boxed(cx, ToObjectFromStack(cx, receiver_));
466     if (!boxed)
467         return false;
468     RootedValue receiver(cx, ObjectValue(*boxed));
469 
470     do {
471         uint32_t index;
472         if (IsDefinitelyIndex(key, &index)) {
473             if (GetElementNoGC(cx, boxed, receiver, index, res.address()))
474                 break;
475 
476             if (!GetElement(cx, boxed, receiver, index, res))
477                 return false;
478             break;
479         }
480 
481         if (key.isString()) {
482             JSString* str = key.toString();
483             JSAtom* name = str->isAtom() ? &str->asAtom() : AtomizeString(cx, str);
484             if (!name)
485                 return false;
486             if (name->isIndex(&index)) {
487                 if (GetElementNoGC(cx, boxed, receiver, index, res.address()))
488                     break;
489             } else {
490                 if (GetPropertyNoGC(cx, boxed, receiver, name->asPropertyName(), res.address()))
491                     break;
492             }
493         }
494 
495         RootedId id(cx);
496         if (!ToPropertyKey(cx, key, &id))
497             return false;
498         if (!GetProperty(cx, boxed, boxed, id, res))
499             return false;
500     } while (false);
501 
502     assertSameCompartmentDebugOnly(cx, res);
503     return true;
504 }
505 
506 static MOZ_ALWAYS_INLINE bool
GetElemOptimizedArguments(JSContext * cx,AbstractFramePtr frame,MutableHandleValue lref,HandleValue rref,MutableHandleValue res,bool * done)507 GetElemOptimizedArguments(JSContext* cx, AbstractFramePtr frame, MutableHandleValue lref,
508                           HandleValue rref, MutableHandleValue res, bool* done)
509 {
510     MOZ_ASSERT(!*done);
511 
512     if (IsOptimizedArguments(frame, lref)) {
513         if (rref.isInt32()) {
514             int32_t i = rref.toInt32();
515             if (i >= 0 && uint32_t(i) < frame.numActualArgs()) {
516                 res.set(frame.unaliasedActual(i));
517                 *done = true;
518                 return true;
519             }
520         }
521 
522         RootedScript script(cx, frame.script());
523         if (!JSScript::argumentsOptimizationFailed(cx, script))
524             return false;
525 
526         lref.set(ObjectValue(frame.argsObj()));
527     }
528 
529     return true;
530 }
531 
532 static MOZ_ALWAYS_INLINE bool
GetElementOperation(JSContext * cx,JSOp op,MutableHandleValue lref,HandleValue rref,MutableHandleValue res)533 GetElementOperation(JSContext* cx, JSOp op, MutableHandleValue lref, HandleValue rref,
534                     MutableHandleValue res)
535 {
536     MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM);
537 
538     uint32_t index;
539     if (lref.isString() && IsDefinitelyIndex(rref, &index)) {
540         JSString* str = lref.toString();
541         if (index < str->length()) {
542             str = cx->staticStrings().getUnitStringForElement(cx, str, index);
543             if (!str)
544                 return false;
545             res.setString(str);
546             return true;
547         }
548     }
549 
550     if (lref.isPrimitive()) {
551         RootedValue thisv(cx, lref);
552         return GetPrimitiveElementOperation(cx, op, thisv, rref, res);
553     }
554 
555     RootedObject thisv(cx, &lref.toObject());
556     return GetObjectElementOperation(cx, op, thisv, thisv, rref, res);
557 }
558 
559 static MOZ_ALWAYS_INLINE JSString*
TypeOfOperation(const Value & v,JSRuntime * rt)560 TypeOfOperation(const Value& v, JSRuntime* rt)
561 {
562     JSType type = js::TypeOfValue(v);
563     return TypeName(type, *rt->commonNames);
564 }
565 
566 static inline JSString*
TypeOfObjectOperation(JSObject * obj,JSRuntime * rt)567 TypeOfObjectOperation(JSObject* obj, JSRuntime* rt)
568 {
569     JSType type = js::TypeOfObject(obj);
570     return TypeName(type, *rt->commonNames);
571 }
572 
573 static MOZ_ALWAYS_INLINE bool
InitElemOperation(JSContext * cx,jsbytecode * pc,HandleObject obj,HandleValue idval,HandleValue val)574 InitElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleValue idval, HandleValue val)
575 {
576     MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE));
577     MOZ_ASSERT(!obj->getClass()->getProperty);
578     MOZ_ASSERT(!obj->getClass()->setProperty);
579 
580     RootedId id(cx);
581     if (!ToPropertyKey(cx, idval, &id))
582         return false;
583 
584     unsigned flags = JSOp(*pc) == JSOP_INITHIDDENELEM ? 0 : JSPROP_ENUMERATE;
585     return DefineProperty(cx, obj, id, val, nullptr, nullptr, flags);
586 }
587 
588 static MOZ_ALWAYS_INLINE bool
InitArrayElemOperation(JSContext * cx,jsbytecode * pc,HandleObject obj,uint32_t index,HandleValue val)589 InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t index, HandleValue val)
590 {
591     JSOp op = JSOp(*pc);
592     MOZ_ASSERT(op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC);
593 
594     MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
595 
596     if (op == JSOP_INITELEM_INC && index == INT32_MAX) {
597         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SPREAD_TOO_LARGE);
598         return false;
599     }
600 
601     /*
602      * If val is a hole, do not call DefineElement.
603      *
604      * Furthermore, if the current op is JSOP_INITELEM_INC, always call
605      * SetLengthProperty even if it is not the last element initialiser,
606      * because it may be followed by JSOP_SPREAD, which will not set the array
607      * length if nothing is spread.
608      *
609      * Alternatively, if the current op is JSOP_INITELEM_ARRAY, the length will
610      * have already been set by the earlier JSOP_NEWARRAY; JSOP_INITELEM_ARRAY
611      * cannot follow JSOP_SPREAD.
612      */
613     if (val.isMagic(JS_ELEMENTS_HOLE)) {
614         if (op == JSOP_INITELEM_INC) {
615             if (!SetLengthProperty(cx, obj, index + 1))
616                 return false;
617         }
618     } else {
619         if (!DefineElement(cx, obj, index, val, nullptr, nullptr, JSPROP_ENUMERATE))
620             return false;
621     }
622 
623     return true;
624 }
625 
626 static MOZ_ALWAYS_INLINE bool
ProcessCallSiteObjOperation(JSContext * cx,RootedObject & cso,RootedObject & raw,RootedValue & rawValue)627 ProcessCallSiteObjOperation(JSContext* cx, RootedObject& cso, RootedObject& raw,
628                             RootedValue& rawValue)
629 {
630     bool extensible;
631     if (!IsExtensible(cx, cso, &extensible))
632         return false;
633     if (extensible) {
634         JSAtom* name = cx->names().raw;
635         if (!DefineProperty(cx, cso, name->asPropertyName(), rawValue, nullptr, nullptr, 0))
636             return false;
637         if (!FreezeObject(cx, raw))
638             return false;
639         if (!FreezeObject(cx, cso))
640             return false;
641     }
642     return true;
643 }
644 
645 #define RELATIONAL_OP(OP)                                                     \
646     JS_BEGIN_MACRO                                                            \
647         /* Optimize for two int-tagged operands (typical loop control). */    \
648         if (lhs.isInt32() && rhs.isInt32()) {                                 \
649             *res = lhs.toInt32() OP rhs.toInt32();                            \
650         } else {                                                              \
651             if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs))                         \
652                 return false;                                                 \
653             if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs))                         \
654                 return false;                                                 \
655             if (lhs.isString() && rhs.isString()) {                           \
656                 JSString* l = lhs.toString();                                 \
657                 JSString* r = rhs.toString();                                 \
658                 int32_t result;                                               \
659                 if (!CompareStrings(cx, l, r, &result))                       \
660                     return false;                                             \
661                 *res = result OP 0;                                           \
662             } else {                                                          \
663                 double l, r;                                                  \
664                 if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r))         \
665                     return false;                                             \
666                 *res = (l OP r);                                              \
667             }                                                                 \
668         }                                                                     \
669         return true;                                                          \
670     JS_END_MACRO
671 
672 static MOZ_ALWAYS_INLINE bool
LessThanOperation(JSContext * cx,MutableHandleValue lhs,MutableHandleValue rhs,bool * res)673 LessThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) {
674     RELATIONAL_OP(<);
675 }
676 
677 static MOZ_ALWAYS_INLINE bool
LessThanOrEqualOperation(JSContext * cx,MutableHandleValue lhs,MutableHandleValue rhs,bool * res)678 LessThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) {
679     RELATIONAL_OP(<=);
680 }
681 
682 static MOZ_ALWAYS_INLINE bool
GreaterThanOperation(JSContext * cx,MutableHandleValue lhs,MutableHandleValue rhs,bool * res)683 GreaterThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) {
684     RELATIONAL_OP(>);
685 }
686 
687 static MOZ_ALWAYS_INLINE bool
GreaterThanOrEqualOperation(JSContext * cx,MutableHandleValue lhs,MutableHandleValue rhs,bool * res)688 GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) {
689     RELATIONAL_OP(>=);
690 }
691 
692 static MOZ_ALWAYS_INLINE bool
BitNot(JSContext * cx,HandleValue in,int * out)693 BitNot(JSContext* cx, HandleValue in, int* out)
694 {
695     int i;
696     if (!ToInt32(cx, in, &i))
697         return false;
698     *out = ~i;
699     return true;
700 }
701 
702 static MOZ_ALWAYS_INLINE bool
BitXor(JSContext * cx,HandleValue lhs,HandleValue rhs,int * out)703 BitXor(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
704 {
705     int left, right;
706     if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
707         return false;
708     *out = left ^ right;
709     return true;
710 }
711 
712 static MOZ_ALWAYS_INLINE bool
BitOr(JSContext * cx,HandleValue lhs,HandleValue rhs,int * out)713 BitOr(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
714 {
715     int left, right;
716     if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
717         return false;
718     *out = left | right;
719     return true;
720 }
721 
722 static MOZ_ALWAYS_INLINE bool
BitAnd(JSContext * cx,HandleValue lhs,HandleValue rhs,int * out)723 BitAnd(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
724 {
725     int left, right;
726     if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
727         return false;
728     *out = left & right;
729     return true;
730 }
731 
732 static MOZ_ALWAYS_INLINE bool
BitLsh(JSContext * cx,HandleValue lhs,HandleValue rhs,int * out)733 BitLsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
734 {
735     int32_t left, right;
736     if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
737         return false;
738     *out = uint32_t(left) << (right & 31);
739     return true;
740 }
741 
742 static MOZ_ALWAYS_INLINE bool
BitRsh(JSContext * cx,HandleValue lhs,HandleValue rhs,int * out)743 BitRsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out)
744 {
745     int32_t left, right;
746     if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
747         return false;
748     *out = left >> (right & 31);
749     return true;
750 }
751 
752 static MOZ_ALWAYS_INLINE bool
UrshOperation(JSContext * cx,HandleValue lhs,HandleValue rhs,MutableHandleValue out)753 UrshOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out)
754 {
755     uint32_t left;
756     int32_t  right;
757     if (!ToUint32(cx, lhs, &left) || !ToInt32(cx, rhs, &right))
758         return false;
759     left >>= right & 31;
760     out.setNumber(uint32_t(left));
761     return true;
762 }
763 
764 #undef RELATIONAL_OP
765 
766 inline JSFunction*
767 ReportIfNotFunction(JSContext* cx, HandleValue v, MaybeConstruct construct = NO_CONSTRUCT)
768 {
769     if (v.isObject() && v.toObject().is<JSFunction>())
770         return &v.toObject().as<JSFunction>();
771 
772     ReportIsNotFunction(cx, v, -1, construct);
773     return nullptr;
774 }
775 
776 /*
777  * FastInvokeGuard is used to optimize calls to JS functions from natives written
778  * in C++, for instance Array.map. If the callee is not Ion-compiled, this will
779  * just call Invoke. If the callee has a valid IonScript, however, it will enter
780  * Ion directly.
781  */
782 class FastInvokeGuard
783 {
784     InvokeArgs args_;
785     RootedFunction fun_;
786     RootedScript script_;
787 
788     // Constructing a JitContext is pretty expensive due to the TLS access,
789     // so only do this if we have to.
790     bool useIon_;
791 
792   public:
FastInvokeGuard(JSContext * cx,const Value & fval)793     FastInvokeGuard(JSContext* cx, const Value& fval)
794       : args_(cx)
795       , fun_(cx)
796       , script_(cx)
797       , useIon_(jit::IsIonEnabled(cx))
798     {
799         initFunction(fval);
800     }
801 
initFunction(const Value & fval)802     void initFunction(const Value& fval) {
803         if (fval.isObject() && fval.toObject().is<JSFunction>()) {
804             JSFunction* fun = &fval.toObject().as<JSFunction>();
805             if (fun->isInterpreted())
806                 fun_ = fun;
807         }
808     }
809 
args()810     InvokeArgs& args() {
811         return args_;
812     }
813 
invoke(JSContext * cx)814     bool invoke(JSContext* cx) {
815         if (useIon_ && fun_) {
816             if (!script_) {
817                 script_ = fun_->getOrCreateScript(cx);
818                 if (!script_)
819                     return false;
820             }
821             MOZ_ASSERT(fun_->nonLazyScript() == script_);
822 
823             jit::MethodStatus status = jit::CanEnterUsingFastInvoke(cx, script_, args_.length());
824             if (status == jit::Method_Error)
825                 return false;
826             if (status == jit::Method_Compiled) {
827                 jit::JitExecStatus result = jit::FastInvoke(cx, fun_, args_);
828                 if (IsErrorStatus(result))
829                     return false;
830 
831                 MOZ_ASSERT(result == jit::JitExec_Ok);
832                 return true;
833             }
834 
835             MOZ_ASSERT(status == jit::Method_Skipped);
836 
837             if (script_->canIonCompile()) {
838                 // This script is not yet hot. Since calling into Ion is much
839                 // faster here, bump the warm-up counter a bit to account for this.
840                 script_->incWarmUpCounter(5);
841             }
842         }
843 
844         return Invoke(cx, args_);
845     }
846 
847   private:
848     FastInvokeGuard(const FastInvokeGuard& other) = delete;
849     const FastInvokeGuard& operator=(const FastInvokeGuard& other) = delete;
850 };
851 
852 }  /* namespace js */
853 
854 #endif /* vm_Interpreter_inl_h */
855