From f541af73d17de0df4722c595d5b34b71954102d9 Mon Sep 17 00:00:00 2001 From: Chi Tsai Date: Thu, 12 Dec 2024 01:14:00 -0800 Subject: [PATCH] Add CreateObjectWithPrototypeRecord in SynthTrace Summary: Add CreateObjectWithPrototypeRecord in Synth Traces, so we can replay them. Reviewed By: neildhar Differential Revision: D66890202 fbshipit-source-id: 8070c5a5172a64c3fc26666d6effbddd32758579 --- API/hermes/SynthTrace.cpp | 7 +++ API/hermes/SynthTrace.h | 26 +++++++++++ API/hermes/SynthTraceParser.cpp | 9 ++++ API/hermes/TraceInterpreter.cpp | 10 +++++ API/hermes/TracingRuntime.cpp | 8 ++++ API/hermes/TracingRuntime.h | 1 + unittests/API/SynthTraceParserTest.cpp | 43 +++++++++++++++++++ unittests/API/SynthTraceSerializationTest.cpp | 10 +++++ unittests/API/SynthTraceTest.cpp | 15 +++++++ 9 files changed, 129 insertions(+) diff --git a/API/hermes/SynthTrace.cpp b/API/hermes/SynthTrace.cpp index 3de9b30b5ea..d1e6b97e4d2 100644 --- a/API/hermes/SynthTrace.cpp +++ b/API/hermes/SynthTrace.cpp @@ -324,6 +324,13 @@ void SynthTrace::CreateObjectRecord::toJSONInternal(JSONEmitter &json) const { json.emitKeyValue("objID", objID_); } +void SynthTrace::CreateObjectWithPrototypeRecord::toJSONInternal( + ::hermes::JSONEmitter &json) const { + Record::toJSONInternal(json); + json.emitKeyValue("objID", objID_); + json.emitKeyValue("prototype", encode(prototype_)); +} + static std::string createBigIntMethodToString( SynthTrace::CreateBigIntRecord::Method m) { switch (m) { diff --git a/API/hermes/SynthTrace.h b/API/hermes/SynthTrace.h index 0d33bc5d636..3e1a2f88342 100644 --- a/API/hermes/SynthTrace.h +++ b/API/hermes/SynthTrace.h @@ -180,6 +180,7 @@ class SynthTrace { RECORD(EndExecJS) \ RECORD(Marker) \ RECORD(CreateObject) \ + RECORD(CreateObjectWithPrototype) \ RECORD(CreateString) \ RECORD(CreatePropNameID) \ RECORD(CreateHostObject) \ @@ -660,6 +661,31 @@ class SynthTrace { } }; + struct CreateObjectWithPrototypeRecord : public Record { + static constexpr RecordType type{RecordType::CreateObjectWithPrototype}; + const ObjectID objID_; + /// The prototype being assigned + const TraceValue prototype_; + + CreateObjectWithPrototypeRecord( + TimeSinceStart time, + ObjectID objID, + TraceValue prototype) + : Record(time), objID_(objID), prototype_(prototype) {} + + void toJSONInternal(::hermes::JSONEmitter &json) const override; + + RecordType getType() const override { + return type; + } + + std::vector uses() const override { + std::vector uses{objID_}; + pushIfTrackedValue(prototype_, uses); + return uses; + } + }; + struct CreateHostObjectRecord final : public CreateObjectRecord { static constexpr RecordType type{RecordType::CreateHostObject}; using CreateObjectRecord::CreateObjectRecord; diff --git a/API/hermes/SynthTraceParser.cpp b/API/hermes/SynthTraceParser.cpp index 31ddc609260..fa4d6646315 100644 --- a/API/hermes/SynthTraceParser.cpp +++ b/API/hermes/SynthTraceParser.cpp @@ -297,6 +297,15 @@ SynthTrace getTrace( trace.emplace_back( timeFromStart, objID->getValue()); break; + case RecordType::CreateObjectWithPrototype: { + auto *prototype = + llvh::dyn_cast_or_null(obj->get("prototype")); + trace.emplace_back( + timeFromStart, + objID->getValue(), + SynthTrace::decode(prototype->c_str())); + break; + } case RecordType::QueueMicrotask: { auto callbackID = getNumberAs(obj->get("callbackID")); diff --git a/API/hermes/TraceInterpreter.cpp b/API/hermes/TraceInterpreter.cpp index 7d3aab17e45..651d60e2e6b 100644 --- a/API/hermes/TraceInterpreter.cpp +++ b/API/hermes/TraceInterpreter.cpp @@ -778,6 +778,16 @@ void TraceInterpreter::executeRecords() { addToObjectMap(cor.objID_, Object(rt_), currentExecIndex); break; } + case RecordType::CreateObjectWithPrototype: { + const auto &record = + static_cast( + *rec); + addToObjectMap( + record.objID_, + Object::create(rt_, traceValueToJSIValue(record.prototype_)), + currentExecIndex); + break; + } case RecordType::CreateBigInt: { const auto &cbr = static_cast(*rec); diff --git a/API/hermes/TracingRuntime.cpp b/API/hermes/TracingRuntime.cpp index 188f97edecd..ee8f179634e 100644 --- a/API/hermes/TracingRuntime.cpp +++ b/API/hermes/TracingRuntime.cpp @@ -390,6 +390,14 @@ jsi::Object TracingRuntime::createObject() { return obj; } +jsi::Object TracingRuntime::createObjectWithPrototype( + const jsi::Value &prototype) { + auto obj = RD::createObjectWithPrototype(prototype); + trace_.emplace_back( + getTimeSinceStart(), defObjectID(obj), useTraceValue(prototype)); + return obj; +} + jsi::Object TracingRuntime::createObject(std::shared_ptr ho) { class TracingHostObject : public jsi::DecoratedHostObject { public: diff --git a/API/hermes/TracingRuntime.h b/API/hermes/TracingRuntime.h index 5f84b521f43..893dffcd548 100644 --- a/API/hermes/TracingRuntime.h +++ b/API/hermes/TracingRuntime.h @@ -47,6 +47,7 @@ class TracingRuntime : public jsi::RuntimeDecorator { jsi::Object global() override; jsi::Object createObject() override; + jsi::Object createObjectWithPrototype(const jsi::Value &prototype) override; jsi::Object createObject(std::shared_ptr ho) override; // Note that the NativeState methods do not need to be traced since they diff --git a/unittests/API/SynthTraceParserTest.cpp b/unittests/API/SynthTraceParserTest.cpp index 606eca6fc26..5192ebc98c6 100644 --- a/unittests/API/SynthTraceParserTest.cpp +++ b/unittests/API/SynthTraceParserTest.cpp @@ -425,4 +425,47 @@ TEST_F(SynthTraceParserTest, ParseSetAndGetPrototypeRecord) { ASSERT_EQ(record3.objID_, 2); } +TEST_F(SynthTraceParserTest, ParseCreateObjectWithPrototypeRecord) { + const char *src = R"( +{ + "version": 5, + "globalObjID": 258, + "runtimeConfig": { + "gcConfig": { + "initHeapSize": 33554432, + "maxHeapSize": 536870912 + } + }, + "trace": [ + { + "type": "CreateObjectWithPrototypeRecord", + "time": 1234, + "objID": 1, + "prototype": "null:" + }, + { + "type": "CreateObjectWithPrototypeRecord", + "time": 12345, + "objID": 2, + "prototype": "object:1" + } + ] +} + )"; + auto parseResult = parseSynthTrace(bufFromStr(src)); + SynthTrace &trace = std::get<0>(parseResult); + + auto record0 = + dynamic_cast( + *trace.records().at(0)); + ASSERT_EQ(record0.objID_, 1); + ASSERT_EQ(record0.prototype_, SynthTrace::encodeNull()); + + auto record1 = + dynamic_cast( + *trace.records().at(1)); + ASSERT_EQ(record1.objID_, 2); + ASSERT_EQ(record1.prototype_, SynthTrace::encodeObject(1)); +} + } // namespace diff --git a/unittests/API/SynthTraceSerializationTest.cpp b/unittests/API/SynthTraceSerializationTest.cpp index 40a16470e9f..69e5a487fe4 100644 --- a/unittests/API/SynthTraceSerializationTest.cpp +++ b/unittests/API/SynthTraceSerializationTest.cpp @@ -442,4 +442,14 @@ TEST_F(SynthTraceSerializationTest, GetPrototypeTest) { to_string(SynthTrace::GetPrototypeRecord(dummyTime, 1))); } +TEST_F(SynthTraceSerializationTest, CreateObjectWithPrototypeRecord) { + EXPECT_EQ( + R"({"type":"CreateObjectWithPrototypeRecord","time":0,"objID":1,"prototype":"null:"})", + to_string(SynthTrace::CreateObjectWithPrototypeRecord( + dummyTime, 1, SynthTrace::encodeNull()))); + EXPECT_EQ( + R"({"type":"CreateObjectWithPrototypeRecord","time":0,"objID":2,"prototype":"object:1"})", + to_string(SynthTrace::CreateObjectWithPrototypeRecord( + dummyTime, 2, SynthTrace::encodeObject(1)))); +} } // namespace diff --git a/unittests/API/SynthTraceTest.cpp b/unittests/API/SynthTraceTest.cpp index cbb14c30f77..270a4deebd9 100644 --- a/unittests/API/SynthTraceTest.cpp +++ b/unittests/API/SynthTraceTest.cpp @@ -1450,6 +1450,15 @@ TEST_F(SynthTraceReplayTest, CreateObjectReplay) { auto obj = jsi::Object(rt); obj.setProperty(rt, "bar", 5); rt.global().setProperty(rt, "foo", obj); + + jsi::Object prototypeObj(rt); + prototypeObj.setProperty(rt, "someProperty", 123); + jsi::Value prototype(rt, prototypeObj); + jsi::Object child1 = jsi::Object::create(rt, prototype); + rt.global().setProperty(rt, "child1", child1); + + jsi::Object child2 = jsi::Object::create(rt, jsi::Value::null()); + rt.global().setProperty(rt, "child2", child2); } replay(); { @@ -1460,6 +1469,12 @@ TEST_F(SynthTraceReplayTest, CreateObjectReplay) { .getProperty(rt, "bar") .asNumber(), 5); + + auto child1 = rt.global().getProperty(rt, "child1").asObject(rt); + EXPECT_EQ(child1.getProperty(rt, "someProperty").asNumber(), 123); + + auto child2 = rt.global().getProperty(rt, "child2").asObject(rt); + EXPECT_TRUE(child2.getPrototype(rt).isNull()); } }