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