unittests/VMRuntime/JSLibTest.cpp (1,039 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TestHelpers.h"
#include "hermes/BCGen/HBC/BytecodeGenerator.h"
#include "hermes/VM/JSDate.h"
#include "hermes/VM/JSLib/RuntimeCommonStorage.h"
#include "hermes/VM/JSLib/Sorting.h"
#include "hermes/VM/MockedEnvironment.h"
#include "hermes/VM/PropertyAccessor.h"
#include "hermes/VM/SmallXString.h"
#include <algorithm>
#include <random>
#include <string>
#include <vector>
using namespace hermes::vm;
using namespace hermes::hbc;
namespace {
using JSLibTest = RuntimeTestFixture;
TEST_F(JSLibTest, globalObjectConstTest) {
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
GET_GLOBAL(NaN);
EXPECT_TRUE(isSameValue(
propRes->get(),
HermesValue::encodeDoubleValue(
std::numeric_limits<double>::quiet_NaN())));
GET_GLOBAL(Infinity);
EXPECT_TRUE(isSameValue(
propRes->get(),
HermesValue::encodeDoubleValue(std::numeric_limits<double>::infinity())));
GET_GLOBAL(undefined);
EXPECT_TRUE(isSameValue(propRes->get(), HermesValue::encodeUndefinedValue()));
}
TEST_F(JSLibTest, CreateObjectTest) {
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
// Object constructor.
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<Callable>(std::move(*propRes));
// Object.prototype.
GET_VALUE(objectCons, prototype);
auto prototype = runtime.makeHandle<JSObject>(std::move(*propRes));
// create a new instance.
auto crtRes = objectCons->newObject(objectCons, runtime, prototype);
ASSERT_RETURNED(crtRes.getStatus());
auto newObj = runtime.makeHandle<JSObject>(std::move(*crtRes));
// Make sure the prototype is correct.
ASSERT_EQ(prototype.get(), newObj->getParent(runtime));
// Call the constructor.
auto callRes = Callable::executeCall0(objectCons, runtime, newObj, true);
ASSERT_RETURNED(callRes.getStatus());
auto newObj1 = runtime.makeHandle<JSObject>(std::move(*callRes));
ASSERT_EQ(newObj, newObj1);
}
static Handle<JSObject> createObject(Runtime &runtime) {
// Object constructor.
auto propRes = JSObject::getNamed_RJS(
runtime.getGlobal(),
runtime,
Predefined::getSymbolID(Predefined::Object));
assert(propRes == ExecutionStatus::RETURNED);
auto objectCons = runtime.makeHandle<Callable>(std::move(*propRes));
// Object.prototype.
propRes = JSObject::getNamed_RJS(
objectCons, runtime, Predefined::getSymbolID(Predefined::prototype));
assert(propRes == ExecutionStatus::RETURNED);
auto prototype = runtime.makeHandle<JSObject>(std::move(*propRes));
// create a new instance.
auto crtRes = objectCons->newObject(objectCons, runtime, prototype);
assert(crtRes == ExecutionStatus::RETURNED);
auto newObj = runtime.makeHandle<JSObject>(std::move(*crtRes));
// Call the constructor.
auto callRes = Callable::executeCall0(objectCons, runtime, newObj, true);
assert(callRes == ExecutionStatus::RETURNED);
return (*callRes)->isUndefined()
? newObj
: runtime.makeHandle<JSObject>(std::move(*callRes));
}
TEST_F(JSLibTest, ObjectToStringTest) {
// Check that "(new Object()).toString() is "[object Object]".
auto obj = createObject(runtime);
auto propRes = JSObject::getNamed_RJS(
obj, runtime, Predefined::getSymbolID(Predefined::toString));
ASSERT_RETURNED(propRes.getStatus());
auto toStringFn = runtime.makeHandle<Callable>(std::move(*propRes));
EXPECT_CALLRESULT_STRING(
"[object Object]", toStringFn->executeCall0(toStringFn, runtime, obj));
// Check that Object.prototype.toString.call(10) is "[object Number]".
EXPECT_CALLRESULT_STRING(
"[object Number]",
toStringFn->executeCall0(
toStringFn,
runtime,
runtime.makeHandle(HermesValue::encodeDoubleValue(10))));
// Check that toStringFn.call(toStringFn) is "[object Function]".
EXPECT_CALLRESULT_STRING(
"[object Function]",
toStringFn->executeCall0(toStringFn, runtime, toStringFn));
// Check that Operations/toString does the right thing.
EXPECT_STRINGPRIM(
"[object Object]", toString_RJS(runtime, obj)->getHermesValue());
}
TEST_F(JSLibTest, ObjectSealTest) {
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
auto obj = createObject(runtime);
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<JSObject>((std::move(*propRes)));
ASSERT_RETURNED(
(propRes = JSObject::getNamed_RJS(
objectCons, runtime, Predefined::getSymbolID(Predefined::seal)))
.getStatus());
auto sealFn = runtime.makeHandle<Callable>((std::move(*propRes)));
ASSERT_RETURNED(
(propRes = JSObject::getNamed_RJS(
objectCons, runtime, Predefined::getSymbolID(Predefined::isSealed)))
.getStatus());
auto isSealedFn = runtime.makeHandle<Callable>((std::move(*propRes)));
// Create a property "obj.prop1".
auto prop1ID = *runtime.getIdentifierTable().getSymbolHandle(
runtime, createUTF16Ref(u"prop1"));
ASSERT_TRUE(
JSObject::putNamed_RJS(
obj,
runtime,
*prop1ID,
runtime.makeHandle(HermesValue::encodeDoubleValue(10))) !=
ExecutionStatus::EXCEPTION);
// Make sure it is configurable.
NamedPropertyDescriptor desc;
ASSERT_TRUE(JSObject::getNamedDescriptorUnsafe(obj, runtime, *prop1ID, desc));
ASSERT_TRUE(desc.flags.configurable);
// Make sure it's not sealed.
EXPECT_CALLRESULT_BOOL(
FALSE,
isSealedFn->executeCall1(
isSealedFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue()));
// obj.seal().
ASSERT_RETURNED(
sealFn
->executeCall1(
sealFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue())
.getStatus());
// Make sure it is no longer configurable.
ASSERT_TRUE(JSObject::getNamedDescriptorUnsafe(obj, runtime, *prop1ID, desc));
ASSERT_FALSE(desc.flags.configurable);
// Try to delete it.
auto res = JSObject::deleteNamed(obj, runtime, *prop1ID);
ASSERT_FALSE(*res);
// Make sure isSealed works.
EXPECT_CALLRESULT_BOOL(
TRUE,
isSealedFn->executeCall1(
isSealedFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue()));
}
TEST_F(JSLibTest, ObjectFreezeTest) {
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
auto obj = createObject(runtime);
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<JSObject>(std::move(*propRes));
ASSERT_RETURNED(
(propRes = JSObject::getNamed_RJS(
objectCons, runtime, Predefined::getSymbolID(Predefined::freeze)))
.getStatus());
auto freezeFn = runtime.makeHandle<Callable>(std::move(*propRes));
ASSERT_RETURNED(
(propRes = JSObject::getNamed_RJS(
objectCons, runtime, Predefined::getSymbolID(Predefined::isFrozen)))
.getStatus());
auto isFrozenFn = runtime.makeHandle<Callable>(std::move(*propRes));
// Create a property "obj.prop1".
auto prop1ID = *runtime.getIdentifierTable().getSymbolHandle(
runtime, createUTF16Ref(u"prop1"));
ASSERT_TRUE(
JSObject::putNamed_RJS(
obj,
runtime,
*prop1ID,
runtime.makeHandle(HermesValue::encodeDoubleValue(10))) !=
ExecutionStatus::EXCEPTION);
// Make sure it is configurable.
NamedPropertyDescriptor desc;
ASSERT_TRUE(JSObject::getNamedDescriptorUnsafe(obj, runtime, *prop1ID, desc));
ASSERT_TRUE(desc.flags.configurable);
ASSERT_TRUE(desc.flags.writable);
// Make sure it's not frozen.
EXPECT_CALLRESULT_BOOL(
FALSE,
isFrozenFn->executeCall1(
isFrozenFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue()));
// obj.freeze().
ASSERT_RETURNED(
freezeFn
->executeCall1(
freezeFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue())
.getStatus());
// Make sure it is no longer configurable or writable.
ASSERT_TRUE(JSObject::getNamedDescriptorUnsafe(obj, runtime, *prop1ID, desc));
ASSERT_FALSE(desc.flags.configurable);
ASSERT_FALSE(desc.flags.writable);
// Try to delete it.
auto res = JSObject::deleteNamed(obj, runtime, *prop1ID);
ASSERT_FALSE(*res);
// Make sure isFrozen works.
EXPECT_CALLRESULT_BOOL(
TRUE,
isFrozenFn->executeCall1(
isFrozenFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue()));
}
TEST_F(JSLibTest, ObjectPreventExtensionsTest) {
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
auto obj = createObject(runtime);
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<JSObject>(std::move(*propRes));
ASSERT_RETURNED((propRes = JSObject::getNamed_RJS(
objectCons,
runtime,
Predefined::getSymbolID(Predefined::preventExtensions)))
.getStatus());
auto preventExtensionsFn = runtime.makeHandle<Callable>(std::move(*propRes));
ASSERT_RETURNED((propRes = JSObject::getNamed_RJS(
objectCons,
runtime,
Predefined::getSymbolID(Predefined::isExtensible)))
.getStatus());
auto isExtensibleFn = runtime.makeHandle<Callable>(std::move(*propRes));
// Make sure it's extensible.
EXPECT_CALLRESULT_BOOL(
TRUE,
isExtensibleFn->executeCall1(
isExtensibleFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue()));
// obj.preventExtensions().
ASSERT_RETURNED(
preventExtensionsFn
->executeCall1(
preventExtensionsFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue())
.getStatus());
// Make sure isExtensible works.
EXPECT_CALLRESULT_BOOL(
FALSE,
isExtensibleFn->executeCall1(
isExtensibleFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue()));
}
TEST_F(JSLibTest, ObjectGetPrototypeOfTest) {
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
auto obj = createObject(runtime);
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<JSObject>(std::move(*propRes));
ASSERT_RETURNED((propRes = JSObject::getNamed_RJS(
objectCons,
runtime,
Predefined::getSymbolID(Predefined::getPrototypeOf)))
.getStatus());
auto getPrototypeOfFn = runtime.makeHandle<Callable>(std::move(*propRes));
// Object.getPrototypeOf(obj).
auto callRes = getPrototypeOfFn->executeCall1(
getPrototypeOfFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue());
ASSERT_RETURNED(callRes.getStatus());
auto objProto = runtime.makeHandle<JSObject>(std::move(*callRes));
// Create a property "objProto.prop1".
auto prop1ID = *runtime.getIdentifierTable().getSymbolHandle(
runtime, createUTF16Ref(u"prop1"));
ASSERT_TRUE(
JSObject::putNamed_RJS(
objProto,
runtime,
*prop1ID,
runtime.makeHandle(HermesValue::encodeDoubleValue(10))) !=
ExecutionStatus::EXCEPTION);
auto obj2 = createObject(runtime);
// Object.getPrototypeOf(obj2).
ASSERT_RETURNED((callRes = getPrototypeOfFn->executeCall1(
getPrototypeOfFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj2.getHermesValue()))
.getStatus());
auto obj2Proto = runtime.makeHandle<JSObject>(std::move(*callRes));
// Make sure that the new object's prototype is correct.
EXPECT_CALLRESULT_DOUBLE(
10, JSObject::getNamed_RJS(obj2Proto, runtime, *prop1ID));
}
TEST_F(JSLibTest, ObjectGetOwnPropertyDescriptorTest) {
GCScope scope{runtime, "JSLibTest.ObjectGetOwnPropertyDescriptorTest", 128};
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
{
auto obj = createObject(runtime);
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<JSObject>(std::move(*propRes));
ASSERT_RETURNED(
(propRes = JSObject::getNamed_RJS(
objectCons,
runtime,
Predefined::getSymbolID(Predefined::getOwnPropertyDescriptor)))
.getStatus());
auto getOwnPropertyDescriptorFn =
runtime.makeHandle<Callable>(std::move(*propRes));
// Create a property "objProto.prop1".
auto prop1ID = *runtime.getIdentifierTable().getSymbolHandle(
runtime, createUTF16Ref(u"prop1"));
ASSERT_TRUE(
JSObject::putNamed_RJS(
obj,
runtime,
*prop1ID,
runtime.makeHandle(HermesValue::encodeDoubleValue(10))) !=
ExecutionStatus::EXCEPTION);
// Object.getOwnPropertyDescriptor(obj).
auto callRes = Callable::executeCall2(
getOwnPropertyDescriptorFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue(),
HermesValue::encodeStringValue(
runtime.getStringPrimFromSymbolID(*prop1ID)));
ASSERT_RETURNED(callRes.getStatus());
auto desc = runtime.makeHandle<JSObject>(std::move(*callRes));
EXPECT_CALLRESULT_BOOL(
TRUE,
JSObject::getNamed_RJS(
desc, runtime, Predefined::getSymbolID(Predefined::writable)));
EXPECT_CALLRESULT_BOOL(
TRUE,
JSObject::getNamed_RJS(
desc, runtime, Predefined::getSymbolID(Predefined::enumerable)));
EXPECT_CALLRESULT_BOOL(
TRUE,
JSObject::getNamed_RJS(
desc, runtime, Predefined::getSymbolID(Predefined::configurable)));
EXPECT_CALLRESULT_DOUBLE(
10,
JSObject::getNamed_RJS(
desc, runtime, Predefined::getSymbolID(Predefined::value)));
}
{
auto obj = createObject(runtime);
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<JSObject>(std::move(*propRes));
ASSERT_RETURNED(
(propRes = JSObject::getNamed_RJS(
objectCons,
runtime,
Predefined::getSymbolID(Predefined::getOwnPropertyDescriptor)))
.getStatus());
auto getOwnPropertyDescriptorFn =
runtime.makeHandle<Callable>(std::move(*propRes));
// Create a property "objProto.prop1".
auto prop1ID = *runtime.getIdentifierTable().getSymbolHandle(
runtime, createUTF16Ref(u"prop1"));
DefinePropertyFlags dpf{};
dpf.setGetter = 1;
dpf.setSetter = 1;
dpf.setConfigurable = 1;
dpf.configurable = 1;
dpf.setEnumerable = 1;
dpf.enumerable = 1;
auto runtimeModule = RuntimeModule::createUninitialized(runtime, domain);
BytecodeModuleGenerator BMG;
auto BFG = BytecodeFunctionGenerator::create(BMG, 1);
BFG->emitLoadConstDouble(0, 18);
BFG->emitRet(0);
auto codeBlock = createCodeBlock(runtimeModule, runtime, BFG.get());
auto getter = runtime.makeHandle<JSFunction>(JSFunction::create(
runtime,
runtimeModule->getDomain(runtime),
runtime.makeNullHandle<JSObject>(),
runtime.makeNullHandle<Environment>(),
codeBlock));
auto setter = runtime.makeHandle<JSFunction>(JSFunction::create(
runtime,
runtimeModule->getDomain(runtime),
runtime.makeNullHandle<JSObject>(),
runtime.makeNullHandle<Environment>(),
codeBlock));
auto accessor = runtime.makeHandle<PropertyAccessor>(
*PropertyAccessor::create(runtime, getter, setter));
ASSERT_TRUE(
JSObject::defineOwnProperty(obj, runtime, *prop1ID, dpf, accessor) !=
ExecutionStatus::EXCEPTION);
// Object.getOwnPropertyDescriptor(obj).
auto callRes = Callable::executeCall2(
getOwnPropertyDescriptorFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue(),
HermesValue::encodeStringValue(
runtime.getStringPrimFromSymbolID(*prop1ID)));
ASSERT_RETURNED(callRes.getStatus());
auto desc = runtime.makeHandle<JSObject>(std::move(*callRes));
EXPECT_CALLRESULT_BOOL(
TRUE,
JSObject::getNamed_RJS(
desc, runtime, Predefined::getSymbolID(Predefined::enumerable)));
EXPECT_CALLRESULT_BOOL(
TRUE,
JSObject::getNamed_RJS(
desc, runtime, Predefined::getSymbolID(Predefined::configurable)));
ASSERT_RETURNED(
(propRes = JSObject::getNamed_RJS(
desc, runtime, Predefined::getSymbolID(Predefined::get)))
.getStatus());
EXPECT_EQ(getter.get(), (*propRes)->getPointer());
ASSERT_RETURNED(
(propRes = JSObject::getNamed_RJS(
desc, runtime, Predefined::getSymbolID(Predefined::set)))
.getStatus());
EXPECT_EQ(setter.get(), (*propRes)->getPointer());
}
}
TEST_F(JSLibTest, ObjectDefinePropertyTest) {
GCScope scope{runtime, "JSLibTest.ObjectDefinePropertyTest", 128};
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
// Get global object.
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<JSObject>(std::move(*propRes));
// Get Object.defineProperty() function.
ASSERT_RETURNED((propRes = JSObject::getNamed_RJS(
objectCons,
runtime,
Predefined::getSymbolID(Predefined::defineProperty),
PropOpFlags().plusMustExist()))
.getStatus());
auto definePropertyFn = runtime.makeHandle<Callable>(std::move(*propRes));
{
// Create a PropertyDescriptor object with enumerable and configurable set.
auto attributes = createObject(runtime);
ASSERT_TRUE(JSObject::putNamed_RJS(
attributes,
runtime,
Predefined::getSymbolID(Predefined::enumerable),
runtime.makeHandle(HermesValue::encodeBoolValue(true)),
PropOpFlags().plusThrowOnError())
.getValue());
ASSERT_TRUE(JSObject::putNamed_RJS(
attributes,
runtime,
Predefined::getSymbolID(Predefined::configurable),
runtime.makeHandle(HermesValue::encodeBoolValue(true)),
PropOpFlags().plusThrowOnError())
.getValue());
// Add value to the PropertyDescriptor.
auto value = HermesValue::encodeDoubleValue(123);
ASSERT_TRUE(JSObject::putNamed_RJS(
attributes,
runtime,
Predefined::getSymbolID(Predefined::value),
runtime.makeHandle(value),
PropOpFlags().plusThrowOnError())
.getValue());
// Call Object.defineProperty() with prop.
auto propHandle =
StringPrimitive::createNoThrow(runtime, createUTF16Ref(u"newkey"));
ASSERT_RETURNED(
definePropertyFn
->executeCall3(
definePropertyFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
objectCons.getHermesValue(),
propHandle.getHermesValue(),
attributes.getHermesValue(),
false)
.getStatus());
// Now fetch the property/value and verify it matches the setup.
NamedPropertyDescriptor desc;
auto propID = valueToSymbolID(runtime, propHandle).getValue();
JSObject::getNamedDescriptorUnsafe(objectCons, runtime, *propID, desc);
EXPECT_TRUE(desc.flags.enumerable);
EXPECT_TRUE(desc.flags.configurable);
EXPECT_FALSE(desc.flags.writable);
EXPECT_CALLRESULT_DOUBLE(
123,
JSObject::getNamed_RJS(
objectCons, runtime, *propID, PropOpFlags().plusMustExist()));
}
{
// Test getter and setters in the attributes.
// Use toString() as the setter and the getter.
auto accessorAttributes = createObject(runtime);
auto propRes = JSObject::getNamed_RJS(
accessorAttributes,
runtime,
Predefined::getSymbolID(Predefined::toString));
ASSERT_RETURNED(propRes.getStatus());
auto toStringFn = runtime.makeHandle<Callable>(std::move(*propRes));
ASSERT_TRUE(JSObject::putNamed_RJS(
accessorAttributes,
runtime,
Predefined::getSymbolID(Predefined::set),
toStringFn,
PropOpFlags().plusThrowOnError())
.getValue());
ASSERT_TRUE(JSObject::putNamed_RJS(
accessorAttributes,
runtime,
Predefined::getSymbolID(Predefined::get),
toStringFn,
PropOpFlags().plusThrowOnError())
.getValue());
// Call Object.defineProperty() with prop.
auto prop = runtime.makeHandle(HermesValue::encodeStringValue(
StringPrimitive::createNoThrow(runtime, createUTF16Ref(u"newkey1"))
.get()));
ASSERT_RETURNED(
definePropertyFn
->executeCall3(
definePropertyFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
objectCons.getHermesValue(),
prop.get(),
accessorAttributes.getHermesValue(),
false)
.getStatus());
// Now fetch the property/value and verify it matches the setup.
NamedPropertyDescriptor desc;
auto propID = valueToSymbolID(runtime, prop).getValue();
JSObject::getNamedDescriptorUnsafe(objectCons, runtime, *propID, desc);
EXPECT_TRUE(desc.flags.accessor);
EXPECT_FALSE(desc.flags.writable);
// Get the accessor and verify it has the correct setter and getter.
PseudoHandle<> accessor = std::move(
JSObject::getNamedSlotValue(objectCons, runtime, desc).getValue());
ASSERT_TRUE(accessor->isPointer());
ASSERT_TRUE(accessor->getPointer() != nullptr);
auto accessorPtr =
PseudoHandle<PropertyAccessor>::dyn_vmcast(std::move(accessor));
ASSERT_TRUE(accessorPtr.get() != nullptr);
EXPECT_EQ(
accessorPtr->getter.get(runtime),
vmcast<Callable>(toStringFn.getHermesValue()));
EXPECT_EQ(
accessorPtr->setter.get(runtime),
vmcast<Callable>(toStringFn.getHermesValue()));
// Call the getter, it should return a string.
ASSERT_RETURNED(
(propRes = JSObject::getNamed_RJS(
objectCons, runtime, *propID, PropOpFlags().plusMustExist()))
.getStatus());
EXPECT_TRUE((*propRes)->isString());
}
}
TEST_F(JSLibTest, ObjectDefinePropertiesTest) {
GCScope scope{runtime, "JSLibTest.ObjectDefinePropertiesTest", 128};
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
auto str1 =
StringPrimitive::createNoThrow(runtime, createUTF16Ref(u"key1")).get();
auto id1 =
valueToSymbolID(
runtime, runtime.makeHandle(HermesValue::encodeStringValue(str1)))
.getValue();
auto str2 =
StringPrimitive::createNoThrow(runtime, createUTF16Ref(u"key2")).get();
auto id2 =
valueToSymbolID(
runtime, runtime.makeHandle(HermesValue::encodeStringValue(str2)))
.getValue();
auto properties = createObject(runtime);
// Create the first property descriptor object.
{
auto property1 = createObject(runtime);
ASSERT_TRUE(JSObject::putNamed_RJS(
property1,
runtime,
Predefined::getSymbolID(Predefined::enumerable),
runtime.makeHandle(HermesValue::encodeBoolValue(true)),
PropOpFlags().plusThrowOnError())
.getValue());
ASSERT_TRUE(JSObject::putNamed_RJS(
property1,
runtime,
Predefined::getSymbolID(Predefined::configurable),
runtime.makeHandle(HermesValue::encodeBoolValue(true)),
PropOpFlags().plusThrowOnError())
.getValue());
auto value1 = HermesValue::encodeDoubleValue(123);
ASSERT_TRUE(JSObject::putNamed_RJS(
property1,
runtime,
Predefined::getSymbolID(Predefined::value),
runtime.makeHandle(value1),
PropOpFlags().plusThrowOnError())
.getValue());
ASSERT_TRUE(JSObject::putNamed_RJS(
properties,
runtime,
*id1,
property1,
PropOpFlags().plusThrowOnError())
.getValue());
}
// Create the second property descriptor object.
{
auto property2 = createObject(runtime);
ASSERT_TRUE(JSObject::putNamed_RJS(
property2,
runtime,
Predefined::getSymbolID(Predefined::writable),
runtime.makeHandle(HermesValue::encodeBoolValue(true)),
PropOpFlags().plusThrowOnError())
.getValue());
auto value2 = HermesValue::encodeNullValue();
ASSERT_TRUE(JSObject::putNamed_RJS(
property2,
runtime,
Predefined::getSymbolID(Predefined::value),
runtime.makeHandle(value2),
PropOpFlags().plusThrowOnError())
.getValue());
ASSERT_TRUE(JSObject::putNamed_RJS(
properties,
runtime,
*id2,
property2,
PropOpFlags().plusThrowOnError())
.getValue());
}
// Get global object.
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<JSObject>(std::move(*propRes));
// Get Object.defineProperties() function.
ASSERT_RETURNED((propRes = JSObject::getNamed_RJS(
objectCons,
runtime,
Predefined::getSymbolID(Predefined::defineProperties),
PropOpFlags().plusMustExist()))
.getStatus());
auto definePropertiesFn = runtime.makeHandle<Callable>(std::move(*propRes));
// Define the properties.
auto obj = createObject(runtime);
ASSERT_RETURNED(
definePropertiesFn
->executeCall2(
definePropertiesFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
obj.getHermesValue(),
properties.getHermesValue(),
false)
.getStatus());
// Verify the first property.
{
NamedPropertyDescriptor desc;
JSObject::getNamedDescriptorUnsafe(obj, runtime, *id1, desc);
EXPECT_TRUE(desc.flags.enumerable);
EXPECT_TRUE(desc.flags.configurable);
EXPECT_FALSE(desc.flags.writable);
EXPECT_EQ(
JSObject::getNamedSlotValue(obj, runtime, desc).getValue()->getDouble(),
123);
}
// Verify the second property.
{
NamedPropertyDescriptor desc;
JSObject::getNamedDescriptorUnsafe(obj, runtime, *id2, desc);
EXPECT_TRUE(desc.flags.writable);
EXPECT_FALSE(desc.flags.enumerable);
EXPECT_FALSE(desc.flags.configurable);
EXPECT_TRUE(
JSObject::getNamedSlotValue(obj, runtime, desc).getValue()->isNull());
}
}
TEST_F(JSLibTest, ObjectCreateTest) {
GCScope scope{runtime, "JSLibTest.ObjectCreateTest", 128};
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
auto str1 =
StringPrimitive::createNoThrow(runtime, createUTF16Ref(u"key1")).get();
auto id1 =
valueToSymbolID(
runtime, runtime.makeHandle(HermesValue::encodeStringValue(str1)))
.getValue();
auto str2 =
StringPrimitive::createNoThrow(runtime, createUTF16Ref(u"key2")).get();
auto id2 =
valueToSymbolID(
runtime, runtime.makeHandle(HermesValue::encodeStringValue(str2)))
.getValue();
auto properties = createObject(runtime);
// Create the first property descriptor object.
{
auto property1 = createObject(runtime);
ASSERT_TRUE(JSObject::putNamed_RJS(
property1,
runtime,
Predefined::getSymbolID(Predefined::enumerable),
runtime.makeHandle(HermesValue::encodeBoolValue(true)),
PropOpFlags().plusThrowOnError())
.getValue());
ASSERT_TRUE(JSObject::putNamed_RJS(
property1,
runtime,
Predefined::getSymbolID(Predefined::configurable),
runtime.makeHandle(HermesValue::encodeBoolValue(true)),
PropOpFlags().plusThrowOnError())
.getValue());
auto value1 = HermesValue::encodeDoubleValue(123);
ASSERT_TRUE(JSObject::putNamed_RJS(
property1,
runtime,
Predefined::getSymbolID(Predefined::value),
runtime.makeHandle(value1),
PropOpFlags().plusThrowOnError())
.getValue());
ASSERT_TRUE(JSObject::putNamed_RJS(
properties,
runtime,
*id1,
property1,
PropOpFlags().plusThrowOnError())
.getValue());
}
// Create the second property descriptor object.
{
auto property2 = createObject(runtime);
ASSERT_TRUE(JSObject::putNamed_RJS(
property2,
runtime,
Predefined::getSymbolID(Predefined::writable),
runtime.makeHandle(HermesValue::encodeBoolValue(true)),
PropOpFlags().plusThrowOnError())
.getValue());
auto value2 = HermesValue::encodeNullValue();
ASSERT_TRUE(JSObject::putNamed_RJS(
property2,
runtime,
Predefined::getSymbolID(Predefined::value),
runtime.makeHandle(value2),
PropOpFlags().plusThrowOnError())
.getValue());
ASSERT_TRUE(JSObject::putNamed_RJS(
properties,
runtime,
*id2,
property2,
PropOpFlags().plusThrowOnError())
.getValue());
}
// Get global object.
GET_GLOBAL(Object);
auto objectCons = runtime.makeHandle<JSObject>(std::move(*propRes));
// Get Object.create() function.
ASSERT_RETURNED((propRes = JSObject::getNamed_RJS(
objectCons,
runtime,
Predefined::getSymbolID(Predefined::create),
PropOpFlags().plusMustExist()))
.getStatus());
auto createFn = runtime.makeHandle<Callable>(std::move(*propRes));
// Call Object.create().
auto prototype = createObject(runtime);
auto callRes = createFn->executeCall2(
createFn,
runtime,
runtime.makeHandle(HermesValue::encodeUndefinedValue()),
prototype.getHermesValue(),
properties.getHermesValue(),
false);
ASSERT_RETURNED(callRes.getStatus());
auto obj = runtime.makeHandle<JSObject>(std::move(*callRes));
// Verify the first property.
{
NamedPropertyDescriptor desc;
JSObject::getNamedDescriptorUnsafe(obj, runtime, *id1, desc);
EXPECT_TRUE(desc.flags.enumerable);
EXPECT_TRUE(desc.flags.configurable);
EXPECT_FALSE(desc.flags.writable);
EXPECT_EQ(
JSObject::getNamedSlotValue(obj, runtime, desc).getValue()->getDouble(),
123);
}
// Verify the second property.
{
NamedPropertyDescriptor desc;
JSObject::getNamedDescriptorUnsafe(obj, runtime, *id2, desc);
EXPECT_TRUE(desc.flags.writable);
EXPECT_FALSE(desc.flags.enumerable);
EXPECT_FALSE(desc.flags.configurable);
EXPECT_TRUE(
JSObject::getNamedSlotValue(obj, runtime, desc).getValue()->isNull());
}
}
TEST_F(JSLibTest, CreateStringTest) {
GCScope scope(runtime);
CallResult<PseudoHandle<>> propRes{ExecutionStatus::EXCEPTION};
// String constructor.
GET_GLOBAL(String);
auto stringCons = runtime.makeHandle<Callable>(std::move(*propRes));
// String.prototype.
GET_VALUE(stringCons, prototype);
auto prototype = runtime.makeHandle<JSObject>(std::move(*propRes));
// create a new instance.
auto crtRes = stringCons->newObject(stringCons, runtime, prototype);
ASSERT_RETURNED(crtRes.getStatus());
auto newStr = runtime.makeHandle<JSObject>(std::move(*crtRes));
// Make sure the prototype is correct.
ASSERT_EQ(prototype.get(), newStr->getParent(runtime));
// Call the constructor.
ASSERT_RETURNED(
Callable::executeCall0(stringCons, runtime, newStr, true).getStatus());
}
TEST_F(JSLibTest, SmallSortTest) {
// Small test, with some duplication
struct StringByLength : public SortModel {
std::vector<std::string> v;
StringByLength(std::vector<std::string> _v) : v(_v) {}
ExecutionStatus swap(uint32_t a, uint32_t b) override {
std::swap(v[a], v[b]);
return ExecutionStatus::RETURNED;
}
CallResult<int> compare(uint32_t a, uint32_t b) override {
return (int)v[a].size() - (int)v[b].size();
}
};
StringByLength sbl(
{"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine"});
ASSERT_EQ(ExecutionStatus::RETURNED, quickSort(&sbl, 0, sbl.v.size()));
std::vector<std::string> expected = {
"one",
"two",
"six",
"zero",
"four",
"five",
"nine",
"three",
"seven",
"eight"};
EXPECT_EQ(expected, sbl.v);
// Exhaustive test of all permutations of 7 unique elements
std::vector<std::string> vs(7);
for (unsigned i = 0; i < vs.size(); ++i)
vs[i] = std::string(i, 'x');
do {
StringByLength sm(vs);
ASSERT_EQ(ExecutionStatus::RETURNED, quickSort(&sm, 0, vs.size()));
for (unsigned i = 0; i < vs.size(); ++i)
EXPECT_EQ(i, sm.v[i].size());
} while (std::next_permutation(vs.begin(), vs.end()));
}
TEST_F(JSLibTest, HugeSortTest) {
// Huge random array, with duplication
struct Uint64ByHigh32 : public SortModel {
std::vector<uint64_t> v;
Uint64ByHigh32(std::vector<uint64_t> _v) : v(_v) {}
ExecutionStatus swap(uint32_t a, uint32_t b) override {
std::swap(v[a], v[b]);
return ExecutionStatus::RETURNED;
}
CallResult<int> compare(uint32_t a, uint32_t b) override {
return ((int)(v[a] >> 32)) - ((int)(v[b] >> 32));
}
};
// Make a shuffled array where each element is equivalent to 9 others.
uint64_t size = 1000 * 1000;
std::vector<uint64_t> v(size);
for (uint64_t i = 0; i < size; ++i)
v[i] = ((i / 10) << 32);
std::shuffle(v.begin(), v.end(), std::mt19937_64());
// Tag low bits of each element with its index before sorting.
for (uint64_t i = 0; i < size; ++i)
v[i] |= i;
Uint64ByHigh32 ubh(v);
ASSERT_EQ(ExecutionStatus::RETURNED, quickSort(&ubh, 0, ubh.v.size()));
for (uint64_t i = 0; i < size; ++i) {
auto cur = ubh.v[i];
EXPECT_EQ(i / 10, cur >> 32);
if (i > 0) {
auto prev = ubh.v[i - 1];
// If equivalent, then lower index should come first.
if ((prev >> 32) == (cur >> 32)) {
EXPECT_LT(prev & 0xffffffff, cur & 0xffffffff);
}
}
}
// Ensure sorting returns without exception even if "compare" is inconsistent.
struct RandomLess : public SortModel {
std::mt19937_64 rng;
ExecutionStatus swap(uint32_t a, uint32_t b) override {
return ExecutionStatus::RETURNED;
}
CallResult<int> compare(uint32_t a, uint32_t b) override {
// -1, 0, or 1
return ((int)(rng() % 3)) - 1;
}
};
RandomLess rl;
ASSERT_EQ(ExecutionStatus::RETURNED, quickSort(&rl, 0, 1000 * 1000));
}
class JSLibMockedEnvironmentTest : public RuntimeTestFixtureBase {
public:
JSLibMockedEnvironmentTest()
: RuntimeTestFixtureBase(RuntimeConfig::Builder()
.withGCConfig(kTestGCConfig)
.withTraceEnabled(true)
.withEnableSampledStats(true)
.build()) {}
};
TEST_F(JSLibMockedEnvironmentTest, MockedEnvironment) {
GCScope scope(runtime);
const std::minstd_rand::result_type mathRandomSeed = 123;
std::minstd_rand engine;
engine.seed(mathRandomSeed);
std::uniform_real_distribution<> dist{0.0, 1.0};
const double mathRandom = dist(engine);
const double secondMathRandom = dist(engine);
const uint64_t dateNow = 100;
const uint64_t newDate = 200;
const std::string dateAsFunc{"foo"};
const std::u16string dateAsFuncU16{u"foo"};
// This will be added on to as part of the test.
std::deque<uint64_t> dateNowColl{dateNow};
const std::deque<uint64_t> newDateColl{newDate};
const std::deque<std::string> dateAsFuncColl{dateAsFunc};
std::string affinityMaskKey{"js_threadAffinityMask"};
std::string affinityMaskValue{"<affinity mask>"};
std::string totalAllocBytesKey{"js_totalAllocatedBytes"};
double totalAllocBytesValue = 2222.0;
MockedEnvironment::StatsTable statsTable{
std::make_pair(
affinityMaskKey,
MockedEnvironment::StatsTableValue(affinityMaskValue)),
std::make_pair(
totalAllocBytesKey,
MockedEnvironment::StatsTableValue(totalAllocBytesValue))};
const std::deque<MockedEnvironment::StatsTable> instrumentedStats{statsTable};
runtime.setMockedEnvironment(hermes::vm::MockedEnvironment{
mathRandomSeed,
dateNowColl,
newDateColl,
dateAsFuncColl,
instrumentedStats});
{
// Call Math.random() and check that its output matches the one given.
auto propRes = JSObject::getNamed_RJS(
runtime.getGlobal(),
runtime,
Predefined::getSymbolID(Predefined::Math));
ASSERT_NE(propRes, ExecutionStatus::EXCEPTION)
<< "Exception accessing Math on the global object";
auto mathObj = runtime.makeHandle<JSObject>(std::move(propRes.getValue()));
propRes = JSObject::getNamed_RJS(
mathObj, runtime, Predefined::getSymbolID(Predefined::random));
ASSERT_NE(propRes, ExecutionStatus::EXCEPTION)
<< "Exception accessing random on the Math object";
auto randomFunc =
runtime.makeHandle<Callable>(std::move(propRes.getValue()));
auto val = Callable::executeCall0(
randomFunc, runtime, Runtime::getUndefinedValue());
ASSERT_NE(val, ExecutionStatus::EXCEPTION)
<< "Exception executing the call on Math.random()";
EXPECT_EQ(val->get().getNumber(), mathRandom);
// Make sure the second call gets the second value.
val = Callable::executeCall0(
randomFunc, runtime, Runtime::getUndefinedValue());
ASSERT_NE(val, ExecutionStatus::EXCEPTION)
<< "Exception executing the call on Math.random()";
EXPECT_EQ(val->get().getNumber(), secondMathRandom);
}
{
// Call various Date functions and check that the output matches the ones
// given.
auto propRes = JSObject::getNamed_RJS(
runtime.getGlobal(),
runtime,
Predefined::getSymbolID(Predefined::Date));
ASSERT_NE(propRes, ExecutionStatus::EXCEPTION)
<< "Exception accessing Date on the global object";
Handle<Callable> dateFunc =
runtime.makeHandle<Callable>(std::move(propRes.getValue()));
// Call Date.now().
propRes = JSObject::getNamed_RJS(
dateFunc, runtime, Predefined::getSymbolID(Predefined::now));
ASSERT_NE(propRes, ExecutionStatus::EXCEPTION)
<< "Exception accessing now on the Date object";
auto nowFunc = runtime.makeHandle<Callable>(std::move(propRes.getValue()));
auto val =
Callable::executeCall0(nowFunc, runtime, Runtime::getUndefinedValue());
ASSERT_NE(val, ExecutionStatus::EXCEPTION)
<< "Exception executing the call on Date.now()";
EXPECT_EQ(val->getHermesValue().getNumberAs<uint64_t>(), dateNow);
// Call new Date()
val = Callable::executeConstruct0(dateFunc, runtime);
ASSERT_NE(val, ExecutionStatus::EXCEPTION)
<< "Exception executing the call on new Date()";
PseudoHandle<JSDate> valAsObj =
PseudoHandle<JSDate>::vmcast(std::move(*val));
HermesValue hv =
HermesValue::encodeNumberValue(valAsObj->getPrimitiveValue());
// This pointer can become invalid, don't be tempted to use it incorrectly.
valAsObj.invalidate();
EXPECT_EQ(hv.getNumberAs<uint64_t>(), newDate);
// Call Date()
val =
Callable::executeCall0(dateFunc, runtime, Runtime::getUndefinedValue());
ASSERT_NE(val, ExecutionStatus::EXCEPTION)
<< "Exception executing the call on Date()";
SmallU16String<32> tmp;
val->get().getString()->appendUTF16String(tmp);
std::u16string str(tmp.begin(), tmp.end());
EXPECT_EQ(str, dateAsFuncU16);
}
#ifndef _WINDOWS
// TODO(T62209287): For unknown reasons, this doesn't work on Windows.
// When we figure out why, and fix, it remove the #ifndef.
{
// Call HermesInternal.getInstrumentedStats() and check that the values
// we've set are what we recorded.
CallResult<PseudoHandle<>> hermesInternalRes = JSObject::getNamed_RJS(
runtime.getGlobal(),
runtime,
Predefined::getSymbolID(Predefined::HermesInternal));
ASSERT_NE(hermesInternalRes, ExecutionStatus::EXCEPTION)
<< "Exception accessing HermesInternal on the global object";
ASSERT_TRUE(hermesInternalRes.getValue()->isObject())
<< "HermesInternal is not an object.";
auto hermesInternal = runtime.makeHandle(
PseudoHandle<JSObject>::vmcast(std::move(*hermesInternalRes)));
auto propRes = JSObject::getNamed_RJS(
hermesInternal,
runtime,
Predefined::getSymbolID(Predefined::getInstrumentedStats));
ASSERT_NE(propRes, ExecutionStatus::EXCEPTION)
<< "Exception accessing getInstrumentedStats on the "
<< "HermesInternal object";
auto getInstrumentedStatsFunc = runtime.makeHandle(
PseudoHandle<Callable>::vmcast(std::move(propRes.getValue())));
auto statsObjRes = Callable::executeCall0(
getInstrumentedStatsFunc, runtime, Runtime::getUndefinedValue());
ASSERT_NE(statsObjRes, ExecutionStatus::EXCEPTION)
<< "Exception executing the call on "
<< "HermesInternal.getInstrumentedStats";
ASSERT_TRUE(statsObjRes.getValue()->isObject())
<< "HermesInternal.getInstrumentedStats result is not an object.";
auto statsObj = runtime.makeHandle(
PseudoHandle<JSObject>::vmcast(std::move(statsObjRes.getValue())));
auto affinityMaskSymHandleRes =
runtime.getIdentifierTable().getSymbolHandle(
runtime, ASCIIRef(affinityMaskKey.c_str(), affinityMaskKey.size()));
ASSERT_NE(affinityMaskSymHandleRes, ExecutionStatus::EXCEPTION)
<< "Exception accessing creating symbol for 'js_threadAffinityMask'";
auto affinityMaskVal2Res =
JSObject::getNamed_RJS(statsObj, runtime, **affinityMaskSymHandleRes);
ASSERT_NE(affinityMaskVal2Res, ExecutionStatus::EXCEPTION)
<< "Exception accessing 'js_threadAffinityMask' in stats object";
ASSERT_TRUE(affinityMaskVal2Res.getValue()->isString())
<< "Value of 'js_threadAffinityMask' in stats object is not a string";
auto affinityMaskVal2ResStringRef =
affinityMaskVal2Res.getValue()->getString()->getStringRef<char>();
ASSERT_EQ(
affinityMaskValue,
std::string(
affinityMaskVal2ResStringRef.data(),
affinityMaskVal2ResStringRef.size()));
auto totalAllocBytesSymHandleRes =
runtime.getIdentifierTable().getSymbolHandle(
runtime,
ASCIIRef(totalAllocBytesKey.c_str(), totalAllocBytesKey.size()));
ASSERT_NE(totalAllocBytesSymHandleRes, ExecutionStatus::EXCEPTION)
<< "Exception accessing creating symbol for 'js_totalAllocatedBytes'";
auto totalAllocBytesVal2Res = JSObject::getNamed_RJS(
statsObj, runtime, **totalAllocBytesSymHandleRes);
ASSERT_NE(totalAllocBytesVal2Res, ExecutionStatus::EXCEPTION)
<< "Exception accessing 'js_totalAllocatedBytes' in stats object";
ASSERT_TRUE(totalAllocBytesVal2Res.getValue()->isNumber())
<< "Value of 'js_totalAllocatedBytes' in stats object is not a number";
double totalAllocBytesVal2 = totalAllocBytesVal2Res.getValue()->getNumber();
ASSERT_EQ(totalAllocBytesVal2, 2222.0);
}
#endif
// If the tracing mode is also engaged, ensure that the same values were
// traced as well.
auto *storage = runtime.getCommonStorage();
EXPECT_EQ(mathRandomSeed, storage->tracedEnv.mathRandomSeed);
EXPECT_EQ(dateNowColl, storage->tracedEnv.callsToDateNow);
EXPECT_EQ(newDateColl, storage->tracedEnv.callsToNewDate);
EXPECT_EQ(dateAsFuncColl, storage->tracedEnv.callsToDateAsFunction);
EXPECT_EQ(dateAsFuncColl, storage->tracedEnv.callsToDateAsFunction);
#ifndef _WINDOWS
// TODO(T62209287): For unknown reasons, this doesn't work on Windows.
// When we figure out why, and fix, it remove the #ifndef.
EXPECT_EQ(
instrumentedStats.size(),
storage->tracedEnv.callsToHermesInternalGetInstrumentedStats.size());
auto &callToHermesInternalGetInstrumentedStats =
storage->tracedEnv.callsToHermesInternalGetInstrumentedStats.at(0);
EXPECT_EQ(
affinityMaskValue,
callToHermesInternalGetInstrumentedStats
[llvh::StringRef(affinityMaskKey.c_str(), affinityMaskKey.size())]
.str());
EXPECT_EQ(
totalAllocBytesValue,
callToHermesInternalGetInstrumentedStats[llvh::StringRef(
totalAllocBytesKey.c_str(),
totalAllocBytesKey.size())]
.num());
#endif
}
} // anonymous namespace