diff --git a/API/hermes/TracingRuntime.cpp b/API/hermes/TracingRuntime.cpp index fa14f500805..3641c46d21e 100644 --- a/API/hermes/TracingRuntime.cpp +++ b/API/hermes/TracingRuntime.cpp @@ -60,7 +60,8 @@ void TracingRuntime::replaceNondeterministicFuncs() { auto fun = args[0].getObject(*runtime_).getFunction(*runtime_); jsi::Value result; if (count > 1 && args[1].isObject()) { - result = fun.callWithThis(*runtime_, args[1].asObject(*runtime_)); + result = fun.callWithThis( + *runtime_, args[1].asObject(*runtime_), &args[2], count - 2); } else { result = fun.call(*runtime_); } @@ -181,25 +182,71 @@ void TracingRuntime::replaceNondeterministicFuncs() { globalThis.WeakRef = WeakRef; } + // Trace date getters and conversion functions as they can produce values + // that depend on the execution environment's timezone. Setters can set + // values that use the timezone, but the effect will not be observable + // because of the overridden getters. var DateReal = globalThis.Date; - var dateNowReal = DateReal.now; - var nativeDateNow = function now() { return callUntraced(dateNowReal); }; + [ + "getDate", + "getDay", + "getFullYear", + "getHours", + "getMilliseconds", + "getMinutes", + "getMonth", + "getSeconds", + "getTime", + "getTimezoneOffset", + "getUTCDate", + "getUTCDay", + "getUTCFullYear", + "getUTCHours", + "getUTCMilliseconds", + "getUTCMinutes", + "getUTCMonth", + "getUTCSeconds", + "getYear", + "toDateString", + "toGMTString", + "toISOString", + "toJSON", + "toLocaleDateString", + "toLocaleString", + "toLocaleTimeString", + "toString", + "toTimeString", + "toUTCString", + "valueOf", + Symbol.toPrimitive + ].forEach((property) => { + const original = DateReal.prototype[property]; + const replacement = function(...args) { + return callUntraced(original, this, ...args); + }; + Object.defineProperty(replacement, "name", {value: original.name}); + Object.defineProperty(DateReal.prototype, property, { + value: replacement, + writable: true, + configurable: true + }); + }); + function Date(...args){ - // Convert non-deterministic calls like `Date()` and `new Date()` into the - // deterministic form `new Date(Date.now())`, so they can be traced. + // Trace calls to `Date()`. if(!new.target){ - return new DateReal(nativeDateNow()).toString(); - } - if (arguments.length == 0){ - return new DateReal(nativeDateNow()); + return callUntraced(DateReal); } + // While `new Date()` technically records the current time, there is no way + // to observe it since we override all the getters. return new DateReal(...args); } // Cannot use Object.assign because Date methods are not enumerable for (p of Object.getOwnPropertyNames(DateReal)){ Date[p] = DateReal[p]; } - Date.now = nativeDateNow; + var dateNowReal = DateReal.now; + Date.now = function now() { return callUntraced(dateNowReal); }; globalThis.Date = Date; const defineProperty = Object.defineProperty; diff --git a/unittests/API/SynthTraceTest.cpp b/unittests/API/SynthTraceTest.cpp index 8027085fd8b..0a5c7350490 100644 --- a/unittests/API/SynthTraceTest.cpp +++ b/unittests/API/SynthTraceTest.cpp @@ -2092,12 +2092,12 @@ TEST_F(NonDeterminismReplayTest, DateFuncTest) { } TEST_F(NonDeterminismReplayTest, DateNewTest) { - eval(*traceRt, "var x = new Date();"); - auto dateTime = eval(*traceRt, "x.getTime()").asNumber(); + eval(*traceRt, "var x = new Date(); var y = x.getTime();"); + auto dateTime = eval(*traceRt, "y").asNumber(); replay(); - auto replayedTime = eval(*replayRt, "x.getTime()").asNumber(); + auto replayedTime = eval(*replayRt, "y").asNumber(); EXPECT_EQ(dateTime, replayedTime); } @@ -2116,12 +2116,13 @@ TEST_F(NonDeterminismReplayTest, DateReplacedTest) { var oldDate = Date; Date = undefined; var x = new oldDate(); +var y = x.getTime(); )"); - auto traceTime = eval(*traceRt, "x.getTime()").asNumber(); + auto traceTime = eval(*traceRt, "y").asNumber(); replay(); - auto replayedTime = eval(*replayRt, "x.getTime()").asNumber(); + auto replayedTime = eval(*replayRt, "y").asNumber(); EXPECT_EQ(traceTime, replayedTime); }