#include "shim/trace/emitter.h" #include #include #include #include #include #include "shim/trace/sha256.h" namespace shim::trace { // ---- modes ---------------------------------------------------------------------------------- const char* mode_name(Mode m) { switch (m) { case Mode::Off: return "off"; case Mode::Trace: return "trace"; case Mode::Compare: return "compare"; case Mode::Replace: return "replace"; } return "off"; } bool parse_mode(const char* text, Mode& out) { if (!text) return false; if (std::strcmp(text, "off") == 0) { out = Mode::Off; return true; } if (std::strcmp(text, "trace") == 0) { out = Mode::Trace; return true; } if (std::strcmp(text, "compare") == 0) { out = Mode::Compare; return true; } if (std::strcmp(text, "replace") == 0) { out = Mode::Replace; return true; } return false; } // ---- typed values ----------------------------------------------------------------------------- const char* Tv::type_name() const { switch (kind) { case Bool: return "bool"; case Int: return itype; case Float: return f32 ? "f32" : "f64"; case Str: return "str"; case WStr: return "wstr"; case Ptr: return "ptr"; case Enum: return "enum"; case Null: return "null"; case Bytes: return "bytes"; case List: return "list"; case Set: return "set"; case Struct: return "struct"; case Json: return "json"; } return "null"; } Tv& Tv::add(const char* key, Tv v) { keys.emplace_back(key); items.push_back(std::move(v)); return *this; } namespace tv { namespace { Tv make_int(const char* t, long long v) { Tv r; r.kind = Tv::Int; r.itype = t; r.i = v; return r; } Tv make_uint(const char* t, unsigned long long v) { Tv r; r.kind = Tv::Int; r.itype = t; r.is_unsigned = true; r.u = v; return r; } } // namespace Tv boolean(bool v) { Tv r; r.kind = Tv::Bool; r.b = v; return r; } Tv i8(std::int8_t v) { return make_int("i8", v); } Tv i16(std::int16_t v) { return make_int("i16", v); } Tv i32(std::int32_t v) { return make_int("i32", v); } Tv i64(std::int64_t v) { return make_int("i64", v); } Tv u8(std::uint8_t v) { return make_uint("u8", v); } Tv u16(std::uint16_t v) { return make_uint("u16", v); } Tv u32(std::uint32_t v) { return make_uint("u32", v); } Tv u64(std::uint64_t v) { return make_uint("u64", v); } Tv f32(float v) { Tv r; r.kind = Tv::Float; r.f32 = true; r.f = v; return r; } Tv f64(double v) { Tv r; r.kind = Tv::Float; r.f = v; return r; } Tv str(const char* s) { if (!s) return ptr(static_cast(0)); return str(s, std::strlen(s)); } Tv str(const void* bytes, std::size_t n) { Tv r; r.kind = Tv::Str; r.s.assign(static_cast(bytes), n); return r; } Tv wstr(const std::uint16_t* units, std::size_t n) { Tv r; r.kind = Tv::WStr; r.w.assign(units, units + n); return r; } Tv wstr(const wchar_t* s) { if (!s) return ptr(static_cast(0)); Tv r; r.kind = Tv::WStr; for (; *s; ++s) r.w.push_back(static_cast(*s)); return r; } Tv ptr(const void* p) { return ptr(reinterpret_cast(p)); } Tv ptr(std::uintptr_t p) { Tv r; r.kind = Tv::Ptr; r.p = p; return r; } Tv enum_(long long v, const char* symbolic) { Tv r; r.kind = Tv::Enum; r.i = v; if (symbolic) r.s = symbolic; return r; } Tv null() { return Tv(); } Tv bytes(const void* data, std::size_t n, std::size_t inline_max) { Tv r; r.kind = Tv::Bytes; r.blen = n; Sha256::digest_hex(data, n, r.sha); const std::uint8_t* p = static_cast(data); if (n <= inline_max) { r.inline_full = true; r.data.assign(p, p + n); } else { r.data.assign(p, p + 32); } return r; } Tv list(std::vector items) { Tv r; r.kind = Tv::List; r.items = std::move(items); return r; } Tv set(std::vector items) { Tv r; r.kind = Tv::Set; r.items = std::move(items); return r; } Tv struct_() { Tv r; r.kind = Tv::Struct; return r; } Tv json(const char* canonical_text) { Tv r; r.kind = Tv::Json; r.s = canonical_text; return r; } } // namespace tv // ---- output buffer -------------------------------------------------------------------------------- Buf::Buf() { grow(4096); } Buf::~Buf() { std::free(data_); } bool Buf::grow(std::size_t need) { if (need <= cap_) return true; std::size_t ncap = cap_ ? cap_ : 4096; while (ncap < need) ncap *= 2; char* nd = static_cast(std::realloc(data_, ncap)); if (!nd) { overflow_ = true; return false; } data_ = nd; cap_ = ncap; return true; } void Buf::put(char c) { if (len_ + 1 > cap_ && !grow(len_ + 1)) return; data_[len_++] = c; } void Buf::put(const void* bytes, std::size_t n) { if (!n) return; if (len_ + n > cap_ && !grow(len_ + n)) return; std::memcpy(data_ + len_, bytes, n); len_ += n; } void Buf::puts(const char* s) { put(s, std::strlen(s)); } void Buf::printf(const char* fmt, ...) { char tmp[128]; va_list ap; va_start(ap, fmt); const int n = std::vsnprintf(tmp, sizeof tmp, fmt, ap); va_end(ap); if (n < 0) { overflow_ = true; return; } if (static_cast(n) < sizeof tmp) { put(tmp, static_cast(n)); return; } if (!grow(len_ + static_cast(n) + 1)) return; va_start(ap, fmt); std::vsnprintf(data_ + len_, static_cast(n) + 1, fmt, ap); va_end(ap); len_ += static_cast(n); } void Buf::clear() { len_ = 0; overflow_ = false; } // ---- escaping / numbers ------------------------------------------------------------------------- namespace { const char* const kHex = "0123456789abcdef"; inline void put_u_escape(Buf& out, unsigned unit) { char e[6] = {'\\', 'u', kHex[(unit >> 12) & 15], kHex[(unit >> 8) & 15], kHex[(unit >> 4) & 15], kHex[unit & 15]}; out.put(e, 6); } // One unit of either string flavour (bytes for str, UTF-16 units for wstr): mkfixture.esc(). inline void put_unit(Buf& out, unsigned unit) { switch (unit) { case '"': out.put("\\\"", 2); return; case '\\': out.put("\\\\", 2); return; case '\n': out.put("\\n", 2); return; case '\r': out.put("\\r", 2); return; case '\t': out.put("\\t", 2); return; default: if (unit < 0x20 || unit >= 0x7f) put_u_escape(out, unit); else out.put(static_cast(unit)); } } void put_hex(Buf& out, const std::uint8_t* p, std::size_t n) { for (std::size_t i = 0; i < n; ++i) { out.put(kHex[p[i] >> 4]); out.put(kHex[p[i] & 15]); } } const long long kTwo53 = 1LL << 53; } // namespace void emit_esc(Buf& out, const char* bytes, std::size_t n) { out.put('"'); for (std::size_t i = 0; i < n; ++i) put_unit(out, static_cast(bytes[i])); out.put('"'); } void emit_esc(Buf& out, const std::string& s) { emit_esc(out, s.data(), s.size()); } void emit_esc_w(Buf& out, const std::uint16_t* units, std::size_t n) { out.put('"'); for (std::size_t i = 0; i < n; ++i) put_unit(out, units[i]); out.put('"'); } // Integers above 2^53 in magnitude go out as decimal strings (section 3): mkfixture.u64(). void emit_int(Buf& out, long long v) { const bool quote = v >= kTwo53 || v <= -kTwo53; if (quote) out.put('"'); out.printf("%lld", v); if (quote) out.put('"'); } void emit_uint(Buf& out, unsigned long long v) { const bool quote = v >= static_cast(kTwo53); if (quote) out.put('"'); out.printf("%llu", v); if (quote) out.put('"'); } // mkfixture.fmt_num() for floats. void emit_float(Buf& out, double v, bool f32) { if (std::isnan(v)) { out.puts("\"nan\""); return; } if (std::isinf(v)) { out.puts(v > 0 ? "\"inf\"" : "\"-inf\""); return; } out.printf(f32 ? "%.9g" : "%.17g", v); } // ---- typed values ---------------------------------------------------------------------------------- // mkfixture.emit_tv(): {"t":..., ("n","sha256","hex"|"head") | "v":... [,"name":...]} [,"n":...] void emit_tv(Buf& out, const Tv& v) { out.puts("{\"t\":"); emit_esc(out, v.type_name(), std::strlen(v.type_name())); if (v.kind == Tv::Bytes) { out.printf(",\"n\":%llu,\"sha256\":\"", static_cast(v.blen)); out.puts(v.sha); out.puts(v.inline_full ? "\",\"hex\":\"" : "\",\"head\":\""); put_hex(out, v.data.data(), v.data.size()); out.puts("\"}"); return; } out.puts(",\"v\":"); switch (v.kind) { case Tv::Bool: out.puts(v.b ? "true" : "false"); break; case Tv::Int: if (v.is_unsigned) emit_uint(out, v.u); else emit_int(out, v.i); break; case Tv::Float: emit_float(out, v.f, v.f32); break; case Tv::Str: emit_esc(out, v.s); break; case Tv::WStr: emit_esc_w(out, v.w.data(), v.w.size()); break; case Tv::Ptr: out.printf("\"0x%08llx\"", static_cast(v.p)); break; case Tv::Enum: out.printf("%lld", v.i); break; case Tv::Null: out.puts("null"); break; case Tv::List: case Tv::Set: out.put('['); for (std::size_t i = 0; i < v.items.size(); ++i) { if (i) out.put(','); emit_tv(out, v.items[i]); } out.put(']'); break; case Tv::Struct: out.put('{'); for (std::size_t i = 0; i < v.items.size(); ++i) { if (i) out.put(','); emit_esc(out, v.keys[i]); out.put(':'); emit_tv(out, v.items[i]); } out.put('}'); break; case Tv::Json: out.puts(v.s.c_str()); break; case Tv::Bytes: break; // handled above } if (v.kind == Tv::Enum && !v.s.empty()) { out.puts(",\"name\":"); emit_esc(out, v.s); } if (!v.name.empty()) { out.puts(",\"n\":"); emit_esc(out, v.name); } out.put('}'); } std::string canonical(const Tv& v) { Buf b; emit_tv(b, v); return b.str(); } // ---- records ---------------------------------------------------------------------------------------- namespace { void emit_side(Buf& out, const std::vector& side) { out.put('{'); for (std::size_t i = 0; i < side.size(); ++i) { const SideEntry& e = side[i]; if (i) out.put(','); emit_esc(out, e.name); out.puts(":{"); if (e.before_kind == SideEntry::IsNull) { out.puts("\"before\":null,"); } else if (e.before_kind == SideEntry::Value) { out.puts("\"before\":"); emit_tv(out, e.before); out.put(','); } out.puts("\"after\":"); emit_tv(out, e.after); out.put('}'); } out.put('}'); } void emit_ours_side(Buf& out, const std::vector& side) { out.put('{'); for (std::size_t i = 0; i < side.size(); ++i) { if (i) out.put(','); emit_esc(out, side[i].name); out.puts(":{\"after\":"); emit_tv(out, side[i].after); out.put('}'); } out.put('}'); } void emit_opt_tv(Buf& out, const std::optional& v) { if (v) emit_tv(out, *v); else out.puts("null"); } void emit_diff(Buf& out, const std::vector& diff) { out.put('['); for (std::size_t i = 0; i < diff.size(); ++i) { const DiffEntry& d = diff[i]; if (i) out.put(','); out.puts("{\"path\":"); emit_esc(out, d.path); out.puts(",\"why\":"); emit_esc(out, d.why, std::strlen(d.why)); out.puts(",\"orig\":"); if (!d.orig_raw.empty()) out.puts(d.orig_raw.c_str()); else emit_opt_tv(out, d.orig); out.puts(",\"ours\":"); if (!d.ours_raw.empty()) out.puts(d.ours_raw.c_str()); else emit_opt_tv(out, d.ours); if (d.first_diff_offset >= 0) out.printf(",\"first_diff_offset\":%lld", d.first_diff_offset); if (!d.note.empty()) { out.puts(",\"note\":"); emit_esc(out, d.note); } out.put('}'); } out.put(']'); } } // namespace // mkfixture.emit_record(): fixed KEY_ORDER, optional keys omitted. void emit_record(Buf& out, const Record& r) { out.printf("{\"ts\":%lld,\"hook\":", static_cast(r.ts)); emit_esc(out, r.hook); out.printf(",\"mode\":\"%s\",\"call_id\":%llu,\"thread\":%llu", mode_name(r.mode), static_cast(r.call_id), static_cast(r.thread)); if (r.depth >= 0) out.printf(",\"depth\":%d", r.depth); out.puts(",\"args\":["); for (std::size_t i = 0; i < r.args.size(); ++i) { if (i) out.put(','); emit_tv(out, r.args[i]); } out.puts("],\"ret\":"); emit_opt_tv(out, r.ret); out.puts(",\"side\":"); emit_side(out, r.side); if (r.has_ours) { out.puts(",\"ours\":{\"ret\":"); emit_opt_tv(out, r.ours_ret); out.puts(",\"side\":"); emit_ours_side(out, r.ours_side); out.put('}'); } if (r.diverged >= 0) out.puts(r.diverged ? ",\"diverged\":true" : ",\"diverged\":false"); if (r.has_diff) { out.puts(",\"diff\":"); emit_diff(out, r.diff); } if (r.err) { out.puts(",\"err\":"); emit_esc(out, *r.err); } if (r.note) { out.puts(",\"note\":"); emit_esc(out, *r.note); } out.puts("}\n"); } // mkfixture.emit_meta() over the dict shape mkfixture.meta() builds. void emit_meta(Buf& out, const Meta& m) { out.puts("{\"meta\":{\"format\":1,\"build\":"); emit_esc(out, m.build); out.puts(",\"exe_sha256\":"); emit_esc(out, m.exe_sha256); out.puts(",\"started\":"); emit_esc(out, m.started); out.printf(",\"inline_max\":%u,\"hooks\":{", m.inline_max); for (std::size_t i = 0; i < m.hooks.size(); ++i) { const HookPolicy& p = m.hooks[i].second; if (i) out.put(','); emit_esc(out, m.hooks[i].first); out.puts(":{\"ftol\":"); emit_float(out, p.ftol, false); out.printf(",\"ftol_kind\":\"%s\",\"ptr\":\"%s\"", p.ftol_kind, p.ptr_exact ? "exact" : "ignore"); if (!p.unordered.empty()) { out.puts(",\"unordered\":["); for (std::size_t j = 0; j < p.unordered.size(); ++j) { if (j) out.put(','); emit_esc(out, p.unordered[j]); } out.put(']'); } out.put('}'); } out.puts("}}}\n"); } } // namespace shim::trace