mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-15 20:05:13 +02:00
[pitboss] phase 18: Track M.0 — New EntryKind variants: ClassMethod, MessageHandler, ScheduledJob, GraphQLResolver, WebSocket, Middleware, Migration
This commit is contained in:
parent
2b96c6005b
commit
1b2f9cb7ca
16 changed files with 750 additions and 178 deletions
|
|
@ -28,7 +28,7 @@
|
|||
//! - `PayloadSlot::Argv(n)` — `main(argc, argv)` shape: appended to argv.
|
||||
|
||||
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -40,10 +40,10 @@ pub struct CEmitter;
|
|||
/// `Function` covers free functions (libfuzzer-style + plain (const
|
||||
/// char*, size_t)). `CliSubcommand` covers `main(argc, argv)`.
|
||||
/// `LibraryApi` covers libFuzzer `LLVMFuzzerTestOneInput`.
|
||||
const SUPPORTED: &[EntryKind] = &[
|
||||
EntryKind::Function,
|
||||
EntryKind::CliSubcommand,
|
||||
EntryKind::LibraryApi,
|
||||
const SUPPORTED: &[EntryKindTag] = &[
|
||||
EntryKindTag::Function,
|
||||
EntryKindTag::CliSubcommand,
|
||||
EntryKindTag::LibraryApi,
|
||||
];
|
||||
|
||||
// ── Phase 16: shape detector ─────────────────────────────────────────────────
|
||||
|
|
@ -66,7 +66,7 @@ impl CShape {
|
|||
/// Detect the shape from `(spec, source)`.
|
||||
pub fn detect(spec: &HarnessSpec, source: &str) -> Self {
|
||||
let entry = spec.entry_name.as_str();
|
||||
let kind = spec.entry_kind;
|
||||
let kind = spec.entry_kind.tag();
|
||||
|
||||
let has_main_argv = (source.contains("int main(") || source.contains("int main ("))
|
||||
&& (source.contains("argc") || source.contains("char *argv")
|
||||
|
|
@ -80,8 +80,8 @@ impl CShape {
|
|||
return Self::MainArgv;
|
||||
}
|
||||
match kind {
|
||||
EntryKind::CliSubcommand => Self::MainArgv,
|
||||
EntryKind::LibraryApi => Self::LibfuzzerEntry,
|
||||
EntryKindTag::CliSubcommand => Self::MainArgv,
|
||||
EntryKindTag::LibraryApi => Self::LibfuzzerEntry,
|
||||
_ => Self::FreeFn,
|
||||
}
|
||||
}
|
||||
|
|
@ -362,13 +362,13 @@ impl LangEmitter for CEmitter {
|
|||
emit(spec)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"c emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 16 shape dispatch (main / libFuzzer / free function)"
|
||||
"c emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 16 / 19 / 20 / 21 shape dispatch (main / libFuzzer / free function + future class / msg / job adapters)"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -646,7 +646,7 @@ clean:
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -674,14 +674,14 @@ mod tests {
|
|||
#[test]
|
||||
fn entry_kinds_supported_is_non_empty() {
|
||||
assert!(!CEmitter.entry_kinds_supported().is_empty());
|
||||
assert!(CEmitter.entry_kinds_supported().contains(&EntryKind::Function));
|
||||
assert!(CEmitter.entry_kinds_supported().contains(&EntryKind::CliSubcommand));
|
||||
assert!(CEmitter.entry_kinds_supported().contains(&EntryKind::LibraryApi));
|
||||
assert!(CEmitter.entry_kinds_supported().contains(&EntryKindTag::Function));
|
||||
assert!(CEmitter.entry_kinds_supported().contains(&EntryKindTag::CliSubcommand));
|
||||
assert!(CEmitter.entry_kinds_supported().contains(&EntryKindTag::LibraryApi));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted_and_phase() {
|
||||
let hint = CEmitter.entry_kind_hint(EntryKind::LibraryApi);
|
||||
let hint = CEmitter.entry_kind_hint(EntryKindTag::LibraryApi);
|
||||
assert!(hint.contains("LibraryApi"));
|
||||
assert!(hint.contains("Phase 16"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
//! `g++ -O0 -std=c++17 -o nyx_harness main.cpp` in the workdir.
|
||||
|
||||
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -24,10 +24,10 @@ use std::path::PathBuf;
|
|||
pub struct CppEmitter;
|
||||
|
||||
/// Entry kinds the C++ emitter understands after Phase 16.
|
||||
const SUPPORTED: &[EntryKind] = &[
|
||||
EntryKind::Function,
|
||||
EntryKind::CliSubcommand,
|
||||
EntryKind::LibraryApi,
|
||||
const SUPPORTED: &[EntryKindTag] = &[
|
||||
EntryKindTag::Function,
|
||||
EntryKindTag::CliSubcommand,
|
||||
EntryKindTag::LibraryApi,
|
||||
];
|
||||
|
||||
// ── Phase 16: shape detector ─────────────────────────────────────────────────
|
||||
|
|
@ -47,7 +47,7 @@ pub enum CppShape {
|
|||
impl CppShape {
|
||||
pub fn detect(spec: &HarnessSpec, source: &str) -> Self {
|
||||
let entry = spec.entry_name.as_str();
|
||||
let kind = spec.entry_kind;
|
||||
let kind = spec.entry_kind.tag();
|
||||
|
||||
let has_main_argv = (source.contains("int main(") || source.contains("int main ("))
|
||||
&& (source.contains("argc") || source.contains("char *argv")
|
||||
|
|
@ -62,8 +62,8 @@ impl CppShape {
|
|||
return Self::MainArgv;
|
||||
}
|
||||
match kind {
|
||||
EntryKind::CliSubcommand => Self::MainArgv,
|
||||
EntryKind::LibraryApi => Self::LibfuzzerEntry,
|
||||
EntryKindTag::CliSubcommand => Self::MainArgv,
|
||||
EntryKindTag::LibraryApi => Self::LibfuzzerEntry,
|
||||
_ => Self::FreeFn,
|
||||
}
|
||||
}
|
||||
|
|
@ -315,13 +315,13 @@ impl LangEmitter for CppEmitter {
|
|||
emit(spec)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"cpp emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 16 shape dispatch (main / libFuzzer / free function)"
|
||||
"cpp emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 16 / 19 / 20 / 21 shape dispatch (main / libFuzzer / free function + future class / msg / job adapters)"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -563,7 +563,7 @@ add_executable(nyx_harness main.cpp)
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -591,14 +591,14 @@ mod tests {
|
|||
#[test]
|
||||
fn entry_kinds_supported_is_non_empty() {
|
||||
assert!(!CppEmitter.entry_kinds_supported().is_empty());
|
||||
assert!(CppEmitter.entry_kinds_supported().contains(&EntryKind::Function));
|
||||
assert!(CppEmitter.entry_kinds_supported().contains(&EntryKind::CliSubcommand));
|
||||
assert!(CppEmitter.entry_kinds_supported().contains(&EntryKind::LibraryApi));
|
||||
assert!(CppEmitter.entry_kinds_supported().contains(&EntryKindTag::Function));
|
||||
assert!(CppEmitter.entry_kinds_supported().contains(&EntryKindTag::CliSubcommand));
|
||||
assert!(CppEmitter.entry_kinds_supported().contains(&EntryKindTag::LibraryApi));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted_and_phase() {
|
||||
let hint = CppEmitter.entry_kind_hint(EntryKind::CliSubcommand);
|
||||
let hint = CppEmitter.entry_kind_hint(EntryKindTag::CliSubcommand);
|
||||
assert!(hint.contains("CliSubcommand"));
|
||||
assert!(hint.contains("Phase 16"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -51,10 +51,10 @@ pub struct GoEmitter;
|
|||
/// `HttpRoute` covers `net/http` and gin handlers. `CliSubcommand`
|
||||
/// covers `flag.Parse` CLIs. `Function` covers plain functions and
|
||||
/// fuzz harnesses.
|
||||
const SUPPORTED: &[EntryKind] = &[
|
||||
EntryKind::Function,
|
||||
EntryKind::HttpRoute,
|
||||
EntryKind::CliSubcommand,
|
||||
const SUPPORTED: &[EntryKindTag] = &[
|
||||
EntryKindTag::Function,
|
||||
EntryKindTag::HttpRoute,
|
||||
EntryKindTag::CliSubcommand,
|
||||
];
|
||||
|
||||
impl LangEmitter for GoEmitter {
|
||||
|
|
@ -62,13 +62,13 @@ impl LangEmitter for GoEmitter {
|
|||
emit(spec)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"go emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 15 shape dispatch"
|
||||
"go emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 15 / 19 / 20 / 21 shape dispatch"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ impl GoShape {
|
|||
/// to [`Self::Generic`]).
|
||||
pub fn detect(spec: &HarnessSpec, source: &str) -> Self {
|
||||
let entry = spec.entry_name.as_str();
|
||||
let kind = spec.entry_kind;
|
||||
let kind = spec.entry_kind.tag();
|
||||
|
||||
let has_http_handler = source.contains("http.ResponseWriter")
|
||||
&& source.contains("*http.Request");
|
||||
|
|
@ -265,10 +265,10 @@ impl GoShape {
|
|||
if has_fuzz_signature {
|
||||
return Self::FuzzVariadic;
|
||||
}
|
||||
if kind == EntryKind::HttpRoute {
|
||||
if kind == EntryKindTag::HttpRoute {
|
||||
return Self::HttpHandlerFunc;
|
||||
}
|
||||
if kind == EntryKind::CliSubcommand {
|
||||
if kind == EntryKindTag::CliSubcommand {
|
||||
return Self::FlagParseCli;
|
||||
}
|
||||
Self::Generic
|
||||
|
|
@ -1098,7 +1098,7 @@ pub fn capitalize_first(s: &str) -> String {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -1168,14 +1168,14 @@ mod tests {
|
|||
#[test]
|
||||
fn entry_kinds_supported_is_non_empty() {
|
||||
assert!(!GoEmitter.entry_kinds_supported().is_empty());
|
||||
assert!(GoEmitter.entry_kinds_supported().contains(&EntryKind::Function));
|
||||
assert!(GoEmitter.entry_kinds_supported().contains(&EntryKind::HttpRoute));
|
||||
assert!(GoEmitter.entry_kinds_supported().contains(&EntryKind::CliSubcommand));
|
||||
assert!(GoEmitter.entry_kinds_supported().contains(&EntryKindTag::Function));
|
||||
assert!(GoEmitter.entry_kinds_supported().contains(&EntryKindTag::HttpRoute));
|
||||
assert!(GoEmitter.entry_kinds_supported().contains(&EntryKindTag::CliSubcommand));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted_and_phase() {
|
||||
let hint = GoEmitter.entry_kind_hint(EntryKind::LibraryApi);
|
||||
let hint = GoEmitter.entry_kind_hint(EntryKindTag::LibraryApi);
|
||||
assert!(hint.contains("LibraryApi"));
|
||||
assert!(hint.contains("Phase 15"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -50,10 +50,10 @@ pub struct JavaEmitter;
|
|||
/// `HttpRoute` covers servlet / Spring / Quarkus shapes. `CliSubcommand`
|
||||
/// covers `public static void main(String[])`. `Function` covers JUnit
|
||||
/// tests and plain static methods.
|
||||
const SUPPORTED: &[EntryKind] = &[
|
||||
EntryKind::Function,
|
||||
EntryKind::HttpRoute,
|
||||
EntryKind::CliSubcommand,
|
||||
const SUPPORTED: &[EntryKindTag] = &[
|
||||
EntryKindTag::Function,
|
||||
EntryKindTag::HttpRoute,
|
||||
EntryKindTag::CliSubcommand,
|
||||
];
|
||||
|
||||
impl LangEmitter for JavaEmitter {
|
||||
|
|
@ -61,13 +61,13 @@ impl LangEmitter for JavaEmitter {
|
|||
emit(spec)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"java emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 14 shape dispatch"
|
||||
"java emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 14 / 19 / 20 / 21 shape dispatch"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ impl JavaShape {
|
|||
/// pipeline tagged the entry kind as [`EntryKind::Function`].
|
||||
pub fn detect(spec: &HarnessSpec, source: &str) -> Self {
|
||||
let entry = spec.entry_name.as_str();
|
||||
let kind = spec.entry_kind;
|
||||
let kind = spec.entry_kind.tag();
|
||||
|
||||
let has_servlet = source.contains("HttpServlet")
|
||||
|| source.contains("javax.servlet")
|
||||
|
|
@ -256,10 +256,10 @@ impl JavaShape {
|
|||
return Self::JunitTest;
|
||||
}
|
||||
|
||||
if kind == EntryKind::CliSubcommand {
|
||||
if kind == EntryKindTag::CliSubcommand {
|
||||
return Self::StaticMain;
|
||||
}
|
||||
if kind == EntryKind::HttpRoute {
|
||||
if kind == EntryKindTag::HttpRoute {
|
||||
return Self::SpringController;
|
||||
}
|
||||
Self::StaticMethod
|
||||
|
|
@ -1810,7 +1810,7 @@ const JUNIT_HELPER: &str = r#"
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -1883,18 +1883,18 @@ mod tests {
|
|||
assert!(!JavaEmitter.entry_kinds_supported().is_empty());
|
||||
assert!(JavaEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::Function));
|
||||
.contains(&EntryKindTag::Function));
|
||||
assert!(JavaEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::HttpRoute));
|
||||
.contains(&EntryKindTag::HttpRoute));
|
||||
assert!(JavaEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::CliSubcommand));
|
||||
.contains(&EntryKindTag::CliSubcommand));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted_and_phase() {
|
||||
let hint = JavaEmitter.entry_kind_hint(EntryKind::LibraryApi);
|
||||
let hint = JavaEmitter.entry_kind_hint(EntryKindTag::LibraryApi);
|
||||
assert!(hint.contains("LibraryApi"));
|
||||
assert!(hint.contains("Phase 14"));
|
||||
}
|
||||
|
|
@ -2380,7 +2380,7 @@ mod tests {
|
|||
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());
|
||||
let spec = make_spec_with(kind.clone(), entry_name, path.to_str().unwrap());
|
||||
assert_eq!(detect_shape(&spec), *expected, "case {name}");
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{js_shared, ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
|
||||
pub use js_shared::{detect_shape, materialize_node, probe_shim, JsShape};
|
||||
|
|
@ -29,13 +29,13 @@ impl LangEmitter for JavaScriptEmitter {
|
|||
emit(spec)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
js_shared::SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"javascript emitter supports {supported:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 13 shape dispatch in `js_shared`",
|
||||
"javascript emitter supports {supported:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 13 / 19 / 20 / 21 shape dispatch in `js_shared`",
|
||||
supported = js_shared::SUPPORTED,
|
||||
)
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -144,14 +144,14 @@ mod tests {
|
|||
#[test]
|
||||
fn entry_kinds_supported_includes_http_and_cli_after_phase_13() {
|
||||
let kinds = JavaScriptEmitter.entry_kinds_supported();
|
||||
assert!(kinds.contains(&EntryKind::Function));
|
||||
assert!(kinds.contains(&EntryKind::HttpRoute));
|
||||
assert!(kinds.contains(&EntryKind::CliSubcommand));
|
||||
assert!(kinds.contains(&EntryKindTag::Function));
|
||||
assert!(kinds.contains(&EntryKindTag::HttpRoute));
|
||||
assert!(kinds.contains(&EntryKindTag::CliSubcommand));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted_and_phase() {
|
||||
let hint = JavaScriptEmitter.entry_kind_hint(EntryKind::HttpRoute);
|
||||
let hint = JavaScriptEmitter.entry_kind_hint(EntryKindTag::HttpRoute);
|
||||
assert!(hint.contains("HttpRoute"));
|
||||
assert!(hint.contains("Phase 13"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use crate::utils::project::DetectedFramework;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -73,7 +73,7 @@ impl JsShape {
|
|||
/// Detect the shape from `(spec, source)`. Framework / runtime
|
||||
/// markers in the source win over `spec.entry_kind`.
|
||||
pub fn detect(spec: &HarnessSpec, source: &str) -> Self {
|
||||
let kind = spec.entry_kind;
|
||||
let kind = spec.entry_kind.tag();
|
||||
let entry = spec.entry_name.as_str();
|
||||
|
||||
// ── Framework / runtime markers ─────────────────────────────
|
||||
|
|
@ -155,7 +155,7 @@ impl JsShape {
|
|||
return Self::BrowserEvent;
|
||||
}
|
||||
|
||||
if kind == EntryKind::HttpRoute {
|
||||
if kind == EntryKindTag::HttpRoute {
|
||||
return Self::Express;
|
||||
}
|
||||
|
||||
|
|
@ -1629,11 +1629,11 @@ fn resolve_http_payload(slot: &PayloadSlot) -> (&'static str, String, &'static s
|
|||
}
|
||||
|
||||
/// Supported entry kinds for both JS + TS after Phase 13.
|
||||
pub const SUPPORTED: &[EntryKind] = &[
|
||||
EntryKind::Function,
|
||||
EntryKind::HttpRoute,
|
||||
EntryKind::CliSubcommand,
|
||||
EntryKind::LibraryApi,
|
||||
pub const SUPPORTED: &[EntryKindTag] = &[
|
||||
EntryKindTag::Function,
|
||||
EntryKindTag::HttpRoute,
|
||||
EntryKindTag::CliSubcommand,
|
||||
EntryKindTag::LibraryApi,
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ pub mod rust;
|
|||
pub mod typescript;
|
||||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -132,14 +132,17 @@ pub trait LangEmitter {
|
|||
/// Build a harness source bundle for `spec`.
|
||||
fn emit(&self, spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason>;
|
||||
|
||||
/// The set of [`EntryKind`] variants this emitter understands.
|
||||
/// The set of [`EntryKind`] variants this emitter understands,
|
||||
/// projected to the [`EntryKindTag`] discriminant so the slice can
|
||||
/// live in `'static` storage even after Phase 18 extended
|
||||
/// `EntryKind` with data-bearing variants.
|
||||
///
|
||||
/// Must be non-empty: every emitter advertises at least one shape it can
|
||||
/// (or will) drive — even stub modules whose `emit` returns
|
||||
/// `LangUnsupported`. Empty would be indistinguishable from "language
|
||||
/// not in the dispatch table" and would defeat the structured
|
||||
/// advertisement that callers consume.
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind];
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag];
|
||||
|
||||
/// Human-actionable hint produced when `attempted` is not in
|
||||
/// [`entry_kinds_supported`](LangEmitter::entry_kinds_supported).
|
||||
|
|
@ -149,7 +152,7 @@ pub trait LangEmitter {
|
|||
/// surfaces directly to operators triaging dynamic verification gaps;
|
||||
/// keep it specific (name the supported kinds, name the phase that will
|
||||
/// extend support).
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String;
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String;
|
||||
|
||||
/// Synthesise the language-specific manifest / lockfile contents that
|
||||
/// pin the [`Environment`]'s direct deps + toolchain into a file the
|
||||
|
|
@ -251,7 +254,7 @@ pub fn materialize_runtime(env: &Environment) -> RuntimeArtifacts {
|
|||
/// in (instead of producing a never-runnable harness).
|
||||
pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||
let supported = entry_kinds_supported(spec.lang);
|
||||
if !supported.is_empty() && !supported.contains(&spec.entry_kind) {
|
||||
if !supported.is_empty() && !supported.contains(&spec.entry_kind.tag()) {
|
||||
return Err(UnsupportedReason::EntryKindUnsupported);
|
||||
}
|
||||
dispatch(spec.lang, |e| e.emit(spec))
|
||||
|
|
@ -263,7 +266,7 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
/// Returns an empty slice when `lang` has no registered emitter — callers
|
||||
/// distinguish that from "emitter exists but advertises none" by treating
|
||||
/// empty as "language unsupported".
|
||||
pub fn entry_kinds_supported(lang: Lang) -> &'static [EntryKind] {
|
||||
pub fn entry_kinds_supported(lang: Lang) -> &'static [EntryKindTag] {
|
||||
dispatch(lang, |e| e.entry_kinds_supported()).unwrap_or(&[])
|
||||
}
|
||||
|
||||
|
|
@ -271,7 +274,7 @@ pub fn entry_kinds_supported(lang: Lang) -> &'static [EntryKind] {
|
|||
///
|
||||
/// Falls back to a generic message when `lang` has no registered emitter so
|
||||
/// callers do not need to special-case that path.
|
||||
pub fn entry_kind_hint(lang: Lang, attempted: EntryKind) -> String {
|
||||
pub fn entry_kind_hint(lang: Lang, attempted: EntryKindTag) -> String {
|
||||
dispatch(lang, |e| e.entry_kind_hint(attempted)).unwrap_or_else(|| {
|
||||
format!(
|
||||
"no harness emitter is registered for {lang:?}; attempted {attempted}"
|
||||
|
|
@ -300,6 +303,7 @@ fn dispatch<R>(lang: Lang, f: impl FnOnce(&dyn LangEmitter) -> R) -> Option<R> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::EntryKind;
|
||||
|
||||
/// Every registered emitter must advertise at least one entry kind so the
|
||||
/// verifier never produces an empty `supported` list in
|
||||
|
|
@ -328,10 +332,110 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn entry_kind_hint_mentions_attempted() {
|
||||
let hint = entry_kind_hint(Lang::Python, EntryKind::HttpRoute);
|
||||
let hint = entry_kind_hint(Lang::Python, EntryKindTag::HttpRoute);
|
||||
assert!(
|
||||
hint.contains("HttpRoute"),
|
||||
"hint must mention the attempted entry kind, got: {hint:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Phase 18 (Track M.0) — every Phase 18 variant resolves to a
|
||||
/// distinct [`EntryKindTag`] via [`EntryKind::tag`], and the
|
||||
/// per-language emitters short-circuit those tags with a typed
|
||||
/// `Inconclusive(EntryKindUnsupported)` hint that mentions the
|
||||
/// follow-up phase that will close the gap.
|
||||
#[test]
|
||||
fn entry_kind_tag_round_trips_for_phase_18_variants() {
|
||||
use crate::evidence::EntryKindTag as T;
|
||||
assert_eq!(EntryKind::Function.tag(), T::Function);
|
||||
assert_eq!(EntryKind::HttpRoute.tag(), T::HttpRoute);
|
||||
assert_eq!(EntryKind::CliSubcommand.tag(), T::CliSubcommand);
|
||||
assert_eq!(EntryKind::LibraryApi.tag(), T::LibraryApi);
|
||||
assert_eq!(
|
||||
EntryKind::ClassMethod {
|
||||
class: "Cls".into(),
|
||||
method: "do".into(),
|
||||
}
|
||||
.tag(),
|
||||
T::ClassMethod
|
||||
);
|
||||
assert_eq!(
|
||||
EntryKind::MessageHandler {
|
||||
queue: "q".into(),
|
||||
message_schema: None,
|
||||
}
|
||||
.tag(),
|
||||
T::MessageHandler
|
||||
);
|
||||
assert_eq!(
|
||||
EntryKind::ScheduledJob { schedule: None }.tag(),
|
||||
T::ScheduledJob
|
||||
);
|
||||
assert_eq!(
|
||||
EntryKind::GraphQLResolver {
|
||||
type_name: "User".into(),
|
||||
field: "name".into(),
|
||||
}
|
||||
.tag(),
|
||||
T::GraphQLResolver
|
||||
);
|
||||
assert_eq!(
|
||||
EntryKind::WebSocket { path: "/ws".into() }.tag(),
|
||||
T::WebSocket
|
||||
);
|
||||
assert_eq!(
|
||||
EntryKind::Middleware { name: "auth".into() }.tag(),
|
||||
T::Middleware
|
||||
);
|
||||
assert_eq!(
|
||||
EntryKind::Migration { version: None }.tag(),
|
||||
T::Migration
|
||||
);
|
||||
assert_eq!(EntryKind::Unknown.tag(), T::Unknown);
|
||||
}
|
||||
|
||||
/// Phase 18 (Track M.0) — none of the Phase 18 variants are wired
|
||||
/// into any per-language emitter yet (those land in Phase 19 /
|
||||
/// 20 / 21). Confirm every lang routes them through the
|
||||
/// supported-set gate so the verifier produces a structured
|
||||
/// `Inconclusive(EntryKindUnsupported)` rather than degrading
|
||||
/// silently.
|
||||
#[test]
|
||||
fn entry_kind_phase_18_variants_are_unsupported_everywhere() {
|
||||
use crate::evidence::EntryKindTag as T;
|
||||
let new = [
|
||||
T::ClassMethod,
|
||||
T::MessageHandler,
|
||||
T::ScheduledJob,
|
||||
T::GraphQLResolver,
|
||||
T::WebSocket,
|
||||
T::Middleware,
|
||||
T::Migration,
|
||||
];
|
||||
for lang in [
|
||||
Lang::Python,
|
||||
Lang::Rust,
|
||||
Lang::JavaScript,
|
||||
Lang::TypeScript,
|
||||
Lang::Go,
|
||||
Lang::Java,
|
||||
Lang::Php,
|
||||
Lang::Ruby,
|
||||
Lang::C,
|
||||
Lang::Cpp,
|
||||
] {
|
||||
let supported = entry_kinds_supported(lang);
|
||||
for tag in new {
|
||||
assert!(
|
||||
!supported.contains(&tag),
|
||||
"{lang:?} prematurely advertised {tag:?} — Phase 18 keeps the new variants unsupported until Phase 19 / 20 / 21 lands the per-lang adapters"
|
||||
);
|
||||
let hint = entry_kind_hint(lang, tag);
|
||||
assert!(
|
||||
hint.contains(tag.as_str()),
|
||||
"{lang:?} hint must mention {tag:?}, got: {hint:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -43,10 +43,10 @@ pub struct PhpEmitter;
|
|||
/// `HttpRoute` covers Slim / Laravel / Symfony route closures.
|
||||
/// `CliSubcommand` covers `$argv`-driven CLI scripts. `Function`
|
||||
/// covers plain functions and top-level scripts.
|
||||
const SUPPORTED: &[EntryKind] = &[
|
||||
EntryKind::Function,
|
||||
EntryKind::HttpRoute,
|
||||
EntryKind::CliSubcommand,
|
||||
const SUPPORTED: &[EntryKindTag] = &[
|
||||
EntryKindTag::Function,
|
||||
EntryKindTag::HttpRoute,
|
||||
EntryKindTag::CliSubcommand,
|
||||
];
|
||||
|
||||
impl LangEmitter for PhpEmitter {
|
||||
|
|
@ -54,13 +54,13 @@ impl LangEmitter for PhpEmitter {
|
|||
emit(spec)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"php emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 15 shape dispatch"
|
||||
"php emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 15 / 19 / 20 / 21 shape dispatch"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ impl PhpShape {
|
|||
/// the source win over `spec.entry_kind`.
|
||||
pub fn detect(spec: &HarnessSpec, source: &str) -> Self {
|
||||
let entry = spec.entry_name.as_str();
|
||||
let kind = spec.entry_kind;
|
||||
let kind = spec.entry_kind.tag();
|
||||
|
||||
let has_symfony_marker = source.contains("#[Route(")
|
||||
|| source.contains("Symfony\\Component\\Routing")
|
||||
|
|
@ -231,10 +231,10 @@ impl PhpShape {
|
|||
if has_argv && !entry_named_function {
|
||||
return Self::CliArgvScript;
|
||||
}
|
||||
if kind == EntryKind::HttpRoute {
|
||||
if kind == EntryKindTag::HttpRoute {
|
||||
return Self::RouteClosure;
|
||||
}
|
||||
if kind == EntryKind::CliSubcommand {
|
||||
if kind == EntryKindTag::CliSubcommand {
|
||||
return Self::CliArgvScript;
|
||||
}
|
||||
// TopLevelScript only fires when we actually saw the source
|
||||
|
|
@ -1215,7 +1215,7 @@ fn function_exists_call(_func: &str) -> bool {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -1294,18 +1294,18 @@ mod tests {
|
|||
assert!(!PhpEmitter.entry_kinds_supported().is_empty());
|
||||
assert!(PhpEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::Function));
|
||||
.contains(&EntryKindTag::Function));
|
||||
assert!(PhpEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::HttpRoute));
|
||||
.contains(&EntryKindTag::HttpRoute));
|
||||
assert!(PhpEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::CliSubcommand));
|
||||
.contains(&EntryKindTag::CliSubcommand));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted_and_phase() {
|
||||
let hint = PhpEmitter.entry_kind_hint(EntryKind::LibraryApi);
|
||||
let hint = PhpEmitter.entry_kind_hint(EntryKindTag::LibraryApi);
|
||||
assert!(hint.contains("LibraryApi"));
|
||||
assert!(hint.contains("Phase 15"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use crate::utils::project::DetectedFramework;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -41,10 +41,10 @@ pub struct PythonEmitter;
|
|||
/// argparse `main()` functions. `Function` covers pytest, async
|
||||
/// coroutines, Celery tasks, and generic module-level functions
|
||||
/// (positional + kwargs).
|
||||
const SUPPORTED: &[EntryKind] = &[
|
||||
EntryKind::Function,
|
||||
EntryKind::HttpRoute,
|
||||
EntryKind::CliSubcommand,
|
||||
const SUPPORTED: &[EntryKindTag] = &[
|
||||
EntryKindTag::Function,
|
||||
EntryKindTag::HttpRoute,
|
||||
EntryKindTag::CliSubcommand,
|
||||
];
|
||||
|
||||
impl LangEmitter for PythonEmitter {
|
||||
|
|
@ -52,13 +52,13 @@ impl LangEmitter for PythonEmitter {
|
|||
emit(spec)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"python emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 12 shape dispatch"
|
||||
"python emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 12 / 19 / 20 / 21 shape dispatch"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ impl PythonShape {
|
|||
/// the legacy substring-only entry-kind heuristic.
|
||||
pub fn detect(spec: &HarnessSpec, source: &str) -> Self {
|
||||
let entry = spec.entry_name.as_str();
|
||||
let kind = spec.entry_kind;
|
||||
let kind = spec.entry_kind.tag();
|
||||
|
||||
// ── Framework-first detection ────────────────────────────────
|
||||
let has_flask =
|
||||
|
|
@ -224,14 +224,14 @@ impl PythonShape {
|
|||
return Self::FlaskRoute;
|
||||
}
|
||||
|
||||
if kind == EntryKind::HttpRoute {
|
||||
if kind == EntryKindTag::HttpRoute {
|
||||
// The flow-step said HTTP but no framework import was
|
||||
// detected — fall back to Flask which has the most forgiving
|
||||
// test client wiring.
|
||||
return Self::FlaskRoute;
|
||||
}
|
||||
|
||||
if kind == EntryKind::CliSubcommand
|
||||
if kind == EntryKindTag::CliSubcommand
|
||||
|| entry == "main"
|
||||
|| entry == "__main__"
|
||||
|| source.contains("if __name__ == \"__main__\"")
|
||||
|
|
@ -1925,7 +1925,7 @@ fn module_name(entry_file: &str) -> &str {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -1992,14 +1992,14 @@ mod tests {
|
|||
#[test]
|
||||
fn entry_kinds_supported_includes_http_and_cli() {
|
||||
let kinds = PythonEmitter.entry_kinds_supported();
|
||||
assert!(kinds.contains(&EntryKind::Function));
|
||||
assert!(kinds.contains(&EntryKind::HttpRoute));
|
||||
assert!(kinds.contains(&EntryKind::CliSubcommand));
|
||||
assert!(kinds.contains(&EntryKindTag::Function));
|
||||
assert!(kinds.contains(&EntryKindTag::HttpRoute));
|
||||
assert!(kinds.contains(&EntryKindTag::CliSubcommand));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted() {
|
||||
let hint = PythonEmitter.entry_kind_hint(EntryKind::LibraryApi);
|
||||
let hint = PythonEmitter.entry_kind_hint(EntryKindTag::LibraryApi);
|
||||
assert!(hint.contains("LibraryApi"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -40,10 +40,10 @@ pub struct RubyEmitter;
|
|||
/// `HttpRoute` covers Sinatra / Rails / Rack. `CliSubcommand` covers
|
||||
/// `ARGV`-driven scripts. `Function` covers plain methods and
|
||||
/// controller method shapes.
|
||||
const SUPPORTED: &[EntryKind] = &[
|
||||
EntryKind::Function,
|
||||
EntryKind::HttpRoute,
|
||||
EntryKind::CliSubcommand,
|
||||
const SUPPORTED: &[EntryKindTag] = &[
|
||||
EntryKindTag::Function,
|
||||
EntryKindTag::HttpRoute,
|
||||
EntryKindTag::CliSubcommand,
|
||||
];
|
||||
|
||||
impl LangEmitter for RubyEmitter {
|
||||
|
|
@ -51,13 +51,13 @@ impl LangEmitter for RubyEmitter {
|
|||
emit(spec)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"ruby emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 15 shape dispatch"
|
||||
"ruby emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 15 / 19 / 20 / 21 shape dispatch"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ impl RubyShape {
|
|||
/// the source win over `spec.entry_kind`.
|
||||
pub fn detect(spec: &HarnessSpec, source: &str) -> Self {
|
||||
let entry = spec.entry_name.as_str();
|
||||
let kind = spec.entry_kind;
|
||||
let kind = spec.entry_kind.tag();
|
||||
|
||||
let has_sinatra = source.contains("require 'sinatra'")
|
||||
|| source.contains("require \"sinatra\"")
|
||||
|
|
@ -188,7 +188,7 @@ impl RubyShape {
|
|||
if has_rack {
|
||||
return Self::RackMiddleware;
|
||||
}
|
||||
if kind == EntryKind::HttpRoute && has_class {
|
||||
if kind == EntryKindTag::HttpRoute && has_class {
|
||||
return Self::ControllerMethod;
|
||||
}
|
||||
if has_class && has_def && !entry.is_empty() && !entry_named_class {
|
||||
|
|
@ -959,7 +959,7 @@ fn parse_first_class_name(source: &str) -> Option<String> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -989,18 +989,18 @@ mod tests {
|
|||
assert!(!RubyEmitter.entry_kinds_supported().is_empty());
|
||||
assert!(RubyEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::Function));
|
||||
.contains(&EntryKindTag::Function));
|
||||
assert!(RubyEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::HttpRoute));
|
||||
.contains(&EntryKindTag::HttpRoute));
|
||||
assert!(RubyEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::CliSubcommand));
|
||||
.contains(&EntryKindTag::CliSubcommand));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted_and_phase() {
|
||||
let hint = RubyEmitter.entry_kind_hint(EntryKind::LibraryApi);
|
||||
let hint = RubyEmitter.entry_kind_hint(EntryKindTag::LibraryApi);
|
||||
assert!(hint.contains("LibraryApi"));
|
||||
assert!(hint.contains("Phase 15"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
use crate::labels::Cap;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -38,11 +38,11 @@ pub struct RustEmitter;
|
|||
/// covers clap-driven CLIs. `LibraryApi` covers libfuzzer
|
||||
/// `fuzz_target!` entry points. `Function` covers plain free functions
|
||||
/// and is the fallback when shape detection is inconclusive.
|
||||
const SUPPORTED: &[EntryKind] = &[
|
||||
EntryKind::Function,
|
||||
EntryKind::HttpRoute,
|
||||
EntryKind::CliSubcommand,
|
||||
EntryKind::LibraryApi,
|
||||
const SUPPORTED: &[EntryKindTag] = &[
|
||||
EntryKindTag::Function,
|
||||
EntryKindTag::HttpRoute,
|
||||
EntryKindTag::CliSubcommand,
|
||||
EntryKindTag::LibraryApi,
|
||||
];
|
||||
|
||||
impl LangEmitter for RustEmitter {
|
||||
|
|
@ -50,13 +50,13 @@ impl LangEmitter for RustEmitter {
|
|||
emit(spec)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"rust emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 16 shape dispatch (actix / axum / clap / libfuzzer)"
|
||||
"rust emitter supports {SUPPORTED:?}; this finding's enclosing context is `EntryKind::{attempted}` — see Phase 16 / 19 / 20 / 21 shape dispatch (actix / axum / clap / libfuzzer + future class / msg / job adapters)"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -527,7 +527,7 @@ impl RustShape {
|
|||
/// bytes of the entry file (best-effort — empty string falls back
|
||||
/// to [`Self::Generic`]).
|
||||
pub fn detect(spec: &HarnessSpec, source: &str) -> Self {
|
||||
let kind = spec.entry_kind;
|
||||
let kind = spec.entry_kind.tag();
|
||||
let entry = spec.entry_name.as_str();
|
||||
|
||||
let has_warp = source.contains("use warp::")
|
||||
|
|
@ -598,9 +598,9 @@ impl RustShape {
|
|||
return Self::LibfuzzerTarget;
|
||||
}
|
||||
match kind {
|
||||
EntryKind::HttpRoute => Self::ActixWebRoute,
|
||||
EntryKind::CliSubcommand => Self::ClapCli,
|
||||
EntryKind::LibraryApi => Self::LibfuzzerTarget,
|
||||
EntryKindTag::HttpRoute => Self::ActixWebRoute,
|
||||
EntryKindTag::CliSubcommand => Self::ClapCli,
|
||||
EntryKindTag::LibraryApi => Self::LibfuzzerTarget,
|
||||
_ => Self::Generic,
|
||||
}
|
||||
}
|
||||
|
|
@ -1050,7 +1050,7 @@ fn clap_invocation(spec: &HarnessSpec, func: &str) -> (String, String) {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot};
|
||||
use crate::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -1140,12 +1140,12 @@ mod tests {
|
|||
assert!(!RustEmitter.entry_kinds_supported().is_empty());
|
||||
assert!(RustEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::Function));
|
||||
.contains(&EntryKindTag::Function));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted_and_phase() {
|
||||
let hint = RustEmitter.entry_kind_hint(EntryKind::LibraryApi);
|
||||
let hint = RustEmitter.entry_kind_hint(EntryKindTag::LibraryApi);
|
||||
assert!(hint.contains("LibraryApi"));
|
||||
assert!(hint.contains("Phase 16"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
use crate::dynamic::environment::{Environment, RuntimeArtifacts};
|
||||
use crate::dynamic::lang::{js_shared, ChainStepHarness, ChainStepTerminal, HarnessSource, LangEmitter};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec};
|
||||
use crate::dynamic::spec::{EntryKindTag, HarnessSpec};
|
||||
use crate::evidence::UnsupportedReason;
|
||||
|
||||
/// Zero-sized [`LangEmitter`] handle for TypeScript.
|
||||
|
|
@ -32,13 +32,13 @@ impl LangEmitter for TypeScriptEmitter {
|
|||
js_shared::emit(spec, true)
|
||||
}
|
||||
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKind] {
|
||||
fn entry_kinds_supported(&self) -> &'static [EntryKindTag] {
|
||||
js_shared::SUPPORTED
|
||||
}
|
||||
|
||||
fn entry_kind_hint(&self, attempted: EntryKind) -> String {
|
||||
fn entry_kind_hint(&self, attempted: EntryKindTag) -> String {
|
||||
format!(
|
||||
"typescript emitter supports {supported:?} (shared dispatch with javascript via `js_shared`); this finding's enclosing context is `EntryKind::{attempted}` — see Phase 13 shape dispatch",
|
||||
"typescript emitter supports {supported:?} (shared dispatch with javascript via `js_shared`); this finding's enclosing context is `EntryKind::{attempted}` — see Phase 13 / 19 / 20 / 21 shape dispatch",
|
||||
supported = js_shared::SUPPORTED,
|
||||
)
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ impl LangEmitter for TypeScriptEmitter {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dynamic::spec::{HarnessSpec, PayloadSlot, SpecDerivationStrategy};
|
||||
use crate::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy};
|
||||
use crate::labels::Cap;
|
||||
use crate::symbol::Lang;
|
||||
|
||||
|
|
@ -89,12 +89,12 @@ mod tests {
|
|||
assert!(!TypeScriptEmitter.entry_kinds_supported().is_empty());
|
||||
assert!(TypeScriptEmitter
|
||||
.entry_kinds_supported()
|
||||
.contains(&EntryKind::HttpRoute));
|
||||
.contains(&EntryKindTag::HttpRoute));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_kind_hint_names_attempted_and_phase() {
|
||||
let hint = TypeScriptEmitter.entry_kind_hint(EntryKind::HttpRoute);
|
||||
let hint = TypeScriptEmitter.entry_kind_hint(EntryKindTag::HttpRoute);
|
||||
assert!(hint.contains("HttpRoute"));
|
||||
assert!(hint.contains("Phase 13"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,14 @@ pub struct EntryRef {
|
|||
/// attempted / supported variants without depending on the `dynamic` feature.
|
||||
pub use crate::evidence::EntryKind;
|
||||
|
||||
/// Re-export of [`crate::evidence::EntryKindTag`].
|
||||
///
|
||||
/// The discriminant tag used by every site that needs a `Copy + Hash`
|
||||
/// handle to an `EntryKind`: supported-set lookups, the
|
||||
/// [`crate::evidence::InconclusiveReason::EntryKindUnsupported`] fields,
|
||||
/// the lang-emitter trait surface.
|
||||
pub use crate::evidence::EntryKindTag;
|
||||
|
||||
/// Where the payload goes when the harness fires.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PayloadSlot {
|
||||
|
|
@ -363,7 +371,7 @@ impl HarnessSpec {
|
|||
/// `Unsupported`.
|
||||
pub fn entry_kind_is_supported(&self) -> bool {
|
||||
let supported = crate::dynamic::lang::entry_kinds_supported(self.lang);
|
||||
supported.contains(&self.entry_kind)
|
||||
supported.contains(&self.entry_kind.tag())
|
||||
}
|
||||
|
||||
/// Returns the ordered list of derivation strategies that
|
||||
|
|
@ -1222,6 +1230,29 @@ fn attach_framework_binding(spec: &mut HarnessSpec, summaries: Option<&GlobalSum
|
|||
if spec.lang == Lang::Java && binding.adapter == "java-spring" {
|
||||
spec.java_toolchain.with_spring_test = true;
|
||||
}
|
||||
// Phase 18 (Track M.0): the binding carries the adapter's view
|
||||
// of the entry shape — when the adapter stamps one of the new
|
||||
// data-bearing variants (`ClassMethod`, `MessageHandler`,
|
||||
// `ScheduledJob`, …), propagate that onto the spec so the
|
||||
// verifier's `entry_kind_is_supported` gate sees the structural
|
||||
// shape and short-circuits to a typed
|
||||
// `Inconclusive(EntryKindUnsupported)`. We deliberately do not
|
||||
// overwrite the legacy unit variants here: every adapter
|
||||
// shipped through Phase 17 stamps `Function` / `HttpRoute` and
|
||||
// the derivation pipeline already routes those correctly.
|
||||
if matches!(
|
||||
binding.kind.tag(),
|
||||
crate::evidence::EntryKindTag::ClassMethod
|
||||
| crate::evidence::EntryKindTag::MessageHandler
|
||||
| crate::evidence::EntryKindTag::ScheduledJob
|
||||
| crate::evidence::EntryKindTag::GraphQLResolver
|
||||
| crate::evidence::EntryKindTag::WebSocket
|
||||
| crate::evidence::EntryKindTag::Middleware
|
||||
| crate::evidence::EntryKindTag::Migration
|
||||
) {
|
||||
spec.entry_kind = binding.kind.clone();
|
||||
spec.spec_hash = compute_spec_hash(spec);
|
||||
}
|
||||
spec.framework = Some(binding);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ fn entry_kind_unsupported_verdict(
|
|||
diag: Option<&Diag>,
|
||||
spec_entry_path: &str,
|
||||
lang: crate::symbol::Lang,
|
||||
attempted: crate::dynamic::spec::EntryKind,
|
||||
attempted: crate::dynamic::spec::EntryKindTag,
|
||||
policy: &SamplingPolicy,
|
||||
) -> VerifyResult {
|
||||
let supported = crate::dynamic::lang::entry_kinds_supported(lang).to_vec();
|
||||
|
|
@ -618,7 +618,7 @@ pub fn verify_finding(diag: &Diag, opts: &VerifyOptions) -> VerifyResult {
|
|||
Some(diag),
|
||||
&spec.entry_file,
|
||||
spec.lang,
|
||||
spec.entry_kind,
|
||||
spec.entry_kind.tag(),
|
||||
&opts.telemetry_policy,
|
||||
);
|
||||
}
|
||||
|
|
@ -1210,13 +1210,13 @@ fn build_verdict(
|
|||
) = &e
|
||||
{
|
||||
let supported = crate::dynamic::lang::entry_kinds_supported(spec.lang);
|
||||
if !supported.contains(&spec.entry_kind) {
|
||||
if !supported.contains(&spec.entry_kind.tag()) {
|
||||
return entry_kind_unsupported_verdict(
|
||||
finding_id.to_owned(),
|
||||
None,
|
||||
&spec.entry_file,
|
||||
spec.lang,
|
||||
spec.entry_kind,
|
||||
spec.entry_kind.tag(),
|
||||
&opts.telemetry_policy,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue