diff --git a/src/dynamic/lang/java.rs b/src/dynamic/lang/java.rs index 25cd669f..69bfa94c 100644 --- a/src/dynamic/lang/java.rs +++ b/src/dynamic/lang/java.rs @@ -524,13 +524,18 @@ fn generate_harness_java(spec: &HarnessSpec, shape: JavaShape, entry_class: &str "" }; + // Reflection imports are only used by shapes whose helpers / catch + // clause reference them; emitting them for `StaticMethod` / + // `StaticMain` produces unused-import warnings under javac -Xlint. + let imports = if shape_uses_reflection(shape) { + "import java.lang.reflect.Method;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.InvocationTargetException;\n\n" + } else { + "" + }; + format!( r#"// Nyx dynamic harness — auto-generated, do not edit (Phase 14 — JavaShape::{shape:?}). -import java.lang.reflect.Method; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -public class NyxHarness {{ +{imports}public class NyxHarness {{ {probe} {helpers} public static void main(String[] args) {{ @@ -557,6 +562,7 @@ public class NyxHarness {{ }} "#, shape = shape, + imports = imports, probe = probe, helpers = helpers, pre_call = pre_call, @@ -989,4 +995,53 @@ mod tests { let harness = emit(&spec).unwrap(); assert_eq!(harness.entry_subpath, Some("Entry.java".to_owned())); } + + #[test] + fn detect_shape_reads_file_and_returns_shape() { + // Drive the public `detect_shape(spec)` wrapper end-to-end: + // write a representative source to a tempfile, then assert the + // wrapper reads it and produces the expected JavaShape variant. + let dir = std::env::temp_dir().join(format!( + "nyx_detect_shape_{}", + std::process::id() + )); + let _ = std::fs::create_dir_all(&dir); + let cases: &[(&str, &str, &str, EntryKind, JavaShape)] = &[ + ( + "Servlet.java", + "import javax.servlet.http.HttpServletRequest;\npublic class Servlet extends HttpServlet { public void doGet(HttpServletRequest r, HttpServletResponse w) {} }", + "doGet", + EntryKind::HttpRoute, + JavaShape::ServletDoGet, + ), + ( + "Spring.java", + "@RestController\npublic class Spring { @GetMapping(\"/x\") public String run(String p) { return p; } }", + "run", + EntryKind::HttpRoute, + JavaShape::SpringController, + ), + ( + "MainClass.java", + "public class MainClass { public static void main(String[] args) {} }", + "main", + EntryKind::CliSubcommand, + JavaShape::StaticMain, + ), + ( + "Plain.java", + "public class Plain { public static void run(String p) {} }", + "run", + EntryKind::Function, + JavaShape::StaticMethod, + ), + ]; + for (name, body, entry_name, kind, expected) in cases { + let path = dir.join(name); + std::fs::write(&path, body).expect("write fixture"); + let spec = make_spec_with(*kind, entry_name, path.to_str().unwrap()); + assert_eq!(detect_shape(&spec), *expected, "case {name}"); + } + let _ = std::fs::remove_dir_all(&dir); + } } diff --git a/src/dynamic/lang/javascript.rs b/src/dynamic/lang/javascript.rs index 7c0cd3d0..36a7e6d5 100644 --- a/src/dynamic/lang/javascript.rs +++ b/src/dynamic/lang/javascript.rs @@ -50,24 +50,6 @@ pub fn emit(spec: &HarnessSpec) -> Result { js_shared::emit(spec, false) } -/// Derive the JS module name from an entry file path. -/// -/// Always returns `"entry"` because the JS harness stages the entry file at -/// `workdir/entry.js` so `require('./entry')` is the only path that resolves -/// regardless of the source file's original name. -pub fn entry_module_name(_entry_file: &str) -> String { - "entry".to_owned() -} - -/// Derive the entry filename from an entry file path. -/// -/// Always `"entry.js"` for the JS surface; TypeScript uses `"entry.ts"` (see -/// [`crate::dynamic::lang::typescript`]) and ESM-default shapes use -/// `"entry.mjs"` (handled inside `js_shared`). -pub fn entry_module_filename(_entry_file: &str) -> String { - "entry.js".to_owned() -} - #[cfg(test)] mod tests { use super::*; @@ -164,11 +146,4 @@ mod tests { assert!(hint.contains("Phase 13")); } - #[test] - fn entry_module_name_is_always_entry_to_match_copy_destination() { - assert_eq!(entry_module_name("src/handlers/login.js"), "entry"); - assert_eq!(entry_module_name("app.ts"), "entry"); - assert_eq!(entry_module_name("handler.mjs"), "entry"); - assert_eq!(entry_module_name("no_ext"), "entry"); - } } diff --git a/src/dynamic/spec.rs b/src/dynamic/spec.rs index 9a5fe86c..e4a06046 100644 --- a/src/dynamic/spec.rs +++ b/src/dynamic/spec.rs @@ -988,7 +988,7 @@ fn finalize_spec( sink_line: u32, derivation: SpecDerivationStrategy, ) -> HarnessSpec { - let toolchain_id = toolchain_id_for_lang(lang).to_owned(); + let toolchain_id = default_toolchain_id(lang).to_owned(); let stubs_required = StubKind::for_cap(expected_cap); let mut spec = HarnessSpec { finding_id: format!("{:016x}", diag.stable_hash), @@ -1031,7 +1031,7 @@ pub fn outermost_entry(steps: &[crate::evidence::FlowStep]) -> Option /// Default toolchain label for a language (informational; harness builder /// may override for locally-installed compilers/runtimes). -fn toolchain_id_for_lang(lang: Lang) -> &'static str { +pub fn default_toolchain_id(lang: Lang) -> &'static str { match lang { Lang::Rust => "rust-stable", Lang::C => "gcc-stable", diff --git a/tests/common/fixture_harness.rs b/tests/common/fixture_harness.rs index b0e8d5e0..b0d0dd73 100644 --- a/tests/common/fixture_harness.rs +++ b/tests/common/fixture_harness.rs @@ -283,17 +283,7 @@ pub fn run_shape_fixture_lang( u64::from_le_bytes(bytes.as_bytes()[..8].try_into().unwrap()) }); - let toolchain_id = match lang { - nyx_scanner::symbol::Lang::Python => "python-3", - nyx_scanner::symbol::Lang::JavaScript | nyx_scanner::symbol::Lang::TypeScript => "node-20", - nyx_scanner::symbol::Lang::Rust => "rust-stable", - nyx_scanner::symbol::Lang::Go => "go-1.21", - nyx_scanner::symbol::Lang::Java => "java-17", - nyx_scanner::symbol::Lang::Php => "php-8", - nyx_scanner::symbol::Lang::Ruby => "ruby-3", - nyx_scanner::symbol::Lang::C => "gcc", - nyx_scanner::symbol::Lang::Cpp => "g++", - }; + let toolchain_id = nyx_scanner::dynamic::spec::default_toolchain_id(lang); let spec = HarnessSpec { finding_id: spec_hash.clone(), @@ -482,11 +472,7 @@ pub fn run_harness_snapshot_lang( std::fs::copy(&fixture_src, &dst).expect("copy fixture into tempdir"); let entry_file = dst.to_string_lossy().into_owned(); - let toolchain_id = match lang { - nyx_scanner::symbol::Lang::Python => "python-3", - nyx_scanner::symbol::Lang::JavaScript | nyx_scanner::symbol::Lang::TypeScript => "node-20", - _ => "unknown", - }; + let toolchain_id = nyx_scanner::dynamic::spec::default_toolchain_id(lang); let spec = HarnessSpec { finding_id: "0000000000000001".into(),