481 lines
21 KiB
C++
481 lines
21 KiB
C++
// Emitter golden tests (byte-exact strings derived from TRACE_FORMAT.md section 4 and the
|
|
// mkfixture.py reference emitter) + a fixture file that oracle_emit.py regenerates with
|
|
// mkfixture's own emit_record() and compares byte for byte.
|
|
#include "shim/trace/emitter.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "check.h"
|
|
#include "harness.h"
|
|
|
|
using namespace shim::trace;
|
|
|
|
static std::string esc(const std::string& s) {
|
|
Buf b;
|
|
emit_esc(b, s);
|
|
return b.str();
|
|
}
|
|
static std::string escw(std::vector<std::uint16_t> u) {
|
|
Buf b;
|
|
emit_esc_w(b, u.data(), u.size());
|
|
return b.str();
|
|
}
|
|
static std::string tvs(const Tv& v) { return canonical(v); }
|
|
|
|
static const char* kSha512 = "110009dcee21620b166f3abfecb5eff7a873be729d1c2d53822e7acc5f34eb9b";
|
|
|
|
static void golden_strings() {
|
|
// section 4 rule 2; the same inputs test_tracecmp.py feeds mkfixture.esc()
|
|
CHECK_STR(esc("plain"), "\"plain\"");
|
|
CHECK_STR(esc("R\xe9sum\xe9"), "\"R\\u00e9sum\\u00e9\"");
|
|
CHECK_STR(esc("Tab\tKey"), "\"Tab\\tKey\"");
|
|
CHECK_STR(esc("Quote\"d"), "\"Quote\\\"d\"");
|
|
CHECK_STR(esc("Back\\slash"), "\"Back\\\\slash\"");
|
|
CHECK_STR(esc("nl\nx"), "\"nl\\nx\"");
|
|
CHECK_STR(esc("cr\rx"), "\"cr\\rx\"");
|
|
CHECK_STR(esc("\x01\x7f\xff\x80"), "\"\\u0001\\u007f\\u00ff\\u0080\"");
|
|
CHECK_STR(esc("\x08\x0c"), "\"\\u0008\\u000c\"");
|
|
CHECK_STR(esc(std::string("a\0b", 3)), "\"a\\u0000b\"");
|
|
CHECK_STR(escw({0x41, 0x20ac, 0x7e, 0x7f, 0xffff, 0x0a}), "\"A\\u20ac~\\u007f\\uffff\\n\"");
|
|
}
|
|
|
|
static void golden_numbers() {
|
|
CHECK_STR(tvs(tv::f32(1.5f)), "{\"t\":\"f32\",\"v\":1.5}");
|
|
CHECK_STR(tvs(tv::f32(0.1f)), "{\"t\":\"f32\",\"v\":0.100000001}");
|
|
CHECK_STR(tvs(tv::f32(-2.5e-7f)), "{\"t\":\"f32\",\"v\":-2.49999999e-07}");
|
|
CHECK_STR(tvs(tv::f32(1e30f)), "{\"t\":\"f32\",\"v\":1.00000002e+30}");
|
|
CHECK_STR(tvs(tv::f32(3.0f)), "{\"t\":\"f32\",\"v\":3}");
|
|
CHECK_STR(tvs(tv::f32(123456789.0f)), "{\"t\":\"f32\",\"v\":123456792}");
|
|
CHECK_STR(tvs(tv::f32(std::numeric_limits<float>::quiet_NaN())), "{\"t\":\"f32\",\"v\":\"nan\"}");
|
|
CHECK_STR(tvs(tv::f32(-std::numeric_limits<float>::infinity())), "{\"t\":\"f32\",\"v\":\"-inf\"}");
|
|
CHECK_STR(tvs(tv::f64(std::numeric_limits<double>::infinity())), "{\"t\":\"f64\",\"v\":\"inf\"}");
|
|
CHECK_STR(tvs(tv::f64(0.1)), "{\"t\":\"f64\",\"v\":0.10000000000000001}");
|
|
CHECK_STR(tvs(tv::f64(1e300)), "{\"t\":\"f64\",\"v\":1.0000000000000001e+300}");
|
|
CHECK_STR(tvs(tv::f64(0.0)), "{\"t\":\"f64\",\"v\":0}");
|
|
CHECK_STR(tvs(tv::f64(-0.0)), "{\"t\":\"f64\",\"v\":-0}");
|
|
|
|
CHECK_STR(tvs(tv::u64(18446744073709551615ull)), "{\"t\":\"u64\",\"v\":\"18446744073709551615\"}");
|
|
CHECK_STR(tvs(tv::u64(9007199254740992ull)), "{\"t\":\"u64\",\"v\":\"9007199254740992\"}"); // 2^53 -> string
|
|
CHECK_STR(tvs(tv::u64(9007199254740991ull)), "{\"t\":\"u64\",\"v\":9007199254740991}"); // 2^53-1 -> int
|
|
CHECK_STR(tvs(tv::i64(-9007199254740992ll)), "{\"t\":\"i64\",\"v\":\"-9007199254740992\"}");
|
|
CHECK_STR(tvs(tv::i64(-9007199254740991ll)), "{\"t\":\"i64\",\"v\":-9007199254740991}");
|
|
CHECK_STR(tvs(tv::i8(-128)), "{\"t\":\"i8\",\"v\":-128}");
|
|
CHECK_STR(tvs(tv::i16(32767)), "{\"t\":\"i16\",\"v\":32767}");
|
|
CHECK_STR(tvs(tv::i32(-3).named("value")), "{\"t\":\"i32\",\"v\":-3,\"n\":\"value\"}");
|
|
CHECK_STR(tvs(tv::u8(255)), "{\"t\":\"u8\",\"v\":255}");
|
|
CHECK_STR(tvs(tv::u16(65535)), "{\"t\":\"u16\",\"v\":65535}");
|
|
CHECK_STR(tvs(tv::u32(4294967295u)), "{\"t\":\"u32\",\"v\":4294967295}");
|
|
CHECK_STR(tvs(tv::boolean(true)), "{\"t\":\"bool\",\"v\":true}");
|
|
CHECK_STR(tvs(tv::null()), "{\"t\":\"null\",\"v\":null}");
|
|
CHECK_STR(tvs(tv::ptr(static_cast<std::uintptr_t>(0xa36fd0)).named("table")), "{\"t\":\"ptr\",\"v\":\"0x00a36fd0\",\"n\":\"table\"}");
|
|
CHECK_STR(tvs(tv::ptr(static_cast<std::uintptr_t>(0))), "{\"t\":\"ptr\",\"v\":\"0x00000000\"}");
|
|
CHECK_STR(tvs(tv::enum_(2, "MF_0").named("f")), "{\"t\":\"enum\",\"v\":2,\"name\":\"MF_0\",\"n\":\"f\"}");
|
|
CHECK_STR(tvs(tv::enum_(-1)), "{\"t\":\"enum\",\"v\":-1}");
|
|
CHECK_STR(tvs(tv::str("R\xe9sum\xe9").named("key")), "{\"t\":\"str\",\"v\":\"R\\u00e9sum\\u00e9\",\"n\":\"key\"}");
|
|
CHECK_STR(tvs(tv::str(nullptr)), "{\"t\":\"ptr\",\"v\":\"0x00000000\"}");
|
|
const std::uint16_t w[] = {0x41, 0x20ac};
|
|
CHECK_STR(tvs(tv::wstr(w, 2)), "{\"t\":\"wstr\",\"v\":\"A\\u20ac\"}");
|
|
CHECK_STR(tvs(tv::json("{\"a\":[1,2.5,\"x\"]}")), "{\"t\":\"json\",\"v\":{\"a\":[1,2.5,\"x\"]}}");
|
|
}
|
|
|
|
static void golden_bytes_and_containers() {
|
|
CHECK_STR(tvs(tv::bytes("\x00\xff", 2, 256)),
|
|
"{\"t\":\"bytes\",\"n\":2,\"sha256\":\"06eb7d6a69ee19e5fbdf749018d3d2abfa04bcbd1365db312eb86dc7169389b8\",\"hex\":\"00ff\"}");
|
|
CHECK_STR(tvs(tv::bytes("", 0, 256)),
|
|
"{\"t\":\"bytes\",\"n\":0,\"sha256\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"hex\":\"\"}");
|
|
std::vector<std::uint8_t> big;
|
|
for (int k = 0; k < 2; ++k)
|
|
for (int i = 0; i < 256; ++i) big.push_back(static_cast<std::uint8_t>(i));
|
|
CHECK_STR(tvs(tv::bytes(big.data(), big.size(), 256)),
|
|
std::string("{\"t\":\"bytes\",\"n\":512,\"sha256\":\"") + kSha512 +
|
|
"\",\"head\":\"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"}");
|
|
// exactly inline_max stays inline; inline_max+1 does not
|
|
CHECK(tvs(tv::bytes(big.data(), 256, 256)).find("\"hex\"") != std::string::npos);
|
|
CHECK(tvs(tv::bytes(big.data(), 257, 256)).find("\"head\"") != std::string::npos);
|
|
// bytes never carry a name even if one is set
|
|
CHECK_STR(tvs(tv::bytes("", 0, 256).named("x")),
|
|
"{\"t\":\"bytes\",\"n\":0,\"sha256\":\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\"hex\":\"\"}");
|
|
|
|
std::vector<Tv> items;
|
|
items.push_back(tv::i32(1));
|
|
items.push_back(tv::str("a"));
|
|
CHECK_STR(tvs(tv::list(items)), "{\"t\":\"list\",\"v\":[{\"t\":\"i32\",\"v\":1},{\"t\":\"str\",\"v\":\"a\"}]}");
|
|
CHECK_STR(tvs(tv::set(items).named("s")), "{\"t\":\"set\",\"v\":[{\"t\":\"i32\",\"v\":1},{\"t\":\"str\",\"v\":\"a\"}],\"n\":\"s\"}");
|
|
CHECK_STR(tvs(tv::list({})), "{\"t\":\"list\",\"v\":[]}");
|
|
Tv st = tv::struct_();
|
|
st.add("id", tv::u32(7)).add("na\"me", tv::str("w7"));
|
|
CHECK_STR(tvs(st), "{\"t\":\"struct\",\"v\":{\"id\":{\"t\":\"u32\",\"v\":7},\"na\\\"me\":{\"t\":\"str\",\"v\":\"w7\"}}}");
|
|
CHECK_STR(tvs(tv::struct_()), "{\"t\":\"struct\",\"v\":{}}");
|
|
}
|
|
|
|
static void golden_records() {
|
|
Record r;
|
|
r.ts = 1;
|
|
r.hook = "X";
|
|
r.mode = Mode::Trace;
|
|
r.call_id = 900;
|
|
r.thread = 1;
|
|
Buf b;
|
|
emit_record(b, r);
|
|
// == mkfixture.invalid_lines()[0], the harness's own minimal valid record
|
|
CHECK_STR(b.str(), "{\"ts\":1,\"hook\":\"X\",\"mode\":\"trace\",\"call_id\":900,\"thread\":1,\"args\":[],\"ret\":null,\"side\":{}}\n");
|
|
|
|
Record c;
|
|
c.ts = 1;
|
|
c.hook = "X";
|
|
c.mode = Mode::Compare;
|
|
c.call_id = 4;
|
|
c.thread = 1;
|
|
c.depth = 0;
|
|
c.args.push_back(tv::i32(1).named("a"));
|
|
c.ret = tv::boolean(true);
|
|
SideEntry se;
|
|
se.name = "r";
|
|
se.before_kind = SideEntry::Value;
|
|
se.before = tv::null();
|
|
se.after = tv::i32(2);
|
|
c.side.push_back(se);
|
|
SideEntry sn;
|
|
sn.name = "q";
|
|
sn.before_kind = SideEntry::IsNull;
|
|
sn.after = tv::i32(0);
|
|
c.side.push_back(sn);
|
|
c.has_ours = true;
|
|
c.ours_ret = tv::boolean(false);
|
|
c.ours_side.push_back({"r", tv::i32(3)});
|
|
c.ours_side.push_back({"q", tv::i32(0)});
|
|
c.diverged = 1;
|
|
c.has_diff = true;
|
|
DiffEntry d;
|
|
d.path = "ret";
|
|
d.why = "exact";
|
|
d.orig = tv::boolean(true);
|
|
d.ours = tv::boolean(false);
|
|
c.diff.push_back(d);
|
|
DiffEntry d2;
|
|
d2.path = "side.r.after";
|
|
d2.why = "hash";
|
|
d2.first_diff_offset = 1;
|
|
d2.note = "x";
|
|
c.diff.push_back(d2);
|
|
DiffEntry d3;
|
|
d3.path = "side.x.after";
|
|
d3.why = "len";
|
|
d3.orig_raw = "3";
|
|
d3.ours_raw = "2";
|
|
c.diff.push_back(d3);
|
|
c.err = "e";
|
|
c.note = "n\xe9";
|
|
Buf cb;
|
|
emit_record(cb, c);
|
|
CHECK_STR(cb.str(),
|
|
"{\"ts\":1,\"hook\":\"X\",\"mode\":\"compare\",\"call_id\":4,\"thread\":1,\"depth\":0,"
|
|
"\"args\":[{\"t\":\"i32\",\"v\":1,\"n\":\"a\"}],\"ret\":{\"t\":\"bool\",\"v\":true},"
|
|
"\"side\":{\"r\":{\"before\":{\"t\":\"null\",\"v\":null},\"after\":{\"t\":\"i32\",\"v\":2}},"
|
|
"\"q\":{\"before\":null,\"after\":{\"t\":\"i32\",\"v\":0}}},"
|
|
"\"ours\":{\"ret\":{\"t\":\"bool\",\"v\":false},\"side\":{\"r\":{\"after\":{\"t\":\"i32\",\"v\":3}},"
|
|
"\"q\":{\"after\":{\"t\":\"i32\",\"v\":0}}}},\"diverged\":true,"
|
|
"\"diff\":[{\"path\":\"ret\",\"why\":\"exact\",\"orig\":{\"t\":\"bool\",\"v\":true},\"ours\":{\"t\":\"bool\",\"v\":false}},"
|
|
"{\"path\":\"side.r.after\",\"why\":\"hash\",\"orig\":null,\"ours\":null,\"first_diff_offset\":1,\"note\":\"x\"},"
|
|
"{\"path\":\"side.x.after\",\"why\":\"len\",\"orig\":3,\"ours\":2}],"
|
|
"\"err\":\"e\",\"note\":\"n\\u00e9\"}\n");
|
|
|
|
// replace record: no ours/diverged/diff; empty diff list still emits "diff":[] when flagged
|
|
Record p;
|
|
p.ts = 22;
|
|
p.hook = "Mars::ParseBlock";
|
|
p.mode = Mode::Replace;
|
|
p.call_id = 7;
|
|
p.thread = 3;
|
|
p.has_diff = true;
|
|
p.diverged = 0;
|
|
Buf pb;
|
|
emit_record(pb, p);
|
|
CHECK_STR(pb.str(), "{\"ts\":22,\"hook\":\"Mars::ParseBlock\",\"mode\":\"replace\",\"call_id\":7,\"thread\":3,\"args\":[],\"ret\":null,\"side\":{},\"diverged\":false,\"diff\":[]}\n");
|
|
|
|
Meta m;
|
|
m.build = "sots-engine test";
|
|
m.exe_sha256 = std::string(64, '0');
|
|
m.started = "2026-09-07T00:00:00Z";
|
|
m.inline_max = 256;
|
|
HookPolicy pa;
|
|
HookPolicy pb2;
|
|
pb2.ftol = 1e-6;
|
|
pb2.ftol_kind = "rel";
|
|
pb2.ptr_exact = true;
|
|
pb2.unordered = {"ret.v.items"};
|
|
HookMeta ha;
|
|
ha.policy = pa;
|
|
ha.coverage.complete("nothing outside the declared regions");
|
|
HookMeta hb;
|
|
hb.policy = pb2;
|
|
hb.coverage.unmodelled("appends to the owner's event list", Risk::High, "no region reaches it",
|
|
"guard:player");
|
|
m.hooks.emplace_back("Hook::A", ha);
|
|
m.hooks.emplace_back("Mars::ParseBlock", hb);
|
|
Buf mb;
|
|
emit_meta(mb, m);
|
|
CHECK_STR(mb.str(),
|
|
"{\"meta\":{\"format\":1,\"build\":\"sots-engine test\",\"exe_sha256\":\"" + std::string(64, '0') +
|
|
"\",\"started\":\"2026-09-07T00:00:00Z\",\"inline_max\":256,\"hooks\":{"
|
|
"\"Hook::A\":{\"ftol\":0,\"ftol_kind\":\"abs\",\"ptr\":\"ignore\","
|
|
"\"coverage\":{\"state\":\"complete\",\"why\":\"nothing outside the declared regions\","
|
|
"\"unmodelled\":[]}},"
|
|
"\"Mars::ParseBlock\":{\"ftol\":9.9999999999999995e-07,\"ftol_kind\":\"rel\",\"ptr\":\"exact\",\"unordered\":[\"ret.v.items\"],"
|
|
"\"coverage\":{\"state\":\"partial\",\"why\":\"\",\"unmodelled\":[{"
|
|
"\"what\":\"appends to the owner's event list\",\"risk\":\"high\","
|
|
"\"why\":\"no region reaches it\",\"mitigation\":\"guard:player\"}]}}}}}\n");
|
|
Meta m0;
|
|
Buf m0b;
|
|
emit_meta(m0b, m0);
|
|
CHECK_STR(m0b.str(), "{\"meta\":{\"format\":1,\"build\":\"\",\"exe_sha256\":\"\",\"started\":\"\",\"inline_max\":256,\"hooks\":{}}}\n");
|
|
|
|
// The coverage block on a record: guards present, one undeclared span, honest total.
|
|
Record cv;
|
|
cv.hook = "Hook::A";
|
|
cv.mode = Mode::Compare;
|
|
cv.call_id = 9;
|
|
cv.thread = 3;
|
|
cv.ts = 30;
|
|
cv.has_coverage = true;
|
|
cv.guards.push_back("player");
|
|
cv.undeclared.push_back(UndeclaredWrite{"player", 0x2b0, 4});
|
|
cv.undeclared_total = 2;
|
|
Buf cvb;
|
|
emit_record(cvb, cv);
|
|
CHECK_STR(cvb.str(),
|
|
"{\"ts\":30,\"hook\":\"Hook::A\",\"mode\":\"compare\",\"call_id\":9,\"thread\":3,"
|
|
"\"args\":[],\"ret\":null,\"side\":{},"
|
|
"\"coverage\":{\"guards\":[\"player\"],"
|
|
"\"undeclared\":[{\"region\":\"player\",\"off\":688,\"len\":4}],\"n\":2}}\n");
|
|
}
|
|
|
|
static void buf_growth() {
|
|
Buf b;
|
|
std::string big(100000, 'z');
|
|
b.put(big.data(), big.size());
|
|
b.printf("%s", big.c_str()); // > 128 chars: takes the grow path
|
|
CHECK_EQ(b.size(), static_cast<std::size_t>(200000));
|
|
CHECK(b.ok());
|
|
b.clear();
|
|
CHECK_EQ(b.size(), static_cast<std::size_t>(0));
|
|
// a 1 MiB bytes value inlines only when inline_max allows
|
|
std::vector<std::uint8_t> mb(1 << 20, 7);
|
|
Buf big1;
|
|
emit_tv(big1, tv::bytes(mb.data(), mb.size(), 1 << 20));
|
|
CHECK_EQ(big1.size(), static_cast<std::size_t>(2 * (1 << 20)) + std::string("{\"t\":\"bytes\",\"n\":1048576,\"sha256\":\"\",\"hex\":\"\"}").size() + 64);
|
|
CHECK(big1.ok());
|
|
}
|
|
|
|
// ---- oracle fixture: mirrored line for line in oracle_emit.py ----------------------------------
|
|
|
|
static std::vector<std::uint8_t> range_bytes(int n) {
|
|
std::vector<std::uint8_t> v;
|
|
for (int i = 0; i < n; ++i) v.push_back(static_cast<std::uint8_t>(i));
|
|
return v;
|
|
}
|
|
|
|
static void write_oracle_fixture(const std::string& path) {
|
|
Buf out;
|
|
Meta m;
|
|
m.build = "shim-trace test";
|
|
m.exe_sha256 = std::string(64, '0');
|
|
m.started = "2026-09-07T00:00:00Z";
|
|
m.inline_max = 256;
|
|
HookPolicy pa;
|
|
HookPolicy pb;
|
|
pb.ftol = 1e-6;
|
|
pb.ftol_kind = "rel";
|
|
pb.ptr_exact = true;
|
|
pb.unordered = {"ret.v.items"};
|
|
HookMeta ha;
|
|
ha.policy = pa;
|
|
ha.coverage.complete("nothing outside the declared regions");
|
|
HookMeta hb;
|
|
hb.policy = pb;
|
|
hb.coverage.unmodelled("appends to the owner's event list", Risk::High, "no region reaches it",
|
|
"guard:player");
|
|
m.hooks.emplace_back("Hook::A", ha);
|
|
m.hooks.emplace_back("Mars::ParseBlock", hb);
|
|
emit_meta(out, m);
|
|
|
|
{ // rec0: str/i32/ptr args, bool ret, inline bytes region before/after
|
|
Record r;
|
|
r.ts = 1000; r.hook = "CfgVar_RegisterKey"; r.mode = Mode::Trace; r.call_id = 0; r.thread = 4120; r.depth = 0;
|
|
r.args.push_back(tv::str("R\xe9sum\xe9").named("key"));
|
|
r.args.push_back(tv::i32(-3).named("value"));
|
|
r.args.push_back(tv::ptr(static_cast<std::uintptr_t>(0x00a36fd0)).named("table"));
|
|
r.ret = tv::boolean(true);
|
|
auto fwd = range_bytes(16);
|
|
auto rev = fwd;
|
|
std::reverse(rev.begin(), rev.end());
|
|
SideEntry s;
|
|
s.name = "cfg_table"; s.before_kind = SideEntry::Value;
|
|
s.before = tv::bytes(fwd.data(), fwd.size(), 256);
|
|
s.after = tv::bytes(rev.data(), rev.size(), 256);
|
|
r.side.push_back(s);
|
|
emit_record(out, r);
|
|
}
|
|
{ // rec1: struct / list / set / enum / big ints / null / wstr
|
|
Record r;
|
|
r.ts = 1037; r.hook = "Manifest_Load"; r.mode = Mode::Trace; r.call_id = 1; r.thread = 4120;
|
|
r.args.push_back(tv::str("Weapons/_weapons.txt").named("path"));
|
|
r.ret = tv::i32(2).named("count");
|
|
Tv reg = tv::struct_();
|
|
std::vector<Tv> entries;
|
|
for (unsigned id : {7u, 9u}) {
|
|
Tv e = tv::struct_();
|
|
e.add("id", tv::u32(id));
|
|
e.add("name", tv::str(("w" + std::to_string(id) + ".weapon").c_str()));
|
|
entries.push_back(e);
|
|
}
|
|
reg.add("entries", tv::list(entries));
|
|
reg.add("deleted", tv::set({tv::u32(501), tv::u32(550), tv::u32(599)}));
|
|
reg.add("flags", tv::enum_(2, "MF_0"));
|
|
reg.add("big", tv::u64((1ull << 60) + 1));
|
|
reg.add("small", tv::u64(5));
|
|
reg.add("edge", tv::u64(1ull << 53));
|
|
reg.add("neg", tv::i64(-(1ll << 60)));
|
|
reg.add("nothing", tv::null());
|
|
const std::uint16_t w[] = {0x41, 0x20ac, 0x0000};
|
|
reg.add("w", tv::wstr(w, 3));
|
|
SideEntry s;
|
|
s.name = "registry";
|
|
s.after = reg;
|
|
r.side.push_back(s);
|
|
emit_record(out, r);
|
|
}
|
|
{ // rec2: every escape, json ret, f32/f64 lists, nan, before:null, hashed region
|
|
Record r;
|
|
r.ts = 1074; r.hook = "Mars::ParseBlock"; r.mode = Mode::Trace; r.call_id = 2; r.thread = 4124; r.depth = 1;
|
|
r.args.push_back(tv::str("Tab\tKey Quote\"d Back\\slash nl\nx cr\rx \x01\x7f\xff \x80").named("text"));
|
|
r.args.push_back(tv::u32(2).named("len"));
|
|
r.ret = tv::json("{\"weapon\":{\"count\":3,\"damage\":1.5,\"name\":\"Laser\\u00e9\",\"ok\":true,\"range\":2.25,\"tags\":[\"a\",\"b\"]}}");
|
|
SideEntry sc;
|
|
sc.name = "scratch";
|
|
sc.after = tv::list({tv::f32(1.5f), tv::f32(0.1f), tv::f32(-2.5e-7f), tv::f32(1e30f)});
|
|
r.side.push_back(sc);
|
|
SideEntry nb;
|
|
nb.name = "nanbox";
|
|
nb.after = tv::f32(std::numeric_limits<float>::quiet_NaN());
|
|
r.side.push_back(nb);
|
|
SideEntry inf;
|
|
inf.name = "infs";
|
|
inf.after = tv::list({tv::f64(std::numeric_limits<double>::infinity()), tv::f64(-std::numeric_limits<double>::infinity()),
|
|
tv::f64(0.1), tv::f64(1e300), tv::f64(0.0)});
|
|
r.side.push_back(inf);
|
|
auto big = range_bytes(256);
|
|
big.insert(big.end(), big.begin(), big.end());
|
|
SideEntry bl;
|
|
bl.name = "blob"; bl.before_kind = SideEntry::IsNull;
|
|
bl.after = tv::bytes(big.data(), big.size(), 256);
|
|
r.side.push_back(bl);
|
|
emit_record(out, r);
|
|
}
|
|
{ // rec3: compare, clean, small ints
|
|
Record r;
|
|
r.ts = 1111; r.hook = "CfgVar_RegisterKey"; r.mode = Mode::Compare; r.call_id = 3; r.thread = 4120;
|
|
r.args = {tv::i8(-128), tv::i16(32767), tv::u8(255), tv::u16(65535), tv::u64((1ull << 53) - 1)};
|
|
r.ret = tv::null();
|
|
r.has_ours = true;
|
|
r.ours_ret = tv::null();
|
|
r.diverged = 0;
|
|
r.has_diff = true;
|
|
emit_record(out, r);
|
|
}
|
|
{ // rec4: compare with divergences + note
|
|
Record r;
|
|
r.ts = 1148; r.hook = "Manifest_Load"; r.mode = Mode::Compare; r.call_id = 4; r.thread = 1;
|
|
r.ret = tv::i32(1);
|
|
SideEntry s;
|
|
s.name = "r";
|
|
s.after = tv::bytes("\x01\x02", 2, 256);
|
|
r.side.push_back(s);
|
|
r.has_ours = true;
|
|
r.ours_ret = tv::i32(2);
|
|
r.ours_side.push_back({"r", tv::bytes("\x01\x03", 2, 256)});
|
|
r.diverged = 1;
|
|
r.has_diff = true;
|
|
DiffEntry d1;
|
|
d1.path = "ret"; d1.why = "exact"; d1.orig = tv::i32(1); d1.ours = tv::i32(2);
|
|
r.diff.push_back(d1);
|
|
DiffEntry d2;
|
|
d2.path = "side.r.after"; d2.why = "hash"; d2.orig = tv::bytes("\x01\x02", 2, 256); d2.ours = tv::bytes("\x01\x03", 2, 256);
|
|
d2.first_diff_offset = 1;
|
|
r.diff.push_back(d2);
|
|
r.note = "hello";
|
|
emit_record(out, r);
|
|
}
|
|
{ // rec5: compare err (no ours)
|
|
Record r;
|
|
r.ts = 1185; r.hook = "X"; r.mode = Mode::Compare; r.call_id = 5; r.thread = 1;
|
|
r.diverged = 1;
|
|
r.has_diff = true;
|
|
DiffEntry d;
|
|
d.path = "call"; d.why = "err"; d.ours_raw = "\"ours threw\"";
|
|
r.diff.push_back(d);
|
|
r.err = "ours threw";
|
|
emit_record(out, r);
|
|
}
|
|
{ // rec6: replace, now with a coverage block (guards seen, nothing undeclared moved)
|
|
Record r;
|
|
r.ts = 1222; r.hook = "Y"; r.mode = Mode::Replace; r.call_id = 6; r.thread = 2;
|
|
r.args.push_back(tv::boolean(false).named("flag"));
|
|
r.ret = tv::ptr(static_cast<std::uintptr_t>(0));
|
|
r.has_coverage = true;
|
|
r.guards.push_back("player");
|
|
emit_record(out, r);
|
|
}
|
|
{ // rec7: compare with undeclared writes the guard caught
|
|
Record r;
|
|
r.ts = 1259; r.hook = "Y"; r.mode = Mode::Compare; r.call_id = 7; r.thread = 2;
|
|
r.ret = tv::null();
|
|
r.has_ours = true;
|
|
r.ours_ret = tv::null();
|
|
r.diverged = 0;
|
|
r.has_diff = true;
|
|
r.has_coverage = true;
|
|
r.guards.push_back("player");
|
|
r.guards.push_back("tree_header");
|
|
r.undeclared.push_back(UndeclaredWrite{"player", 672, 12});
|
|
r.undeclared.push_back(UndeclaredWrite{"player", 688, 4});
|
|
r.undeclared_total = 3;
|
|
emit_record(out, r);
|
|
}
|
|
CHECK(out.ok());
|
|
std::FILE* f = std::fopen(path.c_str(), "wb");
|
|
CHECK(f != nullptr);
|
|
if (f) {
|
|
std::fwrite(out.data(), 1, out.size(), f);
|
|
std::fclose(f);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
golden_strings();
|
|
golden_numbers();
|
|
golden_bytes_and_containers();
|
|
golden_records();
|
|
buf_growth();
|
|
|
|
const std::string fixture = argc > 1 ? argv[1] : "emitter_oracle.jsonl";
|
|
write_oracle_fixture(fixture);
|
|
std::printf("oracle fixture: %s\n", fixture.c_str());
|
|
if (tracetest::harness_present()) {
|
|
// byte-exact against mkfixture.emit_record / emit_meta
|
|
const std::string script = argc > 2 ? argv[2] : "oracle_emit.py";
|
|
const int rc = tracetest::run_python(script, tracetest::harness_dir() + " " + fixture);
|
|
CHECK_EQ(rc, 0);
|
|
// and the harness reads it back as valid, with the injected divergence counted
|
|
const int tc = tracetest::run_tracecmp(fixture);
|
|
CHECK_EQ(tc, 1);
|
|
} else {
|
|
std::printf(" SKIP python oracle (harness absent)\n");
|
|
}
|
|
return tracetest::finish("shim_trace_emitter");
|
|
}
|