This repository has been archived on 2022-03-12. You can view files and clone it, but cannot push or open issues or pull requests.
2021-04-02 02:24:13 +03:00

866 lines
28 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#ifndef HERMES_SYNTHTRACE_H
#define HERMES_SYNTHTRACE_H
#ifdef HERMESVM_API_TRACE
#include "hermes/Public/RuntimeConfig.h"
#include "hermes/Support/JSONEmitter.h"
#include "hermes/Support/StringSetVector.h"
#include "hermes/VM/HermesValue.h"
#include "hermes/VM/MockedEnvironment.h"
#include "hermes/VM/Operations.h"
#include <chrono>
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>
namespace llvm {
// Forward declaration to avoid including llvm headers.
class raw_ostream;
} // namespace llvm
namespace facebook {
namespace hermes {
namespace tracing {
/// A SynthTrace is a list of events that occur in a run of a JS file by a
/// runtime that uses JSI.
/// It can be serialized into JSON and written to a llvm::raw_ostream.
class SynthTrace {
public:
using ObjectID = uint64_t;
/// A tagged union representing different types available in the trace.
/// HermesValue doesn't have to be used, but it is an efficient way
/// of encoding a type tag and a value. std::variant is another option.
/// NOTE: Since HermesValue can only store 64-bit values, strings need to be
/// changed into a table index.
using TraceValue = ::hermes::vm::HermesValue;
/// A TimePoint is a time when some event occurred.
using TimePoint = std::chrono::steady_clock::time_point;
using TimeSinceStart = std::chrono::milliseconds;
static constexpr size_t kHashNumBytes = 20;
/// RecordType is a tag used to differentiate which type of record it is.
/// There should be a unique tag for each record type.
enum class RecordType {
BeginExecJS,
EndExecJS,
Marker,
CreateObject,
CreateHostObject,
CreateHostFunction,
GetProperty,
SetProperty,
HasProperty,
GetPropertyNames,
CreateArray,
ArrayRead,
ArrayWrite,
CallFromNative,
ConstructFromNative,
ReturnFromNative,
ReturnToNative,
CallToNative,
GetPropertyNative,
GetPropertyNativeReturn,
SetPropertyNative,
SetPropertyNativeReturn,
GetNativePropertyNames,
GetNativePropertyNamesReturn,
};
/// A Record is one element of a trace.
struct Record {
/// The time at which this event occurred with respect to the start of
/// execution.
/// NOTE: This is not compared in the \c operator= in order for tests to
/// pass.
const TimeSinceStart time_;
explicit Record() = delete;
explicit Record(TimeSinceStart time) : time_(time) {}
virtual ~Record() = default;
/// Write out a serialization of this Record.
/// \param json An emitter connected to an ostream which will write out
/// JSON.
void toJSON(::hermes::JSONEmitter &json, const SynthTrace &trace) const;
virtual RecordType getType() const = 0;
/// \return A list of object ids that are defined by this record.
/// Defined means that the record would produce that object as a locally
/// accessible value if it were executed.
virtual std::vector<ObjectID> defs() const {
return {};
}
/// \return A list of object ids that are used by this record.
/// Used means that the record would use that object as a value if it were
/// executed.
/// If a record uses an object, then some preceding record (either in the
/// same function invocation, or somewhere globally) must provide a
/// definition.
virtual std::vector<ObjectID> uses() const {
return {};
}
protected:
/// Compare records for equality. Derived classes should override this, call
/// their parent, and mark any public versions as "final".
virtual bool operator==(const Record &that) const {
return getType() == that.getType();
}
/// Emit JSON fields into \p os, excluding the closing curly brace.
/// NOTE: This is overridable, and non-abstract children should call the
/// parent.
virtual void toJSONInternal(
::hermes::JSONEmitter &json,
const SynthTrace &trace) const;
};
/// If \p traceStream is non-null, the trace will be written to that
/// stream. Otherwise, no trace is written.
explicit SynthTrace(
ObjectID globalObjID,
const ::hermes::vm::RuntimeConfig &conf,
std::unique_ptr<llvm::raw_ostream> traceStream = nullptr);
template <typename T, typename... Args>
void emplace_back(Args &&... args) {
records_.emplace_back(new T(std::forward<Args>(args)...));
flushRecordsIfNecessary();
}
const std::vector<std::unique_ptr<Record>> &records() const {
return records_;
}
ObjectID globalObjID() const {
return globalObjID_;
}
/// Given a trace value, turn it into its typed string.
std::string encode(TraceValue value) const;
/// Encode an undefined JS value for the trace.
static TraceValue encodeUndefined();
/// Encode a null JS value for the trace.
static TraceValue encodeNull();
/// Encode a boolean JS value for the trace.
static TraceValue encodeBool(bool value);
/// Encodes a numeric value for the trace.
static TraceValue encodeNumber(double value);
/// Encodes an object for the trace as a unique id.
static TraceValue encodeObject(ObjectID objID);
/// Encodes a string for the trace. Adds it to the string table if it hasn't
/// been seen before.
TraceValue encodeString(const std::string &value);
/// Decodes a string into a trace value. It can add to the string table.
TraceValue decode(const std::string &);
/// Extracts an object ID from a trace value.
/// \pre The value must be an object.
static ObjectID decodeObject(TraceValue value);
/// Extracts a string from a trace value.
/// \pre The value must be a string.
const std::string &decodeString(TraceValue value) const;
static bool equal(TraceValue x, TraceValue y) {
// We are encoding random numbers into strings, and can't use the library
// functions.
// For now, ignore differences that result from NaN, +0/-0.
return x.getRaw() == y.getRaw();
}
/// The version of the Synth Benchmark
constexpr static uint32_t synthVersion() {
return 2;
}
static const char *nameFromReleaseUnused(::hermes::vm::ReleaseUnused ru);
static ::hermes::vm::ReleaseUnused releaseUnusedFromName(const char *name);
private:
llvm::raw_ostream &os() const {
return (*traceStream_);
}
/// If we're tracing to a file, and the number of accumulated
/// records has reached the limit kTraceRecordsToFlush, below,
/// flush the records to the file, and reset the accumulated records
/// to be empty.
void flushRecordsIfNecessary();
/// Assumes we're tracing to a file; flush accumulated records to
/// the file, and reset the accumulated records to be empty.
void flushRecords();
static constexpr unsigned kTraceRecordsToFlush = 100;
/// If we're tracing to a file, pointer to a stream onto
/// traceFilename_. Null otherwise.
std::unique_ptr<llvm::raw_ostream> traceStream_;
/// If we're tracing to a file, pointer to a JSONEmitter writting
/// into *traceStream_. Null otherwise.
std::unique_ptr<::hermes::JSONEmitter> json_;
/// The records currently being accumulated in the trace. If we are
/// tracing to a file, these will be only the records not yet
/// written to the file.
std::vector<std::unique_ptr<Record>> records_;
/// The id of the global object.
const ObjectID globalObjID_;
/// A table of strings to avoid repeated strings taking up memory. Similar to
/// the IdentifierTable, except it doesn't need to be collected (it stores
/// strings forever).
/// Strings are stored in the trace objects as an index into this table.
::hermes::StringSetVector stringTable_;
public:
/// @name Record classes
/// @{
/// A MarkerRecord is an event that simply records an interesting event that
/// is not necessarily meaningful to the interpreter. It comes with a tag that
/// says what type of marker it was.
struct MarkerRecord : public Record {
static constexpr RecordType type{RecordType::Marker};
const std::string tag_;
explicit MarkerRecord(TimeSinceStart time, const std::string &tag)
: Record(time), tag_(tag) {}
RecordType getType() const override {
return type;
}
protected:
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
bool operator==(const Record &that) const override;
};
/// A BeginExecJSRecord is an event where execution begins of JS source
/// code. This is not necessarily the first record, since native code can
/// inject values into the VM before any source code is run.
struct BeginExecJSRecord final : public Record {
static constexpr RecordType type{RecordType::BeginExecJS};
explicit BeginExecJSRecord(
TimeSinceStart time,
std::string sourceURL,
::hermes::SHA1 sourceHash)
: Record(time),
sourceURL_(std::move(sourceURL)),
sourceHash_(std::move(sourceHash)) {}
RecordType getType() const override {
return type;
}
const std::string &sourceURL() const {
return sourceURL_;
}
const ::hermes::SHA1 &sourceHash() const {
return sourceHash_;
}
private:
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
/// The URL providing the source file mapping for the file being executed.
/// Can be empty.
std::string sourceURL_;
/// A hash of the source that was executed. The source hash must match up
/// when the file is replayed.
/// The hash is optional, and will be all zeros if not provided.
::hermes::SHA1 sourceHash_;
};
struct ReturnMixin {
const TraceValue retVal_;
explicit ReturnMixin(TraceValue value) : retVal_(value) {}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const;
bool operator==(const ReturnMixin &that) const;
};
/// A EndExecJSRecord is an event where execution of JS source code stops.
/// This does not mean that the source code will never be entered again, just
/// that it has an entered a phase where it is waiting for native code to call
/// into the JS. This event is not guaranteed to be the last event, for the
/// aforementioned reason. The logged retVal is the result of the evaluation
/// ("undefined" in the majority of cases).
struct EndExecJSRecord final : public MarkerRecord, public ReturnMixin {
static constexpr RecordType type{RecordType::EndExecJS};
EndExecJSRecord(TimeSinceStart time, TraceValue retVal)
: MarkerRecord(time, "end_global_code"), ReturnMixin(retVal) {}
RecordType getType() const override {
return type;
}
bool operator==(const Record &that) const final;
virtual void toJSONInternal(
::hermes::JSONEmitter &json,
const SynthTrace &trace) const final;
std::vector<ObjectID> defs() const override {
auto defs = MarkerRecord::defs();
if (retVal_.isObject()) {
defs.push_back(decodeObject(retVal_));
}
return defs;
}
};
/// A CreateObjectRecord is an event where an empty object is created by the
/// native code.
struct CreateObjectRecord : public Record {
static constexpr RecordType type{RecordType::CreateObject};
const ObjectID objID_;
explicit CreateObjectRecord(TimeSinceStart time, ObjectID objID)
: Record(time), objID_(objID) {}
bool operator==(const Record &that) const override;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
return {objID_};
}
std::vector<ObjectID> uses() const override {
return {};
}
};
struct CreateHostObjectRecord final : public CreateObjectRecord {
static constexpr RecordType type{RecordType::CreateHostObject};
using CreateObjectRecord::CreateObjectRecord;
RecordType getType() const override {
return type;
}
};
struct CreateHostFunctionRecord final : public CreateObjectRecord {
static constexpr RecordType type{RecordType::CreateHostFunction};
const std::string functionName_;
const unsigned paramCount_;
CreateHostFunctionRecord(
TimeSinceStart time,
ObjectID objID,
std::string functionName,
unsigned paramCount)
: CreateObjectRecord(time, objID),
functionName_(std::move(functionName)),
paramCount_(paramCount) {}
bool operator==(const Record &that) const override;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
};
struct GetOrSetPropertyRecord : public Record {
const ObjectID objID_;
const std::string propName_;
const TraceValue value_;
explicit GetOrSetPropertyRecord(
TimeSinceStart time,
ObjectID objID,
const std::string &propName,
TraceValue value)
: Record(time), objID_(objID), propName_(propName), value_(value) {}
bool operator==(const Record &that) const final;
std::vector<ObjectID> uses() const override {
return {objID_};
}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
};
/// A GetPropertyRecord is an event where native code accesses the property
/// of a JS object.
struct GetPropertyRecord : public GetOrSetPropertyRecord {
static constexpr RecordType type{RecordType::GetProperty};
using GetOrSetPropertyRecord::GetOrSetPropertyRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = GetOrSetPropertyRecord::defs();
if (value_.isObject()) {
defs.push_back(decodeObject(value_));
}
return defs;
}
};
/// A SetPropertyRecord is an event where native code writes to the property
/// of a JS object.
struct SetPropertyRecord : public GetOrSetPropertyRecord {
static constexpr RecordType type{RecordType::SetProperty};
using GetOrSetPropertyRecord::GetOrSetPropertyRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = GetOrSetPropertyRecord::uses();
if (value_.isObject()) {
uses.push_back(decodeObject(value_));
}
return uses;
}
};
/// A HasPropertyRecord is an event where native code queries whether a
/// property exists on an object. (We don't care about the result because
/// it cannot influence the trace.)
struct HasPropertyRecord final : public Record {
static constexpr RecordType type{RecordType::HasProperty};
const ObjectID objID_;
const std::string propName_;
explicit HasPropertyRecord(
TimeSinceStart time,
ObjectID objID,
const std::string &propName)
: Record(time), objID_(objID), propName_(propName) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
return {objID_};
}
};
struct GetPropertyNamesRecord final : public Record {
static constexpr RecordType type{RecordType::GetPropertyNames};
const ObjectID objID_;
// Since getPropertyNames always returns an array, this can be an object id
// rather than a TraceValue.
const ObjectID propNamesID_;
explicit GetPropertyNamesRecord(
TimeSinceStart time,
ObjectID objID,
ObjectID propNamesID)
: Record(time), objID_(objID), propNamesID_(propNamesID) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
return {propNamesID_};
}
std::vector<ObjectID> uses() const override {
return {objID_};
}
};
/// A CreateArrayRecord is an event where a new array is created of a specific
/// length.
struct CreateArrayRecord final : public Record {
static constexpr RecordType type{RecordType::CreateArray};
const ObjectID objID_;
const size_t length_;
explicit CreateArrayRecord(
TimeSinceStart time,
ObjectID objID,
size_t length)
: Record(time), objID_(objID), length_(length) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
return {objID_};
}
};
struct ArrayReadOrWriteRecord : public Record {
const ObjectID objID_;
const size_t index_;
const TraceValue value_;
explicit ArrayReadOrWriteRecord(
TimeSinceStart time,
ObjectID objID,
size_t index,
TraceValue value)
: Record(time), objID_(objID), index_(index), value_(value) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
std::vector<ObjectID> uses() const override {
return {objID_};
}
};
/// An ArrayReadRecord is an event where a value was read from an index
/// of an array.
/// It is modeled separately from GetProperty because it is more efficient to
/// read from a numeric index on an array than a string.
struct ArrayReadRecord final : public ArrayReadOrWriteRecord {
static constexpr RecordType type{RecordType::ArrayRead};
using ArrayReadOrWriteRecord::ArrayReadOrWriteRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = ArrayReadOrWriteRecord::defs();
if (value_.isObject()) {
defs.push_back(decodeObject(value_));
}
return defs;
}
};
/// An ArrayWriteRecord is an event where a value was written into an index
/// of an array.
struct ArrayWriteRecord final : public ArrayReadOrWriteRecord {
static constexpr RecordType type{RecordType::ArrayWrite};
using ArrayReadOrWriteRecord::ArrayReadOrWriteRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = ArrayReadOrWriteRecord::uses();
if (value_.isObject()) {
uses.push_back(decodeObject(value_));
}
return uses;
}
};
struct CallRecord : public Record {
/// The functionID_ is the id of the function JS object that is called from
/// JS.
const ObjectID functionID_;
const TraceValue thisArg_;
/// The arguments given to a call (excluding the this parameter),
/// already JSON stringified.
const std::vector<TraceValue> args_;
explicit CallRecord(
TimeSinceStart time,
ObjectID functionID,
TraceValue thisArg,
const std::vector<TraceValue> &args)
: Record(time),
functionID_(functionID),
thisArg_(thisArg),
args_(args) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
std::vector<ObjectID> uses() const override {
// The function is used regardless of direction.
return {functionID_};
}
protected:
std::vector<ObjectID> getArgObjects() const {
std::vector<ObjectID> objs;
if (thisArg_.isObject()) {
objs.push_back(decodeObject(thisArg_));
}
for (const auto &arg : args_) {
if (arg.isObject()) {
objs.push_back(decodeObject(arg));
}
}
return objs;
}
};
/// A CallFromNativeRecord is an event where native code calls into a JS
/// function.
struct CallFromNativeRecord : public CallRecord {
static constexpr RecordType type{RecordType::CallFromNative};
using CallRecord::CallRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = CallRecord::uses();
auto objs = CallRecord::getArgObjects();
uses.insert(uses.end(), objs.begin(), objs.end());
return uses;
}
};
/// A ConstructFromNativeRecord is the same as \c CallFromNativeRecord, except
/// the function is called with the new operator.
struct ConstructFromNativeRecord final : public CallFromNativeRecord {
static constexpr RecordType type{RecordType::ConstructFromNative};
using CallFromNativeRecord::CallFromNativeRecord;
RecordType getType() const override {
return type;
}
};
/// A ReturnFromNativeRecord is an event where a native function returns to a
/// JS caller.
/// It pairs with \c CallToNativeRecord.
struct ReturnFromNativeRecord final : public Record, public ReturnMixin {
static constexpr RecordType type{RecordType::ReturnFromNative};
ReturnFromNativeRecord(TimeSinceStart time, TraceValue retVal)
: Record(time), ReturnMixin(retVal) {}
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = Record::uses();
if (retVal_.isObject()) {
uses.push_back(decodeObject(retVal_));
}
return uses;
}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
};
/// A ReturnToNativeRecord is an event where a JS function returns to a native
/// caller.
/// It pairs with \c CallFromNativeRecord.
struct ReturnToNativeRecord final : public Record, public ReturnMixin {
static constexpr RecordType type{RecordType::ReturnToNative};
ReturnToNativeRecord(TimeSinceStart time, TraceValue retVal)
: Record(time), ReturnMixin(retVal) {}
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = Record::defs();
if (retVal_.isObject()) {
defs.push_back(decodeObject(retVal_));
}
return defs;
}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
};
/// A CallToNativeRecord is an event where JS code calls into a natively
/// defined function.
struct CallToNativeRecord final : public CallRecord {
static constexpr RecordType type{RecordType::CallToNative};
using CallRecord::CallRecord;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = CallRecord::defs();
auto objs = CallRecord::getArgObjects();
defs.insert(defs.end(), objs.begin(), objs.end());
return defs;
}
};
struct GetOrSetPropertyNativeRecord : public Record {
const ObjectID hostObjectID_;
const std::string propName_;
explicit GetOrSetPropertyNativeRecord(
TimeSinceStart time,
ObjectID hostObjectID,
std::string propName)
: Record(time), hostObjectID_(hostObjectID), propName_(propName) {}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
std::vector<ObjectID> uses() const override {
return {hostObjectID_};
}
protected:
bool operator==(const Record &that) const override;
};
/// A GetPropertyNativeRecord is an event where JS tries to access a property
/// on a native object.
/// This needs to be modeled as a call with no arguments, since native code
/// can arbitrarily affect the JS heap during the accessor.
struct GetPropertyNativeRecord final : public GetOrSetPropertyNativeRecord {
static constexpr RecordType type{RecordType::GetPropertyNative};
using GetOrSetPropertyNativeRecord::GetOrSetPropertyNativeRecord;
RecordType getType() const override {
return type;
}
bool operator==(const Record &that) const final;
};
struct GetPropertyNativeReturnRecord final : public Record,
public ReturnMixin {
static constexpr RecordType type{RecordType::GetPropertyNativeReturn};
GetPropertyNativeReturnRecord(TimeSinceStart time, TraceValue retVal)
: Record(time), ReturnMixin(retVal) {}
RecordType getType() const override {
return type;
}
std::vector<ObjectID> uses() const override {
auto uses = Record::uses();
if (retVal_.isObject()) {
uses.push_back(decodeObject(retVal_));
}
return uses;
}
bool operator==(const Record &that) const final;
protected:
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
};
/// A SetPropertyNativeRecord is an event where JS code writes to the property
/// of a Native object.
/// This needs to be modeled as a call with one argument, since native code
/// can arbitrarily affect the JS heap during the accessor.
struct SetPropertyNativeRecord final : public GetOrSetPropertyNativeRecord {
static constexpr RecordType type{RecordType::SetPropertyNative};
TraceValue value_;
explicit SetPropertyNativeRecord(
TimeSinceStart time,
ObjectID hostObjectID,
std::string propName,
TraceValue value)
: GetOrSetPropertyNativeRecord(time, hostObjectID, propName),
value_(value) {}
bool operator==(const Record &that) const final;
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
RecordType getType() const override {
return type;
}
std::vector<ObjectID> defs() const override {
auto defs = GetOrSetPropertyNativeRecord::defs();
if (value_.isObject()) {
defs.push_back(decodeObject(value_));
}
return defs;
}
};
/// A SetPropertyNativeReturnRecord needs to record no extra information
struct SetPropertyNativeReturnRecord final : public Record {
static constexpr RecordType type{RecordType::SetPropertyNativeReturn};
using Record::Record;
RecordType getType() const override {
return type;
}
bool operator==(const Record &that) const final {
// Since there are no fields to compare, any two will always be the same.
return Record::operator==(that);
}
};
/// A GetNativePropertyNamesRecord records an event where JS asked for a list
/// of property names available on a host object. It records the object, and
/// the returned list of property names.
struct GetNativePropertyNamesRecord : public Record {
static constexpr RecordType type{RecordType::GetNativePropertyNames};
const ObjectID hostObjectID_;
explicit GetNativePropertyNamesRecord(
TimeSinceStart time,
ObjectID hostObjectID)
: Record(time), hostObjectID_(hostObjectID) {}
RecordType getType() const override {
return type;
}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
std::vector<ObjectID> uses() const override {
return {hostObjectID_};
}
bool operator==(const Record &that) const override;
};
/// A GetNativePropertyNamesReturnRecord records what property names were
/// returned by the GetNativePropertyNames query.
struct GetNativePropertyNamesReturnRecord final : public Record {
static constexpr RecordType type{RecordType::GetNativePropertyNamesReturn};
const std::vector<std::string> propNames_;
explicit GetNativePropertyNamesReturnRecord(
TimeSinceStart time,
const std::vector<std::string> &propNames)
: Record(time), propNames_(propNames) {}
RecordType getType() const override {
return type;
}
void toJSONInternal(::hermes::JSONEmitter &json, const SynthTrace &trace)
const override;
bool operator==(const Record &that) const override;
};
/// Completes writing of the trace to the trace stream. If writing
/// to a file, disables further writing to the file, or accumulation
/// of data.
void flushAndDisable(const ::hermes::vm::MockedEnvironment &env);
};
llvm::raw_ostream &operator<<(
llvm::raw_ostream &os,
SynthTrace::RecordType type);
std::istream &operator>>(std::istream &is, SynthTrace::RecordType &type);
} // namespace tracing
} // namespace hermes
} // namespace facebook
#endif // HERMESVM_API_TRACE
#endif // HERMES_SYNTHTRACE_H