Skip to content

Commit

Permalink
Replace Date constructor
Browse files Browse the repository at this point in the history
Summary: Convert `Date()` and `new Date()` to HostFunctions. This was a little trickier to pull off. The main reason is because they both return object/string ids which are not defined if you just return the value coming from `runtime_`. So the solution is to recreate the object/string in the tracing runtime with the value from the untraced `runtime_` once you run the nondeterministic function.

Reviewed By: kodafb

Differential Revision: D37229133

fbshipit-source-id: 0022e8c2b49d77c1ef5717b1e5026281d9a94bee
  • Loading branch information
Michael Anthony Leon authored and facebook-github-bot committed Jul 14, 2022
1 parent 068d4ab commit 945b8c3
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 6 deletions.
104 changes: 98 additions & 6 deletions API/hermes/TracingRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,18 @@ TracingRuntime::TracingRuntime(

void TracingRuntime::replaceNondeterministicFuncs() {
insertHostForwarder({"Math", "random"});
insertHostForwarder({"Date", "now"});
setupDate();
setUpWeakRef();

numPreambleRecords_ = trace_.records().size();
}

void TracingRuntime::insertHostForwarder(
const std::vector<const char *> &propertyPath) {
jsi::Function origFunc =
walkPropertyPath(*runtime_, propertyPath).asFunction(*runtime_);
auto lenProp = origFunc.getProperty(*runtime_, "length").asNumber();
savedFunctions.push_back(std::move(origFunc));
jsi::Function *funcPtr = &savedFunctions.back();
auto lenProp = walkPropertyPath(*runtime_, propertyPath)
.getProperty(*runtime_, "length")
.asNumber();
jsi::Function *funcPtr = saveFunction(propertyPath);

jsi::Function funcReplacement = jsi::Function::createFromHostFunction(
*this,
Expand Down Expand Up @@ -104,6 +103,99 @@ void TracingRuntime::setUpWeakRef() {
insertHostForwarder({"WeakRef", "prototype", "deref"});
}

void TracingRuntime::setupDate() {
auto lenProp = walkPropertyPath(*runtime_, {"Date"})
.getProperty(*runtime_, "length")
.asNumber();
jsi::Function *origDateFunc = saveFunction({"Date"});

jsi::Function nativeDateCtor = jsi::Function::createFromHostFunction(
*this,
jsi::PropNameID::forAscii(*this, "Date"),
lenProp,
[this, origDateFunc](
Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
auto ret = origDateFunc->callAsConstructor(*runtime_);
// We cannot return this value here, because the trace would be
// invalid. `new Date()` returns an object, so returning it would mean
// returning an object that has never been defined. Therefore, we trace
// reconstructing a new Date with the argument being the getTime() value
// from the Date object created in the untraced runtime. Conceptually,
// we are transforming calls to the no-arg Date constructor:
// var myDate = new Date();
// -->
// var tmp = new Date(); <-- this is untraced
// var arg = tmp.getTime(); <-- this is untraced
// var myDate = new Date(arg); <-- this is traced
auto obj = ret.asObject(*runtime_);
auto val = obj.getPropertyAsFunction(*runtime_, "getTime")
.callWithThis(*runtime_, obj);
return this->global()
.getPropertyAsFunction(*this, "Date")
.callAsConstructor(*this, val);
});

jsi::Function nativeDateFunc = jsi::Function::createFromHostFunction(
*this,
jsi::PropNameID::forAscii(*this, "Date"),
lenProp,
[this, origDateFunc](
Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) {
auto ret = origDateFunc->call(*runtime_, args, count);
std::string retStr = ret.asString(*runtime_).utf8(*runtime_);
// If we just returned the string directly from the above call, the
// trace would not be valid because we would be using a string that has
// never been defined before. Therefore, we must copy the string in the
// tracing runtime to get this string to show up and be defined in the
// trace.
return jsi::String::createFromAscii(*this, retStr);
});

auto code = R"(
(function(nativeDateCtor, nativeDateFunc){
var DateReal = Date;
function DateJSReplacement(...args){
if (new.target){
if (arguments.length == 0){
return nativeDateCtor();
} else {
// calling new Date with arguments is deterministic
return new DateReal(...args);
}
} else {
return nativeDateFunc(...args);
}
}
// Cannot use Object.assign because Date methods are not enumerable
for (p of Object.getOwnPropertyNames(DateReal)){
DateJSReplacement[p] = DateReal[p];
}
globalThis.Date = DateJSReplacement;
});
)";
global()
.getPropertyAsFunction(*this, "eval")
.call(*this, code)
.asObject(*this)
.asFunction(*this)
.call(*this, {std::move(nativeDateCtor), std::move(nativeDateFunc)});
insertHostForwarder({"Date", "now"});
}

jsi::Function *TracingRuntime::saveFunction(
const std::vector<const char *> &propertyPath) {
jsi::Function origFunc =
walkPropertyPath(*runtime_, propertyPath).asFunction(*runtime_);
savedFunctions.push_back(std::move(origFunc));
return &savedFunctions.back();
}

jsi::Object TracingRuntime::walkPropertyPath(
jsi::Runtime &runtime,
const std::vector<const char *> &propertyPath,
Expand Down
4 changes: 4 additions & 0 deletions API/hermes/TracingRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,12 @@ class TracingRuntime : public jsi::RuntimeDecorator<jsi::Runtime> {

void insertHostForwarder(const std::vector<const char *> &propertyPath);

jsi::Function *saveFunction(const std::vector<const char *> &propertyChain);

void setUpWeakRef();

void setupDate();

// This function will traverse the properties defined in propertyPath,
// starting from the global object in the given runtime. This function can
// optionally skip the last \p skipLastAmt of properties in the given path.
Expand Down
30 changes: 30 additions & 0 deletions unittests/API/SynthTraceTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,36 @@ TEST_F(NonDeterminismReplayTest, DateNowTest) {
EXPECT_EQ(dateTime, replayedTime);
}

TEST_F(NonDeterminismReplayTest, DateFuncTest) {
eval(*traceRt, "var x = Date();");
auto dateTime = eval(*traceRt, "x").asString(*traceRt).utf8(*traceRt);

replay();

auto replayedTime = eval(*replayRt, "x").asString(*replayRt).utf8(*replayRt);
EXPECT_EQ(dateTime, replayedTime);
}

TEST_F(NonDeterminismReplayTest, DateNewTest) {
eval(*traceRt, "var x = new Date();");
auto dateTime = eval(*traceRt, "x.getTime()").asNumber();

replay();

auto replayedTime = eval(*replayRt, "x.getTime()").asNumber();
EXPECT_EQ(dateTime, replayedTime);
}

TEST_F(NonDeterminismReplayTest, DateNewWithArgsTest) {
eval(*traceRt, "var x = new Date(1, 2, 3, 4, 5, 6, 7).getTime();");
auto dateTime = eval(*traceRt, "x").asNumber();

replay();

auto replayedTime = eval(*replayRt, "x").asNumber();
EXPECT_EQ(dateTime, replayedTime);
}

TEST_F(NonDeterminismReplayTest, MathRandomTest) {
eval(*traceRt, "var x = Math.random();");
auto randVal = eval(*traceRt, "x").asNumber();
Expand Down

0 comments on commit 945b8c3

Please sign in to comment.