From c162c638a20343b7cc708152080677509b59cffd Mon Sep 17 00:00:00 2001 From: pitboss Date: Sat, 16 May 2026 06:10:23 -0500 Subject: [PATCH] [pitboss/grind] deferred session-0010 (20260516T052512Z-20f8) --- src/dynamic/lang/go.rs | 110 ++++++++++++++++++++++++++++++++------- src/dynamic/lang/php.rs | 41 ++++++++++++++- src/dynamic/lang/ruby.rs | 36 ++++++++++++- 3 files changed, 167 insertions(+), 20 deletions(-) diff --git a/src/dynamic/lang/go.rs b/src/dynamic/lang/go.rs index bec3d456..919c5ad0 100644 --- a/src/dynamic/lang/go.rs +++ b/src/dynamic/lang/go.rs @@ -412,6 +412,7 @@ fn generate_main_go(spec: &HarnessSpec, shape: GoShape) -> String { let pre_call = pre_call_setup(spec); let imports = imports_for_shape(shape); let invocation = invoke_for_shape(spec, shape, &entry_fn); + let shim = probe_shim(); format!( r#"// Nyx dynamic harness — auto-generated, do not edit (Phase 15 — GoShape::{shape:?}). @@ -419,10 +420,12 @@ package main import ( {imports}) - +{shim} func main() {{ payload := nyxPayload() _ = payload + __nyx_install_crash_guard("{entry_fn}") + defer __nyx_recover_crash("{entry_fn}")() {pre_call}{invocation} }} @@ -442,27 +445,57 @@ func nyxPayload() string {{ imports = imports, pre_call = pre_call, invocation = invocation, + shim = shim, + entry_fn = entry_fn, ) } -fn imports_for_shape(shape: GoShape) -> &'static str { - match shape { - GoShape::Generic => { - "\t\"encoding/base64\"\n\t\"os\"\n\n\t\"nyx-harness/entry\"\n" - } - GoShape::HttpHandlerFunc => { - "\t\"encoding/base64\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\n\t\"nyx-harness/entry\"\n" - } - GoShape::GinHandler => { - "\t\"encoding/base64\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\n\t\"nyx-harness/entry\"\n\t\"nyx-harness/entry/gin\"\n" - } - GoShape::FlagParseCli => { - "\t\"encoding/base64\"\n\t\"os\"\n\n\t\"nyx-harness/entry\"\n" - } - GoShape::FuzzVariadic => { - "\t\"encoding/base64\"\n\t\"os\"\n\n\t\"nyx-harness/entry\"\n" - } +/// Imports required by the spliced probe shim. Always present, deduped +/// against per-shape additions in [`imports_for_shape`]. +const SHIM_IMPORTS: &[&str] = &[ + "encoding/json", + "os/signal", + "strings", + "syscall", + "time", +]; + +fn imports_for_shape(shape: GoShape) -> String { + let stdlib_base: &[&str] = &["encoding/base64", "os"]; + let shape_extras: &[&str] = match shape { + GoShape::Generic | GoShape::FlagParseCli | GoShape::FuzzVariadic => &[], + GoShape::HttpHandlerFunc => &["net/http", "net/http/httptest"], + GoShape::GinHandler => &["net/http", "net/http/httptest"], + }; + let local_pkgs: &[&str] = match shape { + GoShape::GinHandler => &["nyx-harness/entry", "nyx-harness/entry/gin"], + _ => &["nyx-harness/entry"], + }; + + let mut stdlib: Vec<&str> = stdlib_base + .iter() + .copied() + .chain(shape_extras.iter().copied()) + .chain(SHIM_IMPORTS.iter().copied()) + .collect(); + stdlib.sort_unstable(); + stdlib.dedup(); + + let mut out = String::new(); + for path in &stdlib { + out.push('\t'); + out.push('"'); + out.push_str(path); + out.push_str("\"\n"); } + out.push('\n'); + for path in local_pkgs { + out.push('\t'); + out.push('"'); + out.push_str(path); + out.push_str("\"\n"); + } + out } fn pre_call_setup(spec: &HarnessSpec) -> String { @@ -772,4 +805,45 @@ mod tests { let src = generate_main_go(&spec, GoShape::FuzzVariadic); assert!(src.contains("entry.FuzzHandle([]byte(payload))")); } + + #[test] + fn emit_splices_probe_shim_and_installs_crash_guard() { + let spec = make_spec(PayloadSlot::Param(0)); + let h = emit(&spec).unwrap(); + assert!( + h.source.contains("__nyx_probe shim (Phase 06 — Track C.1"), + "probe_shim banner missing from generated main.go — splicing regressed", + ); + assert!( + h.source.contains("func __nyx_install_crash_guard("), + "install_crash_guard definition missing from generated main.go", + ); + assert!( + h.source.contains("__nyx_install_crash_guard(\"HandleRequest\")"), + "install_crash_guard call site missing or wrong callee in main()", + ); + let install_pos = h + .source + .find("__nyx_install_crash_guard(\"HandleRequest\")") + .unwrap(); + let payload_pos = h.source.find("payload := nyxPayload()").unwrap(); + let invoke_pos = h.source.find("entry.HandleRequest(payload)").unwrap(); + assert!( + payload_pos < install_pos && install_pos < invoke_pos, + "install_crash_guard ordering wrong: payload_pos={payload_pos} install_pos={install_pos} invoke_pos={invoke_pos}", + ); + } + + #[test] + fn emit_includes_shim_imports_in_import_block() { + let spec = make_spec(PayloadSlot::Param(0)); + let h = emit(&spec).unwrap(); + for path in SHIM_IMPORTS { + let quoted = format!("\"{path}\""); + assert!( + h.source.contains("ed), + "expected shim-required import {quoted} in generated main.go", + ); + } + } } diff --git a/src/dynamic/lang/php.rs b/src/dynamic/lang/php.rs index 0fc9680a..c65d9635 100644 --- a/src/dynamic/lang/php.rs +++ b/src/dynamic/lang/php.rs @@ -359,11 +359,13 @@ fn generate_source(spec: &HarnessSpec, shape: PhpShape) -> String { let pre_call = build_pre_call(spec, shape); let entry_block = build_entry_block(shape); let call_expr = build_call_expr(spec, shape, entry_fn); + let shim = probe_shim(); + let crash_callee = if entry_fn.is_empty() { "main" } else { entry_fn.as_str() }; format!( r#" String { let entry_fn = &spec.entry_name; let pre_call = build_pre_call(spec); let invocation = invoke_for_shape(spec, shape, entry_fn); + let shim = probe_shim(); + let crash_callee = if entry_fn.is_empty() { "main" } else { entry_fn.as_str() }; format!( r#"# Nyx dynamic harness — auto-generated, do not edit (Phase 15 — RubyShape::{shape:?}). - +{shim} # ── Payload loading ────────────────────────────────────────────────────────── def nyx_payload v = ENV['NYX_PAYLOAD'] @@ -372,6 +374,12 @@ def nyx_payload end $nyx_payload = nyx_payload + +# Phase 08 sink-site signal trap: install AFTER payload decode so a crash +# inside `nyx_payload` writes no Crash probe and routes the verifier to +# `Inconclusive(UnrelatedCrash)`. A fatal signal inside the entry call +# below DOES fire the handler and writes a Crash probe to `NYX_PROBE_PATH`. +__nyx_install_crash_guard('{crash_callee}') {pre_call} # ── Sinatra route registry ────────────────────────────────────────────────── $nyx_sinatra_routes ||= [] @@ -734,4 +742,30 @@ mod tests { assert_eq!(parse_first_class_name("class Bar < Base\nend\n"), Some("Bar".to_owned())); assert_eq!(parse_first_class_name("def foo\nend\n"), None); } + + #[test] + fn emit_splices_probe_shim_and_installs_crash_guard() { + let spec = make_spec(PayloadSlot::Param(0)); + let h = emit(&spec).unwrap(); + assert!( + h.source.contains("__nyx_probe shim (Phase 06 — Track C.1"), + "probe_shim banner missing from generated harness.rb — splicing regressed", + ); + assert!( + h.source.contains("def __nyx_install_crash_guard(sink_callee)"), + "install_crash_guard definition missing from generated harness.rb", + ); + assert!( + h.source.contains("__nyx_install_crash_guard('login')"), + "install_crash_guard call site missing or wrong callee in harness body", + ); + let install_pos = h.source.find("__nyx_install_crash_guard('login')").unwrap(); + let payload_pos = h.source.find("$nyx_payload = nyx_payload").unwrap(); + // The invocation is `login($nyx_payload)` for the default Generic shape. + let invoke_pos = h.source.find("login($nyx_payload)").unwrap(); + assert!( + payload_pos < install_pos && install_pos < invoke_pos, + "install_crash_guard ordering wrong: payload_pos={payload_pos} install_pos={install_pos} invoke_pos={invoke_pos}", + ); + } }