fix(experiment): preserve code when encoded native graphs exceed capacity
Bound compact native JSON with the existing writer, keep a valid code facet when the graph cannot fit, and pass the supervised byte budget explicitly. Verify real SQLite Parser/Pragma captures, exact preserved C/mappings/graph facts and public MCP reads. Keep string inventory parsing/projection under its own owner. Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
This commit is contained in:
1 parent
dc022bcd0f
commit
1346e97dca
6 files changed
+589
-106
No files matched your search
@@ -107,6 +107,13 @@ source digest. The fixed probe passes under the bounded runner (1.328 seconds,
|
||||
1.144 family CPU seconds, 110.6 MiB kernel family peak, zero OOM kills and verified
|
||||
payload cleanup); it is a correctness check, not a model-cost measurement.
|
||||
|
||||
Encoded size is another real coverage boundary: two SQLite functions initially
|
||||
lose even their C because pretty JSON exceeds transport capacity. The native
|
||||
patch now uses bounded compact serialization and a graph-only failure when
|
||||
needed. Parser's full graph fits; Pragma retains identical C with an explicit
|
||||
missing graph. The 8 MiB bound remains. See the exact sizes, rebuilt engine digest
|
||||
and verification scope in [native-engine.md](native-engine.md).
|
||||
|
||||
Completion requires actual model Code Mode use, independently checked task
|
||||
answers, shared atomic/direct/Code Mode semantics, needed relation coverage,
|
||||
failure/cancellation/identity tests, and equivalent completed-task measurements
|
||||
|
||||
@@ -1,26 +1,114 @@
|
||||
diff --git a/decompiler/crates/kuna-cli/src/atomic_json.rs b/decompiler/crates/kuna-cli/src/atomic_json.rs
|
||||
new file mode 100644
|
||||
index 00000000..c2f6f1ad
|
||||
--- /dev/null
|
||||
+++ b/decompiler/crates/kuna-cli/src/atomic_json.rs
|
||||
@@ -0,0 +1,71 @@
|
||||
+//! Opt-in atomic documents share the ordinary record producer and JSON writer.
|
||||
+//! A graph that cannot fit must not discard otherwise readable reconstructed C.
|
||||
+
|
||||
+use crate::jsonfmt::{dumps_compact_bounded, Json};
|
||||
+
|
||||
+pub(crate) fn render(mut document: Json, limit: usize) -> Result<String, String> {
|
||||
+ let budget = limit
|
||||
+ .checked_sub(1)
|
||||
+ .ok_or("atomic JSON output budget is zero")?;
|
||||
+ if let Some(mut text) = dumps_compact_bounded(&document, budget) {
|
||||
+ text.push('\n');
|
||||
+ return Ok(text);
|
||||
+ }
|
||||
+ if let Json::Object(fields) = &mut document {
|
||||
+ if let Some(Json::Array(functions)) = fields
|
||||
+ .iter_mut()
|
||||
+ .find_map(|(key, value)| (key == "functions").then_some(value))
|
||||
+ {
|
||||
+ for function in functions {
|
||||
+ if let Json::Object(record) = function {
|
||||
+ let before = record.len();
|
||||
+ record.retain(|(key, _)| key != "atomic_facts");
|
||||
+ if record.len() != before {
|
||||
+ record.push((
|
||||
+ "atomic_facts_error".into(),
|
||||
+ Json::Str(format!(
|
||||
+ "atomic encoded document exceeds {limit} byte capacity; code retained"
|
||||
+ )),
|
||||
+ ));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ let mut text = dumps_compact_bounded(&document, budget).ok_or_else(|| {
|
||||
+ format!("atomic JSON exceeds {limit} byte capacity even without recovered IR")
|
||||
+ })?;
|
||||
+ text.push('\n');
|
||||
+ Ok(text)
|
||||
+}
|
||||
+
|
||||
+#[cfg(test)]
|
||||
+mod tests {
|
||||
+ use super::*;
|
||||
+
|
||||
+ #[test]
|
||||
+ fn encoded_overflow_preserves_code_and_replaces_only_the_graph() {
|
||||
+ let original = Json::Object(vec![(
|
||||
+ "functions".into(),
|
||||
+ Json::Array(vec![Json::Object(vec![
|
||||
+ ("code".into(), Json::Str("retained C".into())),
|
||||
+ ("error".into(), Json::Null),
|
||||
+ ("atomic_facts".into(), Json::Str("😀".repeat(1000))),
|
||||
+ ])]),
|
||||
+ )]);
|
||||
+ let full = render(original.clone(), 20000).unwrap();
|
||||
+ assert!(full.len() > 12000); // ASCII surrogate escaping is accounted for.
|
||||
+ let reduced = render(original.clone(), 256).unwrap();
|
||||
+ let document: serde_json::Value = serde_json::from_str(&reduced).unwrap();
|
||||
+ let function = &document["functions"][0];
|
||||
+ assert_eq!(function["code"], "retained C");
|
||||
+ assert_eq!(function["error"], serde_json::Value::Null);
|
||||
+ assert!(function.get("atomic_facts").is_none());
|
||||
+ assert!(function["atomic_facts_error"]
|
||||
+ .as_str()
|
||||
+ .unwrap()
|
||||
+ .contains("256"));
|
||||
+ assert!(reduced.len() <= 256);
|
||||
+ assert!(render(original, 1).is_err());
|
||||
+ }
|
||||
+}
|
||||
diff --git a/decompiler/crates/kuna-cli/src/decompile.rs b/decompiler/crates/kuna-cli/src/decompile.rs
|
||||
index ad249b04..9eb551e5 100644
|
||||
index ad249b04..62074996 100644
|
||||
--- a/decompiler/crates/kuna-cli/src/decompile.rs
|
||||
+++ b/decompiler/crates/kuna-cli/src/decompile.rs
|
||||
@@ -841,6 +841,7 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
@@ -841,6 +841,8 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
let mut target: Option<String> = None;
|
||||
let mut addr = false;
|
||||
let mut json = false;
|
||||
+ let mut atomic_facts = false;
|
||||
+ let mut atomic_output_bytes = None;
|
||||
let mut bfd_target: Option<String> = None;
|
||||
let mut raw = false;
|
||||
let mut regions = false;
|
||||
@@ -871,6 +872,7 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
@@ -871,6 +873,13 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
match a {
|
||||
"--addr" => addr = true,
|
||||
"--json" => json = true,
|
||||
+ "--atomic-facts" => atomic_facts = true,
|
||||
+ "--atomic-output-bytes" => {
|
||||
+ let value = take_value(argv, &mut i, "--atomic-output-bytes")?;
|
||||
+ atomic_output_bytes = Some(value.parse::<usize>()
|
||||
+ .ok().filter(|bytes| *bytes > 0)
|
||||
+ .ok_or("--atomic-output-bytes requires a positive byte count")?);
|
||||
+ }
|
||||
"--raw" => raw = true,
|
||||
"--raw-image" => raw_image = true,
|
||||
"--base" => base = Some(parse_cli_address(&take_value(argv, &mut i, "--base")?)?),
|
||||
@@ -980,6 +982,9 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
@@ -979,7 +988,13 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
return Err("--base requires --raw-image".into());
|
||||
}
|
||||
|
||||
|
||||
+ if atomic_output_bytes.is_some() && !atomic_facts {
|
||||
+ return Err("--atomic-output-bytes requires --atomic-facts".into());
|
||||
+ }
|
||||
if json {
|
||||
+ if atomic_facts && (!cfg!(feature = "embed-specs") || sleighpath.is_some()) {
|
||||
+ return Err("--atomic-facts requires embedded specs and does not accept --sleighpath".into());
|
||||
@@ -28,18 +116,19 @@ index ad249b04..9eb551e5 100644
|
||||
// Refused, not ignored: each of these is a `decomp_dbg` transcript the
|
||||
// in-process JSON path never produces, and silently dropping half a
|
||||
// request is the failure mode `--json` exists to end.
|
||||
@@ -997,6 +1002,7 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
@@ -997,6 +1012,8 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
binary: &binary,
|
||||
target: &target,
|
||||
by_address: addr,
|
||||
+ atomic_facts,
|
||||
+ atomic_output_bytes: atomic_output_bytes.unwrap_or(8 * 1024 * 1024),
|
||||
forwarded: &forwarded,
|
||||
mode: mode.as_deref(),
|
||||
bfd_target: bfd_target.as_deref(),
|
||||
@@ -1007,6 +1013,10 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
@@ -1007,6 +1024,10 @@ pub fn main(argv: &[String]) -> Result<i32, String> {
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
+ if atomic_facts {
|
||||
+ return Err("--atomic-facts requires --json".into());
|
||||
+ }
|
||||
@@ -47,15 +136,16 @@ index ad249b04..9eb551e5 100644
|
||||
// (kuna outlang, DIV-80) The auto policy -- follow the binary when the caller
|
||||
// named no language. See `decompile_all::detected_output_language`.
|
||||
if !saw_language && !options.iter().any(|(n, _)| n == "setlanguage") {
|
||||
@@ -1106,6 +1116,7 @@ struct JsonRequest<'a> {
|
||||
@@ -1106,6 +1127,8 @@ struct JsonRequest<'a> {
|
||||
binary: &'a str,
|
||||
target: &'a str,
|
||||
by_address: bool,
|
||||
+ atomic_facts: bool,
|
||||
+ atomic_output_bytes: usize,
|
||||
/// The `--option` / `--language` tokens, in argv order.
|
||||
forwarded: &'a [String],
|
||||
mode: Option<&'a str>,
|
||||
@@ -1127,6 +1138,18 @@ struct JsonRequest<'a> {
|
||||
@@ -1127,6 +1150,18 @@ struct JsonRequest<'a> {
|
||||
/// drift.
|
||||
fn run_json(req: &JsonRequest) -> i32 {
|
||||
let mut argv: Vec<String> = vec![req.binary.to_string(), "--json".into()];
|
||||
@@ -74,35 +164,56 @@ index ad249b04..9eb551e5 100644
|
||||
if req.by_address {
|
||||
argv.push("--addr".into());
|
||||
argv.push(if looks_like_addr(req.target) {
|
||||
@@ -1168,7 +1191,7 @@ fn run_json(req: &JsonRequest) -> i32 {
|
||||
@@ -1168,7 +1203,7 @@ fn run_json(req: &JsonRequest) -> i32 {
|
||||
// it has to reconcile against a prose stderr line — the missing error channel
|
||||
// is half of why this flag exists. The verdict follows the document, matching
|
||||
// `run`'s stdout-then-stderr ordering.
|
||||
- let (text, failure) = match decompile_json(&args, req.target) {
|
||||
+ let (text, failure) = match decompile_json(&args, req.target, req.atomic_facts) {
|
||||
+ let (text, failure) = match decompile_json(&args, req.target, req.atomic_facts.then_some(req.atomic_output_bytes)) {
|
||||
Ok(answered) => answered,
|
||||
Err(message) => (
|
||||
decompile_all::render_result_json(
|
||||
@@ -1197,7 +1220,7 @@ fn run_json(req: &JsonRequest) -> i32 {
|
||||
@@ -1197,7 +1232,7 @@ fn run_json(req: &JsonRequest) -> i32 {
|
||||
/// was asked about exactly that function — so `--json` cannot turn the text
|
||||
/// surface's non-zero verdict into a success; the reason is in the document too,
|
||||
/// as the record's `error` and as the run-level one.
|
||||
-fn decompile_json(args: &AllArgs, target: &str) -> Result<(String, Option<String>), String> {
|
||||
+fn decompile_json(args: &AllArgs, target: &str, atomic_facts: bool) -> Result<(String, Option<String>), String> {
|
||||
+fn decompile_json(args: &AllArgs, target: &str, atomic_limit: Option<usize>) -> Result<(String, Option<String>), String> {
|
||||
let mut prog = decompile_all::load_program(args, DriverDefaults::Decompile)?;
|
||||
let targets = decompile_all::resolve_targets(&prog, args)?;
|
||||
let interior = crate::interior::selections(&prog, args, &targets);
|
||||
@@ -1217,7 +1240,7 @@ fn decompile_json(args: &AllArgs, target: &str) -> Result<(String, Option<String
|
||||
@@ -1217,7 +1252,7 @@ fn decompile_json(args: &AllArgs, target: &str) -> Result<(String, Option<String
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
- let funcs = decompile_all::decompile_entries(&mut prog, args, targets, false);
|
||||
+ let funcs = decompile_all::decompile_entries(&mut prog, args, targets, false, atomic_facts);
|
||||
+ let funcs = decompile_all::decompile_entries(&mut prog, args, targets, false, atomic_limit.is_some());
|
||||
let assertions = prog.assertion_outcomes();
|
||||
let mut failure = funcs.iter().find_map(|f| {
|
||||
f.error.as_ref().map(|reason| {
|
||||
@@ -1235,14 +1270,18 @@ fn decompile_json(args: &AllArgs, target: &str) -> Result<(String, Option<String
|
||||
if failure.is_none() && decompile_all::any_refused_assertion(&assertions) {
|
||||
failure = Some(format!("--assert directive refused by the pipeline in {}", args.binary));
|
||||
}
|
||||
- let text = decompile_all::render_result_json(
|
||||
+ let text = if let Some(limit) = atomic_limit {
|
||||
+ decompile_all::render_atomic_result_json(
|
||||
+ &args.binary, &funcs, &args.options, failure.as_deref(), &assertions, &interior, limit,
|
||||
+ )?
|
||||
+ } else { decompile_all::render_result_json(
|
||||
&args.binary,
|
||||
&funcs,
|
||||
&args.options,
|
||||
failure.as_deref(),
|
||||
&assertions,
|
||||
&interior,
|
||||
- );
|
||||
+ ) };
|
||||
if failure.is_none() && args.assert_strict && rejected {
|
||||
failure = Some(format!("--assert directive rejected in {}", args.binary));
|
||||
}
|
||||
diff --git a/decompiler/crates/kuna-cli/src/decompile_all.rs b/decompiler/crates/kuna-cli/src/decompile_all.rs
|
||||
index 698c7243..1f812ce6 100644
|
||||
index 698c7243..dfd9ef01 100644
|
||||
--- a/decompiler/crates/kuna-cli/src/decompile_all.rs
|
||||
+++ b/decompiler/crates/kuna-cli/src/decompile_all.rs
|
||||
@@ -744,6 +744,7 @@ fn run_jobs_worker(args: &Args) -> Result<(), String> {
|
||||
@@ -138,7 +249,27 @@ index 698c7243..1f812ce6 100644
|
||||
..DecompileOptions::default()
|
||||
};
|
||||
kuna_console::project::decompile_targets_with(prog, targets, &opts)
|
||||
@@ -2152,6 +2155,17 @@ fn result_json(
|
||||
@@ -2003,6 +2006,19 @@ pub(crate) fn render_result_json(
|
||||
render_selected_json(binary, funcs, options, error, None, assertions, interior)
|
||||
}
|
||||
|
||||
+pub(crate) fn render_atomic_result_json(
|
||||
+ binary: &str,
|
||||
+ funcs: &[FuncResult],
|
||||
+ options: &[(String, String)],
|
||||
+ error: Option<&str>,
|
||||
+ assertions: &[kuna_console::assertions::Outcome],
|
||||
+ interior: &crate::interior::Interiors,
|
||||
+ limit: usize,
|
||||
+) -> Result<String, String> {
|
||||
+ let language = last_option_value(options, "setlanguage").unwrap_or("c-language");
|
||||
+ crate::atomic_json::render(result_json(binary, funcs, language, error, None, assertions, interior), limit)
|
||||
+}
|
||||
+
|
||||
/// [`render_result_json`] plus the inventory `total` a NARROWED whole-binary run
|
||||
/// answers with.
|
||||
///
|
||||
@@ -2152,6 +2168,17 @@ fn result_json(
|
||||
// Always present, `[]` unless the option is on.
|
||||
("types".into(), types),
|
||||
];
|
||||
@@ -169,13 +300,13 @@ index 3eb2a6e4..2b951ba2 100644
|
||||
};
|
||||
decompile_callee_first(prog, args, targets, explicit, base)
|
||||
diff --git a/decompiler/crates/kuna-cli/src/jsonfmt.rs b/decompiler/crates/kuna-cli/src/jsonfmt.rs
|
||||
index 198b85a6..22c1e086 100644
|
||||
index 198b85a6..71bd9675 100644
|
||||
--- a/decompiler/crates/kuna-cli/src/jsonfmt.rs
|
||||
+++ b/decompiler/crates/kuna-cli/src/jsonfmt.rs
|
||||
@@ -28,6 +28,19 @@ pub enum Json {
|
||||
Object(Vec<(String, Json)>),
|
||||
}
|
||||
|
||||
|
||||
+impl From<serde_json::Value> for Json {
|
||||
+ fn from(value: serde_json::Value) -> Self {
|
||||
+ match value {
|
||||
@@ -192,6 +323,256 @@ index 198b85a6..22c1e086 100644
|
||||
/// Render `v` exactly as CPython's `json.dumps(v, indent=2)` would (ASCII-safe
|
||||
/// string escaping, `", "`/`": "` item/key separators collapsed to the indent
|
||||
/// form: items separated by `,\n`, keys by `": "`).
|
||||
@@ -50,6 +63,12 @@ pub fn dumps_compact(v: &Json) -> String {
|
||||
writer::render(v, Format::Compact)
|
||||
}
|
||||
|
||||
+/// Enforce the caller's encoded-byte budget before growing the output buffer.
|
||||
+/// Uses the same writer and escaping as the other modes; no length estimator.
|
||||
+pub fn dumps_compact_bounded(v: &Json, limit: usize) -> Option<String> {
|
||||
+ writer::render_bounded(v, Format::Compact, limit)
|
||||
+}
|
||||
+
|
||||
/// Extract the JSON array/object span out of a `decomp_dbg` console transcript.
|
||||
///
|
||||
/// Port of `kuna/catalog.py::_extract_json`: drop the fixed `[decomp]>` prompt
|
||||
diff --git a/decompiler/crates/kuna-cli/src/jsonfmt/writer.rs b/decompiler/crates/kuna-cli/src/jsonfmt/writer.rs
|
||||
index c4eca1db..06b9d024 100644
|
||||
--- a/decompiler/crates/kuna-cli/src/jsonfmt/writer.rs
|
||||
+++ b/decompiler/crates/kuna-cli/src/jsonfmt/writer.rs
|
||||
@@ -1,7 +1,7 @@
|
||||
//! JSON layout and ASCII-safe escaping shared by all CLI output modes.
|
||||
|
||||
use super::Json;
|
||||
-use std::fmt::Write as _;
|
||||
+use std::fmt::{self, Write};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum Format {
|
||||
@@ -12,108 +12,142 @@ pub(super) enum Format {
|
||||
|
||||
pub(super) fn render(value: &Json, format: Format) -> String {
|
||||
let mut out = String::new();
|
||||
- write_value(&mut out, value, 0, format);
|
||||
+ write_value(&mut out, value, 0, format).expect("String writer is infallible");
|
||||
out
|
||||
}
|
||||
|
||||
+struct BoundedString {
|
||||
+ text: String,
|
||||
+ limit: usize,
|
||||
+}
|
||||
+
|
||||
+impl Write for BoundedString {
|
||||
+ fn write_str(&mut self, value: &str) -> fmt::Result {
|
||||
+ let next = self.text.len().checked_add(value.len()).ok_or(fmt::Error)?;
|
||||
+ if next > self.limit {
|
||||
+ return Err(fmt::Error);
|
||||
+ }
|
||||
+ if next > self.text.capacity() {
|
||||
+ let capacity = next
|
||||
+ .max(self.text.capacity().saturating_mul(2))
|
||||
+ .min(self.limit);
|
||||
+ self.text
|
||||
+ .try_reserve_exact(capacity - self.text.len())
|
||||
+ .map_err(|_| fmt::Error)?;
|
||||
+ }
|
||||
+ self.text.push_str(value);
|
||||
+ Ok(())
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+pub(super) fn render_bounded(value: &Json, format: Format, limit: usize) -> Option<String> {
|
||||
+ let mut out = BoundedString {
|
||||
+ text: String::new(),
|
||||
+ limit,
|
||||
+ };
|
||||
+ write_value(&mut out, value, 0, format).ok()?;
|
||||
+ Some(out.text)
|
||||
+}
|
||||
+
|
||||
impl Format {
|
||||
- fn line(self, out: &mut String, indent: usize) {
|
||||
+ fn line(self, out: &mut impl Write, indent: usize) -> fmt::Result {
|
||||
if self != Self::Compact {
|
||||
- out.push('\n');
|
||||
+ out.write_char('\n')?;
|
||||
for _ in 0..indent {
|
||||
- out.push(' ');
|
||||
+ out.write_char(' ')?;
|
||||
}
|
||||
}
|
||||
+ Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
-fn write_value(out: &mut String, value: &Json, indent: usize, format: Format) {
|
||||
+fn write_value(out: &mut impl Write, value: &Json, indent: usize, format: Format) -> fmt::Result {
|
||||
match value {
|
||||
- Json::Null => out.push_str("null"),
|
||||
- Json::Bool(true) => out.push_str("true"),
|
||||
- Json::Bool(false) => out.push_str("false"),
|
||||
- Json::Number(number) => out.push_str(number),
|
||||
+ Json::Null => out.write_str("null"),
|
||||
+ Json::Bool(true) => out.write_str("true"),
|
||||
+ Json::Bool(false) => out.write_str("false"),
|
||||
+ Json::Number(number) => out.write_str(number),
|
||||
Json::Str(text) => write_string(out, text),
|
||||
Json::Array(items) => {
|
||||
- out.push('[');
|
||||
+ out.write_char('[')?;
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
if i > 0 {
|
||||
- out.push(',');
|
||||
+ out.write_char(',')?;
|
||||
}
|
||||
- format.line(out, indent + 2);
|
||||
- write_value(out, item, indent + 2, format);
|
||||
+ format.line(out, indent + 2)?;
|
||||
+ write_value(out, item, indent + 2, format)?;
|
||||
}
|
||||
if !items.is_empty() {
|
||||
- format.line(out, indent);
|
||||
+ format.line(out, indent)?;
|
||||
}
|
||||
- out.push(']');
|
||||
+ out.write_char(']')
|
||||
}
|
||||
Json::Object(pairs) => {
|
||||
if format == Format::Sorted && pairs.len() > 1 {
|
||||
let mut ordered: Vec<_> = pairs.iter().collect();
|
||||
ordered.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
- write_pairs(out, ordered.into_iter(), indent, format);
|
||||
+ write_pairs(out, ordered.into_iter(), indent, format)
|
||||
} else {
|
||||
- write_pairs(out, pairs.iter(), indent, format);
|
||||
+ write_pairs(out, pairs.iter(), indent, format)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pairs<'a>(
|
||||
- out: &mut String,
|
||||
+ out: &mut impl Write,
|
||||
pairs: impl ExactSizeIterator<Item = &'a (String, Json)>,
|
||||
indent: usize,
|
||||
format: Format,
|
||||
-) {
|
||||
+) -> fmt::Result {
|
||||
let nonempty = pairs.len() != 0;
|
||||
- out.push('{');
|
||||
+ out.write_char('{')?;
|
||||
for (i, (key, value)) in pairs.enumerate() {
|
||||
if i > 0 {
|
||||
- out.push(',');
|
||||
+ out.write_char(',')?;
|
||||
}
|
||||
- format.line(out, indent + 2);
|
||||
- write_string(out, key);
|
||||
- out.push(':');
|
||||
+ format.line(out, indent + 2)?;
|
||||
+ write_string(out, key)?;
|
||||
+ out.write_char(':')?;
|
||||
if format != Format::Compact {
|
||||
- out.push(' ');
|
||||
+ out.write_char(' ')?;
|
||||
}
|
||||
- write_value(out, value, indent + 2, format);
|
||||
+ write_value(out, value, indent + 2, format)?;
|
||||
}
|
||||
if nonempty {
|
||||
- format.line(out, indent);
|
||||
+ format.line(out, indent)?;
|
||||
}
|
||||
- out.push('}');
|
||||
+ out.write_char('}')
|
||||
}
|
||||
|
||||
-fn write_string(out: &mut String, s: &str) {
|
||||
- out.push('"');
|
||||
+fn write_string(out: &mut impl Write, s: &str) -> fmt::Result {
|
||||
+ out.write_char('"')?;
|
||||
for ch in s.chars() {
|
||||
match ch {
|
||||
- '"' => out.push_str("\\\""),
|
||||
- '\\' => out.push_str("\\\\"),
|
||||
- '\n' => out.push_str("\\n"),
|
||||
- '\r' => out.push_str("\\r"),
|
||||
- '\t' => out.push_str("\\t"),
|
||||
- '\u{0008}' => out.push_str("\\b"),
|
||||
- '\u{000C}' => out.push_str("\\f"),
|
||||
+ '"' => out.write_str("\\\""),
|
||||
+ '\\' => out.write_str("\\\\"),
|
||||
+ '\n' => out.write_str("\\n"),
|
||||
+ '\r' => out.write_str("\\r"),
|
||||
+ '\t' => out.write_str("\\t"),
|
||||
+ '\u{0008}' => out.write_str("\\b"),
|
||||
+ '\u{000C}' => out.write_str("\\f"),
|
||||
c if (c as u32) < 0x20 => {
|
||||
- let _ = write!(out, "\\u{:04x}", c as u32);
|
||||
+ write!(out, "\\u{:04x}", c as u32)
|
||||
}
|
||||
- c if (c as u32) < 0x7F => out.push(c),
|
||||
+ c if (c as u32) < 0x7F => out.write_char(c),
|
||||
c => {
|
||||
// ensure_ascii: emit \uXXXX (surrogate pair for astral planes).
|
||||
let cp = c as u32;
|
||||
if cp <= 0xFFFF {
|
||||
- let _ = write!(out, "\\u{:04x}", cp);
|
||||
+ write!(out, "\\u{:04x}", cp)
|
||||
} else {
|
||||
let v = cp - 0x10000;
|
||||
let hi = 0xD800 + (v >> 10);
|
||||
let lo = 0xDC00 + (v & 0x3FF);
|
||||
- let _ = write!(out, "\\u{:04x}\\u{:04x}", hi, lo);
|
||||
+ write!(out, "\\u{:04x}\\u{:04x}", hi, lo)
|
||||
}
|
||||
}
|
||||
- }
|
||||
+ }?;
|
||||
}
|
||||
- out.push('"');
|
||||
+ out.write_char('"')
|
||||
}
|
||||
diff --git a/decompiler/crates/kuna-cli/src/jsonfmt/writer_tests.rs b/decompiler/crates/kuna-cli/src/jsonfmt/writer_tests.rs
|
||||
index 6710e12a..ef90468c 100644
|
||||
--- a/decompiler/crates/kuna-cli/src/jsonfmt/writer_tests.rs
|
||||
+++ b/decompiler/crates/kuna-cli/src/jsonfmt/writer_tests.rs
|
||||
@@ -94,3 +94,21 @@ fn every_unicode_scalar_round_trips_through_ascii_output() {
|
||||
assert_eq!(serde_json::from_str::<String>(&output).unwrap(), text);
|
||||
}
|
||||
}
|
||||
+
|
||||
+#[test]
|
||||
+fn bounded_writer_counts_actual_escaped_bytes_at_every_boundary() {
|
||||
+ let value = Json::Object(vec![("escaped".into(), Json::Str("\0\"\\é😀".into()))]);
|
||||
+ let complete = dumps_compact(&value);
|
||||
+ assert!(complete.is_ascii());
|
||||
+ for limit in 0..complete.len() {
|
||||
+ assert!(dumps_compact_bounded(&value, limit).is_none());
|
||||
+ }
|
||||
+ assert_eq!(
|
||||
+ dumps_compact_bounded(&value, complete.len()),
|
||||
+ Some(complete.clone())
|
||||
+ );
|
||||
+ assert_eq!(
|
||||
+ dumps_compact_bounded(&value, complete.len() + 1),
|
||||
+ Some(complete)
|
||||
+ );
|
||||
+}
|
||||
diff --git a/decompiler/crates/kuna-cli/src/main.rs b/decompiler/crates/kuna-cli/src/main.rs
|
||||
index 2345743c..40fff9ec 100644
|
||||
--- a/decompiler/crates/kuna-cli/src/main.rs
|
||||
+++ b/decompiler/crates/kuna-cli/src/main.rs
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Command dispatch for the kuna CLI.
|
||||
|
||||
mod args;
|
||||
+mod atomic_json;
|
||||
mod callgraph;
|
||||
mod catalog;
|
||||
mod crypto;
|
||||
diff --git a/decompiler/crates/kuna-cli/src/project_stream.rs b/decompiler/crates/kuna-cli/src/project_stream.rs
|
||||
index e40b18c9..b8cae233 100644
|
||||
--- a/decompiler/crates/kuna-cli/src/project_stream.rs
|
||||
@@ -203,7 +584,7 @@ index e40b18c9..b8cae233 100644
|
||||
+ want_atomic_facts: false,
|
||||
want_pointer_arguments: false,
|
||||
};
|
||||
|
||||
|
||||
diff --git a/decompiler/crates/kuna-console/src/inspect.rs b/decompiler/crates/kuna-console/src/inspect.rs
|
||||
index 74024b10..1ecc4fae 100644
|
||||
--- a/decompiler/crates/kuna-console/src/inspect.rs
|
||||
@@ -214,7 +595,7 @@ index 74024b10..1ecc4fae 100644
|
||||
pub tokens_error: Option<String>,
|
||||
+ pub atomic_facts: Option<Result<kuna_decomp::atomic_facts::FunctionFacts, String>>,
|
||||
}
|
||||
|
||||
|
||||
impl FuncDetail {
|
||||
@@ -40,13 +41,13 @@ impl FuncDetail {
|
||||
) -> Self {
|
||||
@@ -231,7 +612,7 @@ index 74024b10..1ecc4fae 100644
|
||||
+ FuncDetail { tokens, tokens_error: None, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
diff --git a/decompiler/crates/kuna-console/src/project.rs b/decompiler/crates/kuna-console/src/project.rs
|
||||
index bc401db2..775ca348 100644
|
||||
--- a/decompiler/crates/kuna-console/src/project.rs
|
||||
@@ -295,65 +676,14 @@ index 40b093b6..8f03b654 100644
|
||||
smallvec.workspace = true
|
||||
slotmap.workspace = true
|
||||
+serde = { version = "1", features = ["derive"] }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
# Verifier-only: the datatest <stringmatch> engine (item w10-structure-printbody).
|
||||
regex = { workspace = true }
|
||||
-serde = { version = "1", features = ["derive"] }
|
||||
toml = { version = "1.1", default-features = false, features = ["parse", "serde", "std"] }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
diff --git a/decompiler/crates/kuna-decomp/src/substrate/funcdata_printraw.rs b/decompiler/crates/kuna-decomp/src/substrate/funcdata_printraw.rs
|
||||
index 39927ceb..844950a6 100644
|
||||
--- a/decompiler/crates/kuna-decomp/src/substrate/funcdata_printraw.rs
|
||||
+++ b/decompiler/crates/kuna-decomp/src/substrate/funcdata_printraw.rs
|
||||
@@ -376,7 +376,7 @@ fn callother_operator_name(
|
||||
/// dispatch. The special op-codes (LOAD/STORE/branches/calls/RETURN/marker)
|
||||
/// match their `TypeOp*::printRaw` override; everything else falls to the
|
||||
/// generic Binary/Unary/Func shape keyed by the [`TypeOpClass`].
|
||||
-fn render_op(s: &mut String, arch: &Architecture, fd: &Funcdata, op: OpId) -> Result<(), String> {
|
||||
+pub(crate) fn render_op(s: &mut String, arch: &Architecture, fd: &Funcdata, op: OpId) -> Result<(), String> {
|
||||
let opc = fd.obank().get(op).ok_or_else(|| "render_op: stale op".to_string())?.code();
|
||||
let num_input = fd.obank().get(op).ok_or_else(|| "render_op: stale op".to_string())?.num_input();
|
||||
let in_at = |i: int4| -> Option<VarnodeId> { fd.obank().get(op).and_then(|o| o.get_in(i)) };
|
||||
diff --git a/decompiler/crates/kuna-decomp/src/substrate/mod.rs b/decompiler/crates/kuna-decomp/src/substrate/mod.rs
|
||||
index 00528cb5..08da88eb 100644
|
||||
--- a/decompiler/crates/kuna-decomp/src/substrate/mod.rs
|
||||
+++ b/decompiler/crates/kuna-decomp/src/substrate/mod.rs
|
||||
@@ -11,6 +11,7 @@ pub mod funcdata_block;
|
||||
pub mod funcdata_encode;
|
||||
pub mod funcdata_op;
|
||||
pub mod funcdata_printraw;
|
||||
+pub mod atomic_facts;
|
||||
pub mod funcdata_varnode;
|
||||
pub mod dtype;
|
||||
pub mod context;
|
||||
diff --git a/docs/spec/00-overview.md b/docs/spec/00-overview.md
|
||||
index ce499c4f..f54772b3 100644
|
||||
--- a/docs/spec/00-overview.md
|
||||
+++ b/docs/spec/00-overview.md
|
||||
@@ -3431,3 +3431,21 @@ Reserving it creates no analysis varnode and changes no physical mapping or
|
||||
emitted C. Ghidra receives temporary storage for the logical local, rather than
|
||||
a persistable ordinary stack-variable location; ordinary symbol storage and
|
||||
register-plus-stack parameter JOINs keep their existing encoding.
|
||||
+
|
||||
+## Experimental read-only atomic facts
|
||||
+
|
||||
+The opt-in `decompile --json --atomic-facts` surface captures the same live
|
||||
+function after C rendering (`decompiler/crates/kuna-decomp/src/substrate/atomic_facts.rs`).
|
||||
+It does not change the analysis phases or C output. The default path does not
|
||||
+collect these records. This experiment requires embedded processor specs and
|
||||
+selects those assets explicitly; a caller-provided `--sleighpath` is rejected.
|
||||
+
|
||||
+The export retains ordered basic-block predecessors and successors, slotmap
|
||||
+value identities, operation sequence identities, operand indexes, outputs and
|
||||
+phi input-to-predecessor-edge bindings. IDs belong to this captured function
|
||||
+model; instruction addresses are origins, not SSA value identities. Native
|
||||
+recovered graph completeness does not establish runtime successor coverage,
|
||||
+memory alias dependencies or machine-semantic equivalence. Indirect-branch
|
||||
+operations and definitions outside block membership remain explicit. Capacity
|
||||
+failures make this facet unavailable with a reason and preserve the C record;
|
||||
+they do not emit a clipped graph labeled complete.
|
||||
diff --git a/decompiler/crates/kuna-decomp/src/substrate/atomic_facts.rs b/decompiler/crates/kuna-decomp/src/substrate/atomic_facts.rs
|
||||
new file mode 100644
|
||||
index 00000000..65e18270
|
||||
@@ -634,3 +964,63 @@ index 00000000..65e18270
|
||||
+ coverage,
|
||||
+ })
|
||||
+}
|
||||
diff --git a/decompiler/crates/kuna-decomp/src/substrate/funcdata_printraw.rs b/decompiler/crates/kuna-decomp/src/substrate/funcdata_printraw.rs
|
||||
index 39927ceb..844950a6 100644
|
||||
--- a/decompiler/crates/kuna-decomp/src/substrate/funcdata_printraw.rs
|
||||
+++ b/decompiler/crates/kuna-decomp/src/substrate/funcdata_printraw.rs
|
||||
@@ -376,7 +376,7 @@ fn callother_operator_name(
|
||||
/// dispatch. The special op-codes (LOAD/STORE/branches/calls/RETURN/marker)
|
||||
/// match their `TypeOp*::printRaw` override; everything else falls to the
|
||||
/// generic Binary/Unary/Func shape keyed by the [`TypeOpClass`].
|
||||
-fn render_op(s: &mut String, arch: &Architecture, fd: &Funcdata, op: OpId) -> Result<(), String> {
|
||||
+pub(crate) fn render_op(s: &mut String, arch: &Architecture, fd: &Funcdata, op: OpId) -> Result<(), String> {
|
||||
let opc = fd.obank().get(op).ok_or_else(|| "render_op: stale op".to_string())?.code();
|
||||
let num_input = fd.obank().get(op).ok_or_else(|| "render_op: stale op".to_string())?.num_input();
|
||||
let in_at = |i: int4| -> Option<VarnodeId> { fd.obank().get(op).and_then(|o| o.get_in(i)) };
|
||||
diff --git a/decompiler/crates/kuna-decomp/src/substrate/mod.rs b/decompiler/crates/kuna-decomp/src/substrate/mod.rs
|
||||
index 00528cb5..08da88eb 100644
|
||||
--- a/decompiler/crates/kuna-decomp/src/substrate/mod.rs
|
||||
+++ b/decompiler/crates/kuna-decomp/src/substrate/mod.rs
|
||||
@@ -11,6 +11,7 @@ pub mod funcdata_block;
|
||||
pub mod funcdata_encode;
|
||||
pub mod funcdata_op;
|
||||
pub mod funcdata_printraw;
|
||||
+pub mod atomic_facts;
|
||||
pub mod funcdata_varnode;
|
||||
pub mod dtype;
|
||||
pub mod context;
|
||||
diff --git a/docs/spec/00-overview.md b/docs/spec/00-overview.md
|
||||
index ce499c4f..e1bebe28 100644
|
||||
--- a/docs/spec/00-overview.md
|
||||
+++ b/docs/spec/00-overview.md
|
||||
@@ -3431,3 +3431,30 @@ Reserving it creates no analysis varnode and changes no physical mapping or
|
||||
emitted C. Ghidra receives temporary storage for the logical local, rather than
|
||||
a persistable ordinary stack-variable location; ordinary symbol storage and
|
||||
register-plus-stack parameter JOINs keep their existing encoding.
|
||||
+
|
||||
+## Experimental read-only atomic facts
|
||||
+
|
||||
+The opt-in `decompile --json --atomic-facts` surface captures the same live
|
||||
+function after C rendering (`decompiler/crates/kuna-decomp/src/substrate/atomic_facts.rs`).
|
||||
+It does not change the analysis phases or C output. The default path does not
|
||||
+collect these records. This experiment requires embedded processor specs and
|
||||
+selects those assets explicitly; a caller-provided `--sleighpath` is rejected.
|
||||
+
|
||||
+The export retains ordered basic-block predecessors and successors, slotmap
|
||||
+value identities, operation sequence identities, operand indexes, outputs and
|
||||
+phi input-to-predecessor-edge bindings. IDs belong to this captured function
|
||||
+model; instruction addresses are origins, not SSA value identities. Native
|
||||
+recovered graph completeness does not establish runtime successor coverage,
|
||||
+memory alias dependencies or machine-semantic equivalence. Indirect-branch
|
||||
+operations and definitions outside block membership remain explicit. Capacity
|
||||
+failures make this facet unavailable with a reason and preserve the C record;
|
||||
+they do not emit a clipped graph labeled complete.
|
||||
+
|
||||
+`--atomic-output-bytes N` sets the encoded document budget (default 8 MiB).
|
||||
+Atomic JSON uses the ordinary record producer and the shared compact ASCII-safe
|
||||
+writer. That writer checks each encoded write before growing its bounded output
|
||||
+buffer, including control/surrogate escaping and the final newline. A document
|
||||
+that cannot fit drops only its graph facet, reports `atomic_facts_error`, and
|
||||
+retains C, locations and ordinary metadata. If that base record also cannot fit,
|
||||
+the command reports failure instead of truncating it or analyzing the target
|
||||
+again. This flag requires `--atomic-facts --json`; ordinary output is unchanged.
|
||||
@@ -125,3 +125,44 @@ cumulative kernel memory peak is 275.1 MiB for both phases; the warm number is a
|
||||
upper bound on its isolated phase peak. This is an API-only measurement, with no
|
||||
model token result or cross-session cache claim. Targeted tests now total 38,
|
||||
including late cancellation retaining partial facts and their provenance.
|
||||
|
||||
A subsequent real boundary sweep found a different loss of usable facts:
|
||||
`sqlite3Pragma` (`0xb90e0`) and `sqlite3Parser` (`0xb3d00`) stay below the row
|
||||
limits but overflow the provider's 8 MiB stdout budget. Both public snapshots
|
||||
fail, losing C as well as IR. A separate diagnostic capture with a stated 32 MiB
|
||||
observer limit establishes the exact producer sizes; it is not relabeled as a
|
||||
successful public-provider workflow. The old engine is preserved locally by its
|
||||
`7d357e75…` digest for historical reproduction.
|
||||
|
||||
| Function | Previous pretty JSON bytes | Revised source bytes | C characters | Revised graph |
|
||||
| --- | ---: | ---: | ---: | --- |
|
||||
| `sqlite3Parser` | 8,492,810 | 4,526,811 | 50,508 | All 5,164 operations retained |
|
||||
| `sqlite3Pragma` | 16,728,407 | 136,501 | 67,300 | Explicitly unavailable at encoded-byte capacity |
|
||||
|
||||
The structural fix is at the producing serialization boundary. Atomic output
|
||||
uses the existing record producer and shared compact ASCII-safe writer.
|
||||
`--atomic-output-bytes` takes an explicit encoded-document budget (default 8 MiB);
|
||||
the provider passes the same bound it supervises. The writer checks each encoded
|
||||
write before growing its bounded buffer, accounting for escaping and newline.
|
||||
If necessary, the document drops only the graph and records an exact facet
|
||||
failure. C, line mappings and metadata survive under the same Evidence. If even
|
||||
the base document cannot fit, the command fails rather than clipping it or
|
||||
repeating the analysis. Ordinary non-atomic JSON keeps its previous formatting.
|
||||
Row limits alone never promise that the whole encoded graph fits.
|
||||
|
||||
The rebuilt local engine is SHA-256
|
||||
`2290e8b13caabe30aab6832ab163d92925b2e652eebf7fc0cdbe65b889cb251b`
|
||||
(same pinned source/release profile plus the revised patch). Real Code Mode MCP
|
||||
verification succeeds for both functions. The preserved C and line mappings are
|
||||
identical to the earlier full diagnostic captures; the Parser graph is also
|
||||
deep-equal, and retained source hashes verify. Pragma's unknown graph counts and
|
||||
failed graph reads retain the correct source ID. No limit was increased.
|
||||
|
||||
The first successful revised MCP check took 13.721 family wall seconds, 13.461
|
||||
CPU seconds and 326.9 MiB kernel family peak, with verified cleanup and no OOM.
|
||||
This is a two-function API regression, not a model-cost comparison or a claimed
|
||||
analysis speedup. Native writer/escaping checks (14) and graph-only fallback (1)
|
||||
pass in the release test binary; the shared JS tests (38), SDK ownership probe,
|
||||
lint and patch apply/reverse checks pass. Full upstream corpus/stages/workspace
|
||||
gates still have not run. The first MCP launch used a stale pre-format-build hash
|
||||
and was correctly refused; the retained successful retry uses the digest above.
|
||||
@@ -5,7 +5,6 @@ import { join } from "node:path";
|
||||
import { parseBinaryTarget } from "../../dist/application/BinaryTargetResolver.js";
|
||||
import { inspectKunaInstallation } from "../../dist/kuna/KunaInstallation.js";
|
||||
import { KunaTargetCommands } from "../../dist/kuna/KunaTargetCommands.js";
|
||||
import { parseKunaStringInventory } from "../../dist/kuna/KunaStringInventory.js";
|
||||
import { parseKunaIncomingReferenceFacts } from "../../dist/kuna/KunaReferenceFiles.js";
|
||||
import { PrivateRuntimeRoot } from "../../dist/process/PrivateRuntimeRoot.js";
|
||||
import { parseNativeIR } from "./native-ir.mjs";
|
||||
@@ -16,15 +15,16 @@ import {
|
||||
pageFacts,
|
||||
projectFact,
|
||||
readFactBatch,
|
||||
freezeFacts,
|
||||
} from "./fact-reads.mjs";
|
||||
import { parseFunctionFacts, selectFunctionFacts } from "./function-facts.mjs";
|
||||
import { parseStringFacts, selectStringFacts } from "./string-facts.mjs";
|
||||
import {
|
||||
parseKunaSsaDump,
|
||||
backwardSlice,
|
||||
} from "../atomic-re-tools/kuna-ssa.mjs";
|
||||
|
||||
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
||||
const NATIVE_OUTPUT_BYTES = 8 * 1024 * 1024;
|
||||
export class KunaFactOwner {
|
||||
#lifetime = new AbortController();
|
||||
#sources = new Map();
|
||||
@@ -123,7 +123,7 @@ export class KunaFactOwner {
|
||||
installation: this.#installation,
|
||||
operation: "experiment:atomic-facts",
|
||||
arguments: arguments_,
|
||||
outputBytes: 8 * 1024 * 1024,
|
||||
outputBytes: NATIVE_OUTPUT_BYTES,
|
||||
timeoutMs: 120000,
|
||||
signal: AbortSignal.any([signal, this.#lifetime.signal]),
|
||||
}),
|
||||
@@ -196,32 +196,13 @@ export class KunaFactOwner {
|
||||
],
|
||||
signal,
|
||||
);
|
||||
this.#strings ??= freezeFacts(
|
||||
requireResult(
|
||||
parseKunaStringInventory(
|
||||
await this.#sourceText(capture.source.evidence_id),
|
||||
capture.input,
|
||||
),
|
||||
"invalid_provider_output",
|
||||
),
|
||||
);
|
||||
const inventory = this.#strings;
|
||||
const rows = inventory.items.filter((row) =>
|
||||
row.value.includes(input.query),
|
||||
this.#strings ??= parseStringFacts(
|
||||
await this.#sourceText(capture.source.evidence_id),
|
||||
capture.input,
|
||||
);
|
||||
return {
|
||||
source: sourceSummary(capture.source),
|
||||
items: rows.slice(input.offset, input.offset + input.limit),
|
||||
total: rows.length,
|
||||
next_offset:
|
||||
input.offset + input.limit < rows.length
|
||||
? input.offset + input.limit
|
||||
: null,
|
||||
coverage: {
|
||||
scope: "native string inventory, minimum 5",
|
||||
artifact_discovery: "unknown",
|
||||
reference_attribution: "not-computed",
|
||||
},
|
||||
...selectStringFacts(this.#strings, input),
|
||||
};
|
||||
}
|
||||
async functions(input, signal) {
|
||||
@@ -274,7 +255,14 @@ export class KunaFactOwner {
|
||||
path,
|
||||
input.entry,
|
||||
"--addr",
|
||||
...(this.#nativeIR ? ["--json", "--atomic-facts"] : ["--raw"]),
|
||||
...(this.#nativeIR
|
||||
? [
|
||||
"--json",
|
||||
"--atomic-facts",
|
||||
"--atomic-output-bytes",
|
||||
String(NATIVE_OUTPUT_BYTES),
|
||||
]
|
||||
: ["--raw"]),
|
||||
"--max-fn-memory",
|
||||
"1024",
|
||||
],
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { parseKunaStringInventory } from "../../dist/kuna/KunaStringInventory.js";
|
||||
import { requireResult } from "./fact-error.mjs";
|
||||
import { freezeFacts, pageFacts } from "./fact-reads.mjs";
|
||||
|
||||
export const parseStringFacts = (text, input) =>
|
||||
freezeFacts(
|
||||
requireResult(
|
||||
parseKunaStringInventory(text, input),
|
||||
"invalid_provider_output",
|
||||
),
|
||||
);
|
||||
|
||||
export const selectStringFacts = (inventory, input) => ({
|
||||
...pageFacts(
|
||||
inventory.items.filter((row) => row.value.includes(input.query)),
|
||||
input,
|
||||
),
|
||||
coverage: {
|
||||
scope: "native string inventory, minimum 5",
|
||||
scanned: inventory.scanned,
|
||||
artifact_discovery: "unknown",
|
||||
reference_attribution: "not-computed",
|
||||
},
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "../../scripts/lib/verifier-run.mjs";
|
||||
|
||||
const [binary, output, scenario = "zlib"] = process.argv.slice(2);
|
||||
assert.ok(["zlib", "sqlite-partial"].includes(scenario));
|
||||
assert.ok(["zlib", "sqlite-partial", "sqlite-encoded"].includes(scenario));
|
||||
const verifier = createVerifierRun();
|
||||
const client = new Client({ name: "rea-codemode-verifier", version: "0.1.0" });
|
||||
const transport = new StdioClientTransport({
|
||||
@@ -49,7 +49,40 @@ try {
|
||||
const ajv = new Ajv2020({ strict: false });
|
||||
for (const tool of catalog.tools) ajv.compile(tool.inputSchema);
|
||||
const opened = await call("open_binary", { path: binary });
|
||||
if (scenario === "sqlite-partial") {
|
||||
if (scenario === "sqlite-encoded") {
|
||||
for (const entry of ["0xb90e0", "0xb3d00"]) {
|
||||
const result = await call("execute_javascript", {
|
||||
code: `
|
||||
const s=await rea.snapshot({entry:${JSON.stringify(entry)}});if(!s.ok)throw new Error(JSON.stringify(s));
|
||||
const c=await rea.code({snapshot:s.data.snapshot,limit:4});if(!c.ok)throw new Error(JSON.stringify(c));
|
||||
const graph=await rea.operations({snapshot:s.data.snapshot,limit:1,fields:["opcode"]});
|
||||
return {snapshot:s.data,code:c.data,graph};`,
|
||||
});
|
||||
assert.equal(result.value.snapshot.facets.code.availability, "available");
|
||||
assert.ok(result.value.snapshot.source.bytes <= 8 * 1024 * 1024);
|
||||
assert.ok(result.value.code.total_lines > 1000);
|
||||
assert.ok(result.value.code.text.length > 0);
|
||||
assert.equal(
|
||||
result.value.code.evidence_id,
|
||||
result.value.snapshot.source.evidence_id,
|
||||
);
|
||||
if (entry === "0xb3d00") {
|
||||
assert.equal(result.value.graph.ok, true);
|
||||
assert.equal(result.value.graph.data.total, 5164);
|
||||
} else {
|
||||
assert.equal(result.value.snapshot.coverage.operations, null);
|
||||
assert.equal(result.value.graph.ok, false);
|
||||
assert.match(
|
||||
result.value.graph.error.details.native_error,
|
||||
/encoded document/u,
|
||||
);
|
||||
assert.equal(
|
||||
result.value.graph.error.details.evidence_id,
|
||||
result.value.snapshot.source.evidence_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (scenario === "sqlite-partial") {
|
||||
assert.equal(opened.context.analysis_model, "experimental-native-ir-v1");
|
||||
const result = await call("execute_javascript", {
|
||||
code: `
|
||||
|
||||
Reference in new issue
Block a user