Issue #2241 - Part 1: Move {js::,JS_}{{Strictly,Loosely}Equal,SameValue} into their own header and implementation files

Backported from Mozilla bug 1516742.

The .from* methods are going to depend on SameValueZero, which needs to be
visible to /dom. This patch provides the foundation for that.
This commit is contained in:
Job Bautista 2023-05-10 19:10:41 +08:00 committed by roytam1
commit ec2fb72568
17 changed files with 338 additions and 223 deletions

55
js/public/Equality.h Normal file
View file

@ -0,0 +1,55 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Equality operations. */
#ifndef js_Equality_h
#define js_Equality_h
#include "jstypes.h" // JS_PUBLIC_API
#include "js/RootingAPI.h" // JS::Handle
#include "js/Value.h" // JS::Value
struct JSContext;
namespace JS {
/**
* Store |v1 === v2| to |*equal| -- strict equality, which performs no
* conversions on |v1| or |v2| before comparing.
*
* This operation can fail only if an internal error occurs (e.g. OOM while
* linearizing a string value).
*/
extern JS_PUBLIC_API(bool)
StrictlyEqual(JSContext* cx, JS::Handle<JS::Value> v1, JS::Handle<JS::Value> v2, bool* equal);
/**
* Store |v1 == v2| to |*equal| -- loose equality, which may perform
* user-modifiable conversions on |v1| or |v2|.
*
* This operation can fail if a user-modifiable conversion fails *or* if an
* internal error occurs. (e.g. OOM while linearizing a string value).
*/
extern JS_PUBLIC_API(bool)
LooselyEqual(JSContext* cx, JS::Handle<JS::Value> v1, JS::Handle<JS::Value> v2, bool* equal);
/**
* Stores |SameValue(v1, v2)| to |*equal| -- using the SameValue operation
* defined in ECMAScript, initially exposed to script as |Object.is|. SameValue
* behaves identically to strict equality, except that it equates two NaN values
* and does not equate differently-signed zeroes. It performs no conversions on
* |v1| or |v2| before comparing.
*
* This operation can fail only if an internal error occurs (e.g. OOM while
* linearizing a string value).
*/
extern JS_PUBLIC_API(bool)
SameValue(JSContext* cx, JS::Handle<JS::Value> v1, JS::Handle<JS::Value> v2, bool* same);
} // namespace JS
#endif /* js_Equality_h */

View file

@ -12,6 +12,7 @@
#include "ds/OrderedHashTable.h"
#include "gc/Marking.h"
#include "js/Utility.h"
#include "vm/EqualityOperations.h" // js::SameValue
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/SelfHosting.h"

View file

@ -15,6 +15,7 @@
#include "jit/InlinableNatives.h"
#include "js/UniquePtr.h"
#include "vm/AsyncFunction.h"
#include "vm/EqualityOperations.h" // js::SameValue
#include "vm/StringBuffer.h"
#include "jsobjinlines.h"

View file

@ -17,6 +17,7 @@
#include "jit/mips64/Simulator-mips64.h"
#include "vm/ArrayObject.h"
#include "vm/Debugger.h"
#include "vm/EqualityOperations.h" // js::StrictlyEqual
#include "vm/Interpreter.h"
#include "vm/TraceLogging.h"

View file

@ -5,6 +5,8 @@
#include <limits>
#include <math.h>
#include "js/Equality.h" // JS::LooselyEqual
#include "jsapi-tests/tests.h"
using namespace std;
@ -15,15 +17,15 @@ struct LooseEqualityFixture : public JSAPITest
bool leq(JS::HandleValue x, JS::HandleValue y) {
bool equal;
CHECK(JS_LooselyEqual(cx, x, y, &equal) && equal);
CHECK(JS_LooselyEqual(cx, y, x, &equal) && equal);
CHECK(JS::LooselyEqual(cx, x, y, &equal) && equal);
CHECK(JS::LooselyEqual(cx, y, x, &equal) && equal);
return true;
}
bool nleq(JS::HandleValue x, JS::HandleValue y) {
bool equal;
CHECK(JS_LooselyEqual(cx, x, y, &equal) && !equal);
CHECK(JS_LooselyEqual(cx, y, x, &equal) && !equal);
CHECK(JS::LooselyEqual(cx, x, y, &equal) && !equal);
CHECK(JS::LooselyEqual(cx, y, x, &equal) && !equal);
return true;
}
};

View file

@ -4,6 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "js/Equality.h" // JS::SameValue
#include "jsapi-tests/tests.h"
BEGIN_TEST(testSameValue)
@ -11,7 +12,7 @@ BEGIN_TEST(testSameValue)
/*
* NB: passing a double that fits in an integer jsval is API misuse. As a
* matter of defense in depth, however, JS_SameValue should return the
* matter of defense in depth, however, JS::SameValue should return the
* correct result comparing a positive-zero double to a negative-zero
* double, and this is believed to be the only way to make such a
* comparison possible.
@ -19,7 +20,7 @@ BEGIN_TEST(testSameValue)
JS::RootedValue v1(cx, JS::DoubleValue(0.0));
JS::RootedValue v2(cx, JS::DoubleValue(-0.0));
bool same;
CHECK(JS_SameValue(cx, v1, v2, &same));
CHECK(JS::SameValue(cx, v1, v2, &same));
CHECK(!same);
return true;
}

View file

@ -18,6 +18,7 @@
#include "jscntxt.h"
#include "jsgc.h"
#include "js/Equality.h" // JS::SameValue
#include "js/Vector.h"
/* Note: Aborts on OOM. */
@ -201,9 +202,9 @@ class JSAPITest
const char* filename, int lineno) {
bool same;
JS::RootedValue actual(cx, actualArg), expected(cx, expectedArg);
return (JS_SameValue(cx, actual, expected, &same) && same) ||
fail(JSAPITestString("CHECK_SAME failed: expected JS_SameValue(cx, ") +
actualExpr + ", " + expectedExpr + "), got !JS_SameValue(cx, " +
return (JS::SameValue(cx, actual, expected, &same) && same) ||
fail(JSAPITestString("CHECK_SAME failed: expected JS::SameValue(cx, ") +
actualExpr + ", " + expectedExpr + "), got !JS::SameValue(cx, " +
jsvalToSource(actual) + ", " + jsvalToSource(expected) + ")", filename, lineno);
}

View file

@ -391,36 +391,6 @@ JS_TypeOfValue(JSContext* cx, HandleValue value)
return TypeOfValue(value);
}
JS_PUBLIC_API(bool)
JS_StrictlyEqual(JSContext* cx, HandleValue value1, HandleValue value2, bool* equal)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, value1, value2);
MOZ_ASSERT(equal);
return StrictlyEqual(cx, value1, value2, equal);
}
JS_PUBLIC_API(bool)
JS_LooselyEqual(JSContext* cx, HandleValue value1, HandleValue value2, bool* equal)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, value1, value2);
MOZ_ASSERT(equal);
return LooselyEqual(cx, value1, value2, equal);
}
JS_PUBLIC_API(bool)
JS_SameValue(JSContext* cx, HandleValue value1, HandleValue value2, bool* same)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, value1, value2);
MOZ_ASSERT(same);
return SameValue(cx, value1, value2, same);
}
JS_PUBLIC_API(bool)
JS_IsBuiltinEvalFunction(JSFunction* fun)
{

View file

@ -959,15 +959,6 @@ InformalValueTypeName(const JS::Value& v);
} /* namespace JS */
extern JS_PUBLIC_API(bool)
JS_StrictlyEqual(JSContext* cx, JS::Handle<JS::Value> v1, JS::Handle<JS::Value> v2, bool* equal);
extern JS_PUBLIC_API(bool)
JS_LooselyEqual(JSContext* cx, JS::Handle<JS::Value> v1, JS::Handle<JS::Value> v2, bool* equal);
extern JS_PUBLIC_API(bool)
JS_SameValue(JSContext* cx, JS::Handle<JS::Value> v1, JS::Handle<JS::Value> v2, bool* same);
/** True iff fun is the global eval function. */
extern JS_PUBLIC_API(bool)
JS_IsBuiltinEvalFunction(JSFunction* fun);

View file

@ -70,6 +70,7 @@ EXPORTS.js += [
'../public/Conversions.h',
'../public/Date.h',
'../public/Debug.h',
'../public/Equality.h',
'../public/GCAnnotations.h',
'../public/GCAPI.h',
'../public/GCHashTable.h',
@ -293,6 +294,7 @@ main_deunified_sources = [
'vm/Debugger.cpp',
'vm/DebuggerMemory.cpp',
'vm/EnvironmentObject.cpp',
'vm/EqualityOperations.cpp',
'vm/ErrorObject.cpp',
'vm/ForOfIterator.cpp',
'vm/GeneratorObject.cpp',

View file

@ -7,6 +7,8 @@
#include "jsapi.h"
#include "vm/EqualityOperations.h" // js::SameValue
#include "jsobjinlines.h"
#include "vm/NativeObject-inl.h"

View file

@ -71,6 +71,7 @@
#include "jit/JitcodeMap.h"
#include "jit/OptimizationTracking.h"
#include "js/Debug.h"
#include "js/Equality.h" // JS::SameValue
#include "js/GCAPI.h"
#include "js/Initialization.h"
#include "js/StructuredClone.h"
@ -2286,7 +2287,7 @@ AssertEq(JSContext* cx, unsigned argc, Value* vp)
}
bool same;
if (!JS_SameValue(cx, args[0], args[1], &same))
if (!JS::SameValue(cx, args[0], args[1], &same))
return false;
if (!same) {
JSAutoByteString bytes0, bytes1;

View file

@ -0,0 +1,215 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "vm/EqualityOperations.h" // js::LooselyEqual, js::StrictlyEqual, js::SameValue
#include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_ASSERT_IF
#include "jsapi.h" // js::AssertHeapIsIdle, CHECK_REQUEST
#include "jsnum.h" // js::StringToNumber
#include "jsobj.h" // js::ToPrimitive
#include "jsstr.h" // js::EqualStrings
#include "jstypes.h" // JS_PUBLIC_API
#include "js/Equality.h" // JS::LooselyEqual, JS::StrictlyEqual, JS::SameValue
#include "js/RootingAPI.h" // JS::Rooted, JS::Handle
#include "js/Value.h" // JS::Int32Value, JS::SameType, JS::Value
#include "jsboolinlines.h" // js::EmulatesUndefined
#include "jscntxtinlines.h" // js::assertSameCompartment
static inline bool
EqualGivenSameType(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bool* equal)
{
MOZ_ASSERT(SameType(lval, rval));
if (lval.isString())
return EqualStrings(cx, lval.toString(), rval.toString(), equal);
if (lval.isDouble()) {
*equal = (lval.toDouble() == rval.toDouble());
return true;
}
if (lval.isGCThing()) { // objects or symbols
*equal = (lval.toGCThing() == rval.toGCThing());
return true;
}
*equal = lval.get().payloadAsRawUint32() == rval.get().payloadAsRawUint32();
MOZ_ASSERT_IF(lval.isUndefined() || lval.isNull(), *equal);
return true;
}
static inline bool
LooselyEqualBooleanAndOther(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bool* result)
{
MOZ_ASSERT(!rval.isBoolean());
JS::RootedValue lvalue(cx, JS::Int32Value(lval.toBoolean() ? 1 : 0));
// The tail-call would end up in Step 3.
if (rval.isNumber()) {
*result = (lvalue.toNumber() == rval.toNumber());
return true;
}
// The tail-call would end up in Step 6.
if (rval.isString()) {
double num;
if (!StringToNumber(cx, rval.toString(), &num))
return false;
*result = (lvalue.toNumber() == num);
return true;
}
return js::LooselyEqual(cx, lvalue, rval, result);
}
// ES6 draft rev32 7.2.12 Abstract Equality Comparison
bool
js::LooselyEqual(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bool* result)
{
// Step 3.
if (SameType(lval, rval))
return EqualGivenSameType(cx, lval, rval, result);
// Handle int32 x double.
if (lval.isNumber() && rval.isNumber()) {
*result = (lval.toNumber() == rval.toNumber());
return true;
}
// Step 4. This a bit more complex, because of the undefined emulating object.
if (lval.isNullOrUndefined()) {
// We can return early here, because null | undefined is only equal to the same set.
*result = rval.isNullOrUndefined() ||
(rval.isObject() && EmulatesUndefined(&rval.toObject()));
return true;
}
// Step 5.
if (rval.isNullOrUndefined()) {
MOZ_ASSERT(!lval.isNullOrUndefined());
*result = lval.isObject() && EmulatesUndefined(&lval.toObject());
return true;
}
// Step 6.
if (lval.isNumber() && rval.isString()) {
double num;
if (!StringToNumber(cx, rval.toString(), &num))
return false;
*result = (lval.toNumber() == num);
return true;
}
// Step 7.
if (lval.isString() && rval.isNumber()) {
double num;
if (!StringToNumber(cx, lval.toString(), &num))
return false;
*result = (num == rval.toNumber());
return true;
}
// Step 8.
if (lval.isBoolean())
return LooselyEqualBooleanAndOther(cx, lval, rval, result);
// Step 9.
if (rval.isBoolean())
return LooselyEqualBooleanAndOther(cx, rval, lval, result);
// Step 10.
if ((lval.isString() || lval.isNumber() || lval.isSymbol()) && rval.isObject()) {
JS::RootedValue rvalue(cx, rval);
if (!ToPrimitive(cx, &rvalue))
return false;
return js::LooselyEqual(cx, lval, rvalue, result);
}
// Step 11.
if (lval.isObject() && (rval.isString() || rval.isNumber() || rval.isSymbol())) {
JS::RootedValue lvalue(cx, lval);
if (!ToPrimitive(cx, &lvalue))
return false;
return js::LooselyEqual(cx, lvalue, rval, result);
}
// Step 12.
*result = false;
return true;
}
JS_PUBLIC_API(bool)
JS::LooselyEqual(JSContext* cx, Handle<Value> value1, Handle<Value> value2, bool* equal)
{
js::AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
js::assertSameCompartment(cx, value1, value2);
MOZ_ASSERT(equal);
return js::LooselyEqual(cx, value1, value2, equal);
}
bool
js::StrictlyEqual(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bool* equal)
{
if (SameType(lval, rval))
return EqualGivenSameType(cx, lval, rval, equal);
if (lval.isNumber() && rval.isNumber()) {
*equal = (lval.toNumber() == rval.toNumber());
return true;
}
*equal = false;
return true;
}
JS_PUBLIC_API(bool)
JS::StrictlyEqual(JSContext* cx, Handle<Value> value1, Handle<Value> value2, bool* equal)
{
js::AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
js::assertSameCompartment(cx, value1, value2);
MOZ_ASSERT(equal);
return js::StrictlyEqual(cx, value1, value2, equal);
}
static inline bool
IsNegativeZero(const JS::Value& v)
{
return v.isDouble() && mozilla::IsNegativeZero(v.toDouble());
}
static inline bool
IsNaN(const JS::Value& v)
{
return v.isDouble() && mozilla::IsNaN(v.toDouble());
}
bool
js::SameValue(JSContext* cx, JS::HandleValue v1, JS::HandleValue v2, bool* same)
{
if (IsNegativeZero(v1)) {
*same = IsNegativeZero(v2);
return true;
}
if (IsNegativeZero(v2)) {
*same = false;
return true;
}
if (IsNaN(v1) && IsNaN(v2)) {
*same = true;
return true;
}
return js::StrictlyEqual(cx, v1, v2, same);
}
JS_PUBLIC_API(bool)
JS::SameValue(JSContext* cx, Handle<Value> value1, Handle<Value> value2, bool* same)
{
js::AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
js::assertSameCompartment(cx, value1, value2);
MOZ_ASSERT(same);
return js::SameValue(cx, value1, value2, same);
}

View file

@ -0,0 +1,44 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* The equality comparisons of js/Equality.h, but with extra efficiency for
* SpiderMonkey-internal callers.
*
* These functions, assuming they're passed C++-valid arguments, are identical
* to the same-named JS::-namespaced functions -- just with hidden linkage (so
* they're more efficient to call), and without various external-caller-focused
* JSAPI-usage assertions performed that SpiderMonkey users never come close to
* failing.
*/
#ifndef vm_EqualityOperations_h
#define vm_EqualityOperations_h
#include "js/RootingAPI.h" // JS::Handle
#include "js/Value.h" // JS::Value
struct JSContext;
namespace js {
/** Computes |lval === rval|. */
extern bool
StrictlyEqual(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bool* equal);
/** Computes |lval == rval|. */
extern bool
LooselyEqual(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bool* result);
/**
* Computes |SameValue(v1, v2)| -- strict equality except that NaNs are
* considered equal and opposite-signed zeroes are considered unequal.
*/
extern bool
SameValue(JSContext* cx, JS::HandleValue v1, JS::HandleValue v2, bool* same);
} // namespace js
#endif // vm_EqualityOperations_h

View file

@ -41,6 +41,7 @@
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/Debugger.h"
#include "vm/EqualityOperations.h" // js::StrictlyEqual
#include "vm/GeneratorObject.h"
#include "vm/Opcodes.h"
#include "vm/Scope.h"
@ -786,170 +787,6 @@ js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp)
return JS::InstanceofOperator(cx, obj, local, bp);
}
static inline bool
EqualGivenSameType(JSContext* cx, HandleValue lval, HandleValue rval, bool* equal)
{
MOZ_ASSERT(SameType(lval, rval));
if (lval.isString())
return EqualStrings(cx, lval.toString(), rval.toString(), equal);
if (lval.isDouble()) {
*equal = (lval.toDouble() == rval.toDouble());
return true;
}
if (lval.isGCThing()) { // objects or symbols
*equal = (lval.toGCThing() == rval.toGCThing());
return true;
}
*equal = lval.get().payloadAsRawUint32() == rval.get().payloadAsRawUint32();
MOZ_ASSERT_IF(lval.isUndefined() || lval.isNull(), *equal);
return true;
}
static inline bool
LooselyEqualBooleanAndOther(JSContext* cx, HandleValue lval, HandleValue rval, bool* result)
{
MOZ_ASSERT(!rval.isBoolean());
RootedValue lvalue(cx, Int32Value(lval.toBoolean() ? 1 : 0));
// The tail-call would end up in Step 3.
if (rval.isNumber()) {
*result = (lvalue.toNumber() == rval.toNumber());
return true;
}
// The tail-call would end up in Step 6.
if (rval.isString()) {
double num;
if (!StringToNumber(cx, rval.toString(), &num))
return false;
*result = (lvalue.toNumber() == num);
return true;
}
return LooselyEqual(cx, lvalue, rval, result);
}
// ES6 draft rev32 7.2.12 Abstract Equality Comparison
bool
js::LooselyEqual(JSContext* cx, HandleValue lval, HandleValue rval, bool* result)
{
// Step 3.
if (SameType(lval, rval))
return EqualGivenSameType(cx, lval, rval, result);
// Handle int32 x double.
if (lval.isNumber() && rval.isNumber()) {
*result = (lval.toNumber() == rval.toNumber());
return true;
}
// Step 4. This a bit more complex, because of the undefined emulating object.
if (lval.isNullOrUndefined()) {
// We can return early here, because null | undefined is only equal to the same set.
*result = rval.isNullOrUndefined() ||
(rval.isObject() && EmulatesUndefined(&rval.toObject()));
return true;
}
// Step 5.
if (rval.isNullOrUndefined()) {
MOZ_ASSERT(!lval.isNullOrUndefined());
*result = lval.isObject() && EmulatesUndefined(&lval.toObject());
return true;
}
// Step 6.
if (lval.isNumber() && rval.isString()) {
double num;
if (!StringToNumber(cx, rval.toString(), &num))
return false;
*result = (lval.toNumber() == num);
return true;
}
// Step 7.
if (lval.isString() && rval.isNumber()) {
double num;
if (!StringToNumber(cx, lval.toString(), &num))
return false;
*result = (num == rval.toNumber());
return true;
}
// Step 8.
if (lval.isBoolean())
return LooselyEqualBooleanAndOther(cx, lval, rval, result);
// Step 9.
if (rval.isBoolean())
return LooselyEqualBooleanAndOther(cx, rval, lval, result);
// Step 10.
if ((lval.isString() || lval.isNumber() || lval.isSymbol()) && rval.isObject()) {
RootedValue rvalue(cx, rval);
if (!ToPrimitive(cx, &rvalue))
return false;
return LooselyEqual(cx, lval, rvalue, result);
}
// Step 11.
if (lval.isObject() && (rval.isString() || rval.isNumber() || rval.isSymbol())) {
RootedValue lvalue(cx, lval);
if (!ToPrimitive(cx, &lvalue))
return false;
return LooselyEqual(cx, lvalue, rval, result);
}
// Step 12.
*result = false;
return true;
}
bool
js::StrictlyEqual(JSContext* cx, HandleValue lval, HandleValue rval, bool* equal)
{
if (SameType(lval, rval))
return EqualGivenSameType(cx, lval, rval, equal);
if (lval.isNumber() && rval.isNumber()) {
*equal = (lval.toNumber() == rval.toNumber());
return true;
}
*equal = false;
return true;
}
static inline bool
IsNegativeZero(const Value& v)
{
return v.isDouble() && mozilla::IsNegativeZero(v.toDouble());
}
static inline bool
IsNaN(const Value& v)
{
return v.isDouble() && mozilla::IsNaN(v.toDouble());
}
bool
js::SameValue(JSContext* cx, HandleValue v1, HandleValue v2, bool* same)
{
if (IsNegativeZero(v1)) {
*same = IsNegativeZero(v2);
return true;
}
if (IsNegativeZero(v2)) {
*same = false;
return true;
}
if (IsNaN(v1) && IsNaN(v2)) {
*same = true;
return true;
}
return StrictlyEqual(cx, v1, v2, same);
}
JSType
js::TypeOfObject(JSObject* obj)
{

View file

@ -305,16 +305,6 @@ class InvokeState final : public RunState
extern bool
RunScript(JSContext* cx, RunState& state);
extern bool
StrictlyEqual(JSContext* cx, HandleValue lval, HandleValue rval, bool* equal);
extern bool
LooselyEqual(JSContext* cx, HandleValue lval, HandleValue rval, bool* equal);
/* === except that NaN is the same as NaN and -0 is not the same as +0. */
extern bool
SameValue(JSContext* cx, HandleValue v1, HandleValue v2, bool* same);
extern JSType
TypeOfObject(JSObject* obj);

View file

@ -11,6 +11,7 @@
#include "gc/Marking.h"
#include "js/Value.h"
#include "vm/Debugger.h"
#include "vm/EqualityOperations.h" // js::SameValue
#include "vm/TypedArrayCommon.h"
#include "jsobjinlines.h"