refactor(dynamic): standardize shell commands across fixtures, add __NYX_SINK_HIT__ markers, improve PHP support

This commit is contained in:
elipeter 2026-05-23 10:31:57 -05:00
parent ca075a7141
commit fe09986a25
32 changed files with 707 additions and 71 deletions

View file

@ -503,6 +503,7 @@ int main(int argc, char *argv[]) {{
if (!payload) payload = (char*)"";
__nyx_install_crash_guard("{symbol}");
{symbol}(payload, strlen(payload));
puts("__NYX_SINK_HIT__");
return 0;
}}

View file

@ -451,6 +451,7 @@ int main(int argc, char *argv[]) {{
__nyx_install_crash_guard("{class}::{method}");
{class} instance;
instance.{method}(payload);
std::cout << "__NYX_SINK_HIT__" << std::endl;
return 0;
}}

View file

@ -1928,6 +1928,7 @@ func main() {{
}}
}}
out := m.Call(args)
fmt.Println("__NYX_SINK_HIT__")
if len(out) > 0 {{
fmt.Println(out[0].Interface())
}}

View file

@ -3383,6 +3383,7 @@ public class NyxHarness {{
mArgs[i] = params[i].equals(String.class) ? payload : nyxStubForType(params[i]);
}}
match.invoke(instance, mArgs);
System.out.println("__NYX_SINK_HIT__");
}} catch (InvocationTargetException ite) {{
Throwable cause = ite.getCause() == null ? ite : ite.getCause();
System.err.println("NYX_EXCEPTION: " + cause.getClass().getName() + ": " + cause.getMessage());

View file

@ -725,14 +725,10 @@ fn emit_class_method(
_spec: &HarnessSpec,
class: &str,
method: &str,
is_typescript: bool,
_is_typescript: bool,
) -> HarnessSource {
let probe = probe_shim();
let entry_subpath = if is_typescript {
"entry.ts"
} else {
"entry.js"
};
let entry_subpath = "entry.js";
let entry_require_path = entry_require_path(entry_subpath);
let mock_http = crate::dynamic::stubs::mock_source(
crate::dynamic::stubs::MockKind::HttpClient,
@ -806,6 +802,7 @@ if (typeof _m !== 'function') {{
(async () => {{
try {{
const _result = await Promise.resolve(_m.call(_instance, payload));
process.stdout.write('__NYX_SINK_HIT__\n');
if (_result != null) process.stdout.write(String(_result) + '\n');
}} catch (e) {{
process.stderr.write('NYX_EXCEPTION: ' + (e.constructor ? e.constructor.name : 'Error') + ': ' + e.message + '\n');

View file

@ -29,6 +29,7 @@
//! Build container: `nyx-build-php:{toolchain_id}` (deferred; §19.1).
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
use crate::dynamic::framework::HttpMethod;
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
use crate::evidence::UnsupportedReason;
@ -1943,6 +1944,7 @@ fn generate_source(spec: &HarnessSpec, shape: PhpShape) -> String {
let call_expr = build_call_expr(spec, shape, entry_fn);
let shim = probe_shim();
let toolchain_marker = build_toolchain_marker(shape);
let route_methods_fn = build_route_methods_fn(spec);
let crash_callee = if entry_fn.is_empty() {
"main"
} else {
@ -1966,6 +1968,8 @@ function nyx_payload(): string {{
return '';
}}
{route_methods_fn}
$payload = nyx_payload();
// Phase 08 sink-site signal handler: install AFTER payload decode so a crash
@ -1980,13 +1984,21 @@ __nyx_install_crash_guard('{crash_callee}');
{entry_block}
// ── Framework toolchain marker (Phase 16 — Track L.14) ────────────────────────
{toolchain_marker}// ── Call entry point ──────────────────────────────────────────────────────────
try {{
$result = {call_expr};
if ($result !== null) {{
echo $result . "\n";
foreach (__nyx_route_methods() as $__nyx_method) {{
if ($__nyx_method !== '') {{
$_SERVER['REQUEST_METHOD'] = $__nyx_method;
putenv('REQUEST_METHOD=' . $__nyx_method);
$_ENV['REQUEST_METHOD'] = $__nyx_method;
$GLOBALS['__nyx_request_method'] = $__nyx_method;
}}
try {{
$result = {call_expr};
if ($result !== null) {{
echo $result . "\n";
}}
}} catch (Throwable $e) {{
fwrite(STDERR, 'NYX_EXCEPTION: ' . get_class($e) . ': ' . $e->getMessage() . "\n");
}}
}} catch (Throwable $e) {{
fwrite(STDERR, 'NYX_EXCEPTION: ' . get_class($e) . ': ' . $e->getMessage() . "\n");
}}
"#,
shape = shape,
@ -1995,10 +2007,45 @@ try {{
call_expr = call_expr,
shim = shim,
toolchain_marker = toolchain_marker,
route_methods_fn = route_methods_fn,
crash_callee = crash_callee,
)
}
fn build_route_methods_fn(spec: &HarnessSpec) -> String {
let mut methods = spec
.framework
.as_ref()
.and_then(|binding| binding.route.as_ref())
.map(|route| route.reachable_methods())
.unwrap_or_default();
if methods.is_empty() && matches!(spec.entry_kind, crate::evidence::EntryKind::HttpRoute) {
methods.push(HttpMethod::GET);
}
let body = if methods.is_empty() {
"''".to_owned()
} else {
methods
.iter()
.map(|method| format!("'{}'", http_method_name(*method)))
.collect::<Vec<_>>()
.join(", ")
};
format!("function __nyx_route_methods(): array {{\n return [{body}];\n}}\n",)
}
fn http_method_name(method: HttpMethod) -> &'static str {
match method {
HttpMethod::GET => "GET",
HttpMethod::HEAD => "HEAD",
HttpMethod::POST => "POST",
HttpMethod::PUT => "PUT",
HttpMethod::PATCH => "PATCH",
HttpMethod::DELETE => "DELETE",
HttpMethod::OPTIONS => "OPTIONS",
}
}
fn build_pre_call(spec: &HarnessSpec, shape: PhpShape) -> String {
let mut out = String::new();
match &spec.payload_slot {
@ -2671,6 +2718,7 @@ if (!method_exists($instance, {method_lit:?})) {{
}}
try {{
$result = call_user_func([$instance, {method_lit:?}], $payload);
echo "__NYX_SINK_HIT__\n";
if ($result !== null) {{
echo $result . "\n";
}}
@ -2889,6 +2937,7 @@ fn function_exists_call(_func: &str) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use crate::dynamic::framework::{FrameworkBinding, RouteShape};
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
use crate::labels::Cap;
use crate::symbol::Lang;
@ -3048,6 +3097,26 @@ mod tests {
assert!(src.contains("$GLOBALS['__nyx_route']"));
}
#[test]
fn laravel_shape_fans_out_framework_route_methods() {
let mut spec = make_spec_with(EntryKind::HttpRoute, "run", "entry.php");
spec.framework = Some(FrameworkBinding {
adapter: "php-laravel".to_owned(),
kind: EntryKind::HttpRoute,
route: Some(RouteShape::multi(
vec![HttpMethod::GET, HttpMethod::POST, HttpMethod::PATCH],
"/run",
)),
request_params: vec![],
response_writer: None,
middleware: vec![],
});
let src = generate_source(&spec, PhpShape::LaravelRoute);
assert!(src.contains("return ['GET', 'POST', 'PATCH'];"));
assert!(src.contains("foreach (__nyx_route_methods() as $__nyx_method)"));
assert!(src.contains("$_SERVER['REQUEST_METHOD'] = $__nyx_method;"));
}
#[test]
fn symfony_shape_emits_toolchain_marker_and_controller_dispatch() {
let spec = make_spec_with(EntryKind::HttpRoute, "run", "entry.php");

View file

@ -875,6 +875,7 @@ try:
print("NYX_METHOD_NOT_FOUND: " + {method:?}, file=sys.stderr, flush=True)
sys.exit(78)
_result = _m(payload)
print("__NYX_SINK_HIT__", flush=True)
if _result is not None:
try:
print(str(_result), flush=True)

View file

@ -607,6 +607,7 @@ unless instance.respond_to?({method:?})
end
begin
result = instance.send({method:?}, $nyx_payload)
puts "__NYX_SINK_HIT__"
print(result.to_s) if result
rescue StandardError => e
STDERR.puts("NYX_EXCEPTION: #{{e.class.name}}: #{{e.message}}")

View file

@ -2043,6 +2043,7 @@ fn main() {{
__nyx_install_crash_guard("{entry_label}");
let instance = entry::{class}::{ctor}();
let _ = instance.{method}(&payload);
println!("__NYX_SINK_HIT__");
}}
fn nyx_payload() -> String {{