diff --git a/API/hermes/TracingRuntime.h b/API/hermes/TracingRuntime.h index 59c72b17089..c7c455889a8 100644 --- a/API/hermes/TracingRuntime.h +++ b/API/hermes/TracingRuntime.h @@ -140,6 +140,13 @@ class TracingRuntime : public jsi::RuntimeDecorator jsi::Value getPrototypeOf(const jsi::Object &object) override; jsi::Array getPropertyNames(const jsi::Object &o) override; + // Uses the default implementation of jsi::Runtime, which calls the traced + // getPropertyNames and the other traced methods, so replay sees the same + // calls. + std::vector getPropertyNameIDs( + const jsi::Object &o) override { + return jsi::Runtime::getPropertyNameIDs(o); + } jsi::WeakObject createWeakObject(const jsi::Object &o) override; diff --git a/API/hermes/hermes.cpp b/API/hermes/hermes.cpp index 0f96516c5f2..370de573d4d 100644 --- a/API/hermes/hermes.cpp +++ b/API/hermes/hermes.cpp @@ -882,6 +882,8 @@ class HermesRuntimeImpl final : public HermesRuntime, bool isHostObject(const jsi::Object &) const override; bool isHostFunction(const jsi::Function &) const override; jsi::Array getPropertyNames(const jsi::Object &) override; + std::vector getPropertyNameIDs( + const jsi::Object &obj) override; void setPrototypeOf(const jsi::Object &object, const jsi::Value &prototype) override; @@ -3118,6 +3120,49 @@ jsi::Array HermesRuntimeImpl::getPropertyNames(const jsi::Object &obj) { return ret; } +std::vector HermesRuntimeImpl::getPropertyNameIDs( + const jsi::Object &obj) { + ExecutionScopeRAII scopeRAII(mutatorScope); + vm::GCScope gcScope(runtime_); + uint32_t beginIndex; + uint32_t endIndex; + vm::CallResult> cr = + vm::getForInPropertyNames(runtime_, handle(obj), beginIndex, endIndex); + checkStatus(cr.getStatus()); + vm::Handle names = *cr; + + std::vector result; + result.reserve(endIndex - beginIndex); + for (uint32_t i = beginIndex; i < endIndex; ++i) { + vm::GCScopeMarkerRAII marker{runtime_}; + vm::SmallHermesValue name = names->at(i); + if (name.isSymbol()) { + // for-in keeps the names of ordinary objects as symbols, which are what + // a PropNameID holds. + result.push_back(add( + vm::HermesValue::encodeSymbolValue(name.getSymbol()))); + continue; + } + vm::StringPrimitive *str; + if (name.isString()) { + str = name.getString(runtime_); + } else if (name.isNumber()) { + // May allocate. 'name' must not be used afterwards. + auto strRes = + vm::numberToStringPrimitive(runtime_, name.getNumber(runtime_)); + checkStatus(strRes.getStatus()); + str = strRes->get(); + } else { + llvm_unreachable("property name is not String, Symbol or Number"); + } + auto symbolRes = + vm::stringToSymbolID(runtime_, vm::createPseudoHandle(str)); + checkStatus(symbolRes.getStatus()); + result.push_back(add(symbolRes->getHermesValue())); + } + return result; +} + jsi::WeakObject HermesRuntimeImpl::createWeakObject(const jsi::Object &obj) { ExecutionScopeRAII scopeRAII(mutatorScope); return addWeak( diff --git a/API/jsi/jsi/decorator.h b/API/jsi/jsi/decorator.h index f91720b4194..5ee17d5153e 100644 --- a/API/jsi/jsi/decorator.h +++ b/API/jsi/jsi/decorator.h @@ -403,6 +403,9 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { Array getPropertyNames(const Object& o) override { return plain_.getPropertyNames(o); } + std::vector getPropertyNameIDs(const Object& o) override { + return plain_.getPropertyNameIDs(o); + } WeakObject createWeakObject(const Object& o) override { return plain_.createWeakObject(o); @@ -1043,6 +1046,10 @@ class WithRuntimeDecorator : public RuntimeDecorator { Around around{with_}; return RD::getPropertyNames(o); } + std::vector getPropertyNameIDs(const Object& o) override { + Around around{with_}; + return RD::getPropertyNameIDs(o); + } WeakObject createWeakObject(const Object& o) override { Around around{with_}; diff --git a/API/jsi/jsi/jsi-inl.h b/API/jsi/jsi/jsi-inl.h index 6a29f64798b..b5323b9ff54 100644 --- a/API/jsi/jsi/jsi-inl.h +++ b/API/jsi/jsi/jsi-inl.h @@ -297,6 +297,11 @@ inline Array Object::getPropertyNames(IRuntime& runtime) const { return runtime.getPropertyNames(*this); } +inline std::vector Object::getPropertyNameIDs( + IRuntime& runtime) const { + return runtime.getPropertyNameIDs(*this); +} + inline Value WeakObject::lock(IRuntime& runtime) const { return runtime.lockWeakObject(*this); } diff --git a/API/jsi/jsi/jsi.cpp b/API/jsi/jsi/jsi.cpp index 521001a8980..726ae8cf370 100644 --- a/API/jsi/jsi/jsi.cpp +++ b/API/jsi/jsi/jsi.cpp @@ -459,6 +459,18 @@ void Runtime::deleteProperty(const Object& object, const Value& name) { } } +std::vector Runtime::getPropertyNameIDs(const Object& object) { + Array names = getPropertyNames(object); + size_t count = names.size(*this); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; ++i) { + result.push_back(PropNameID::forString( + *this, names.getValueAtIndex(*this, i).getString(*this))); + } + return result; +} + size_t Runtime::push(const Array& arr, const Value* elements, size_t count) { size_t newSize = size(arr); for (size_t i = 0; i < count; i++) { diff --git a/API/jsi/jsi/jsi.h b/API/jsi/jsi/jsi.h index bf98028fb3f..2ab45328374 100644 --- a/API/jsi/jsi/jsi.h +++ b/API/jsi/jsi/jsi.h @@ -558,6 +558,9 @@ class JSI_EXPORT IRuntime : public ICast { virtual bool isHostObject(const jsi::Object&) const = 0; virtual bool isHostFunction(const jsi::Function&) const = 0; virtual Array getPropertyNames(const Object&) = 0; + /// Returns the same names as getPropertyNames, in the same order, as + /// PropNameIDs and without creating a JS array. + virtual std::vector getPropertyNameIDs(const Object& object) = 0; virtual WeakObject createWeakObject(const Object&) = 0; virtual Value lockWeakObject(const WeakObject&) = 0; @@ -731,6 +734,7 @@ class JSI_EXPORT Runtime : public IRuntime { void deleteProperty(const Object&, const PropNameID& name) override; void deleteProperty(const Object&, const String& name) override; void deleteProperty(const Object&, const Value& name) override; + std::vector getPropertyNameIDs(const Object& object) override; void setRuntimeDataImpl( const UUID& uuid, @@ -1419,6 +1423,11 @@ class JSI_EXPORT Object : public Pointer { /// works. I only need it in one place.) Array getPropertyNames(IRuntime& runtime) const; + /// \return the names of all enumerable properties in the object and its + /// prototype chain, as getPropertyNames does, but as PropNameIDs, which can + /// be used to get the property values without converting the names again. + std::vector getPropertyNameIDs(IRuntime& runtime) const; + /// Inform the runtime that there is additional memory associated with a given /// JavaScript object that is not visible to the GC. This can be used if an /// object is known to retain some native memory, and may be used to guide diff --git a/API/jsi/jsi/test/testlib.cpp b/API/jsi/jsi/test/testlib.cpp index 37b71538b87..53d215b4f8f 100644 --- a/API/jsi/jsi/test/testlib.cpp +++ b/API/jsi/jsi/test/testlib.cpp @@ -2222,6 +2222,47 @@ TEST_P(JSITest, GetStringDataTest) { EXPECT_EQ(buf, str.utf16(rd)); } +TEST_P(JSITest, GetPropertyNameIDsTest) { + // This Runtime Decorator is used to test the default getPropertyNameIDs + // implementation for VMs that do not provide their own implementation + class RD : public RuntimeDecorator { + public: + explicit RD(Runtime& rt) : RuntimeDecorator(rt) {} + + std::vector getPropertyNameIDs(const Object& object) override { + return Runtime::getPropertyNameIDs(object); + } + }; + RD rd(rt); + + for (const char* source : + {"({title: 1, 7: 2, '\\u00e9t\\u00e9': 3, [Symbol('s')]: 4, x: null})", + "[10, 20, 30]", + "(function() {" + " var o = Object.create({inherited: 1});" + " o.own = 2;" + " Object.defineProperty(o, 'getter', {" + " get: function() { return 3; }, enumerable: true});" + " Object.defineProperty(o, 'hidden', {value: 4, enumerable: false});" + " return o;" + "})()", + "({})"}) { + Object obj = eval(source).getObject(rt); + Array names = obj.getPropertyNames(rt); + for (Runtime* runtime : {static_cast(&rt), static_cast(&rd)}) { + std::vector ids = obj.getPropertyNameIDs(*runtime); + ASSERT_EQ(ids.size(), names.size(rt)) << source; + for (size_t i = 0; i < ids.size(); ++i) { + String name = names.getValueAtIndex(rt, i).getString(rt); + EXPECT_EQ(ids[i].utf8(rt), name.utf8(rt)) << source; + EXPECT_TRUE(Value::strictEquals( + rt, obj.getProperty(rt, ids[i]), obj.getProperty(rt, name))) + << source; + } + } + } +} + TEST_P(JSITest, ObjectSetPrototype) { // This Runtime Decorator is used to test the default implementation of // Object.setPrototypeOf