Skip to content

Commit 26c872f

Browse files
avrabeclaude
andcommitted
feat: Verus StaticString proofs, code quality improvements, decoder fixes, coverage --html
- Add 20 verified Verus proofs for StaticString (total: 50 across 3 structures) - Reduce panic! macros from 87 to 24 (72% reduction) via expect() and try_new() - Reduce .unwrap() calls from 486 to 435 via proper error propagation with ? - Fix 5 kiln-decoder test failures (3 real bugs in lazy_detection/unified_loader) - Implement cargo-kiln coverage --html with full llvm-cov pipeline - Add NaN canonicalization for SIMD f32x4/f64x2 operations - Enable multi-memory proposal support (WebAssembly 3.0) - Add custom section UTF-8 name validation per spec Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ae76d71 commit 26c872f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1315
-376
lines changed

cargo-kiln/src/main.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,9 +2188,12 @@ async fn cmd_coverage(
21882188
println!("{} Running coverage analysis...", "📊".bright_blue());
21892189
}
21902190

2191+
// Determine if HTML output is needed (explicit flag or format=html)
2192+
let generate_html = html || format == "html";
2193+
21912194
if best_effort {
21922195
// In best-effort mode, try to run coverage but continue on failures
2193-
match build_system.run_coverage() {
2196+
match build_system.run_coverage(generate_html) {
21942197
Ok(_) => println!(
21952198
"{} Coverage analysis completed successfully",
21962199
"✅".bright_green()
@@ -2205,15 +2208,43 @@ async fn cmd_coverage(
22052208
}
22062209
} else {
22072210
// Normal mode - fail on errors
2208-
build_system.run_coverage().context("Coverage analysis failed")?;
2211+
build_system
2212+
.run_coverage(generate_html)
2213+
.context("Coverage analysis failed")?;
22092214
}
22102215

22112216
if open {
2212-
println!(
2213-
"{} Opening coverage report in browser...",
2214-
"🌐".bright_blue()
2215-
);
2216-
// TODO: Implement browser opening
2217+
let html_index = std::path::Path::new("target/coverage/html/index.html");
2218+
if html_index.exists() {
2219+
println!(
2220+
"{} Opening coverage report in browser...",
2221+
"🌐".bright_blue()
2222+
);
2223+
#[cfg(target_os = "macos")]
2224+
{
2225+
let _ = std::process::Command::new("open")
2226+
.arg(html_index)
2227+
.spawn();
2228+
}
2229+
#[cfg(target_os = "linux")]
2230+
{
2231+
let _ = std::process::Command::new("xdg-open")
2232+
.arg(html_index)
2233+
.spawn();
2234+
}
2235+
#[cfg(target_os = "windows")]
2236+
{
2237+
let _ = std::process::Command::new("cmd")
2238+
.args(["/C", "start"])
2239+
.arg(html_index)
2240+
.spawn();
2241+
}
2242+
} else {
2243+
println!(
2244+
"{} No HTML report found. Use --html to generate one.",
2245+
"⚠️".bright_yellow()
2246+
);
2247+
}
22172248
}
22182249

22192250
Ok(())

kiln-build-core/src/build.rs

Lines changed: 272 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,31 @@ pub mod xtask_port {
8484
use super::*;
8585

8686
/// Run comprehensive coverage analysis (ported from xtask coverage)
87-
pub fn run_coverage_analysis(config: &BuildConfig) -> BuildResult<()> {
87+
pub fn run_coverage_analysis(config: &BuildConfig, html: bool) -> BuildResult<()> {
8888
println!(
8989
"{} Running comprehensive coverage analysis...",
9090
"📊".bright_blue()
9191
);
9292

93-
// Build with coverage flags
93+
let coverage_dir = PathBuf::from("target/coverage");
94+
// Ensure coverage directory exists
95+
std::fs::create_dir_all(&coverage_dir).map_err(|e| {
96+
BuildError::Build(format!("Failed to create coverage directory: {}", e))
97+
})?;
98+
99+
// Clean old profraw files
100+
if let Ok(entries) = std::fs::read_dir(&coverage_dir) {
101+
for entry in entries.flatten() {
102+
let path = entry.path();
103+
if path.extension().and_then(|e| e.to_str()) == Some("profraw") {
104+
let _ = std::fs::remove_file(&path);
105+
}
106+
}
107+
}
108+
109+
// Build with coverage flags and capture test binary paths
94110
let mut cmd = Command::new("cargo");
95-
cmd.args(["test", "--no-run", "--workspace"])
111+
cmd.args(["test", "--no-run", "--workspace", "--message-format=json"])
96112
.env("RUSTFLAGS", "-C instrument-coverage")
97113
.env("LLVM_PROFILE_FILE", "target/coverage/profile-%p-%m.profraw");
98114

@@ -102,6 +118,31 @@ pub mod xtask_port {
102118
return Err(BuildError::Build("Coverage build failed".to_string()));
103119
}
104120

121+
// Extract test binary paths from cargo's JSON output
122+
let stdout = String::from_utf8_lossy(&output.stdout);
123+
let test_binaries: Vec<String> = stdout
124+
.lines()
125+
.filter_map(|line| {
126+
// Parse JSON lines looking for compiler artifacts with executables
127+
if let Ok(json) = serde_json::from_str::<serde_json::Value>(line) {
128+
if json.get("reason").and_then(|r| r.as_str()) == Some("compiler-artifact") {
129+
if let Some(executable) = json.get("executable").and_then(|e| e.as_str()) {
130+
return Some(executable.to_string());
131+
}
132+
}
133+
}
134+
None
135+
})
136+
.collect();
137+
138+
if config.verbose {
139+
println!(
140+
"{} Found {} test binaries",
141+
"ℹ️".bright_blue(),
142+
test_binaries.len()
143+
);
144+
}
145+
105146
// Run tests with coverage
106147
let mut test_cmd = Command::new("cargo");
107148
test_cmd
@@ -116,10 +157,233 @@ pub mod xtask_port {
116157
return Err(BuildError::Test("Coverage tests failed".to_string()));
117158
}
118159

160+
// Find llvm tools
161+
let llvm_profdata = find_llvm_tool("llvm-profdata")?;
162+
let llvm_cov = find_llvm_tool("llvm-cov")?;
163+
164+
if config.verbose {
165+
println!(
166+
"{} Using llvm-profdata: {}",
167+
"ℹ️".bright_blue(),
168+
llvm_profdata.display()
169+
);
170+
println!(
171+
"{} Using llvm-cov: {}",
172+
"ℹ️".bright_blue(),
173+
llvm_cov.display()
174+
);
175+
}
176+
177+
// Collect profraw files
178+
let profraw_files: Vec<PathBuf> = std::fs::read_dir(&coverage_dir)
179+
.map_err(|e| {
180+
BuildError::Build(format!("Failed to read coverage directory: {}", e))
181+
})?
182+
.filter_map(|entry| {
183+
let entry = entry.ok()?;
184+
let path = entry.path();
185+
if path.extension().and_then(|e| e.to_str()) == Some("profraw") {
186+
Some(path)
187+
} else {
188+
None
189+
}
190+
})
191+
.collect();
192+
193+
if profraw_files.is_empty() {
194+
return Err(BuildError::Build(
195+
"No .profraw files found. Tests may not have generated coverage data.".to_string(),
196+
));
197+
}
198+
199+
println!(
200+
"{} Merging {} profile data files...",
201+
"📊".bright_blue(),
202+
profraw_files.len()
203+
);
204+
205+
// Merge profraw files into a single profdata file
206+
let profdata_path = coverage_dir.join("coverage.profdata");
207+
let mut merge_cmd = Command::new(&llvm_profdata);
208+
merge_cmd.arg("merge").arg("-sparse");
209+
for profraw in &profraw_files {
210+
merge_cmd.arg(profraw);
211+
}
212+
merge_cmd.arg("-o").arg(&profdata_path);
213+
214+
let merge_output =
215+
super::execute_command(&mut merge_cmd, config, "Merging profile data")?;
216+
217+
if !merge_output.status.success() {
218+
let stderr = String::from_utf8_lossy(&merge_output.stderr);
219+
return Err(BuildError::Build(format!(
220+
"Failed to merge profile data: {}",
221+
stderr
222+
)));
223+
}
224+
225+
// Generate lcov report
226+
let lcov_path = coverage_dir.join("lcov.info");
227+
println!(
228+
"{} Generating lcov report...",
229+
"📊".bright_blue()
230+
);
231+
232+
let mut lcov_cmd = Command::new(&llvm_cov);
233+
lcov_cmd
234+
.arg("export")
235+
.arg("--format=lcov")
236+
.arg(format!("--instr-profile={}", profdata_path.display()))
237+
.arg("--ignore-filename-regex=\\.cargo|rustc|target");
238+
// Add test binaries as objects
239+
if let Some(first) = test_binaries.first() {
240+
lcov_cmd.arg(first);
241+
for binary in test_binaries.iter().skip(1) {
242+
lcov_cmd.arg(format!("--object={}", binary));
243+
}
244+
}
245+
246+
let lcov_output =
247+
super::execute_command(&mut lcov_cmd, config, "Generating lcov report")?;
248+
249+
if !lcov_output.status.success() {
250+
let stderr = String::from_utf8_lossy(&lcov_output.stderr);
251+
return Err(BuildError::Build(format!(
252+
"Failed to generate lcov report: {}",
253+
stderr
254+
)));
255+
}
256+
257+
// Write lcov data to file
258+
std::fs::write(&lcov_path, &lcov_output.stdout).map_err(|e| {
259+
BuildError::Build(format!("Failed to write lcov report: {}", e))
260+
})?;
261+
262+
println!(
263+
"{} lcov report written to {}",
264+
"✅".bright_green(),
265+
lcov_path.display()
266+
);
267+
268+
// Generate HTML report if requested
269+
if html {
270+
let html_dir = coverage_dir.join("html");
271+
std::fs::create_dir_all(&html_dir).map_err(|e| {
272+
BuildError::Build(format!("Failed to create HTML output directory: {}", e))
273+
})?;
274+
275+
println!(
276+
"{} Generating HTML coverage report...",
277+
"📊".bright_blue()
278+
);
279+
280+
let mut html_cmd = Command::new(&llvm_cov);
281+
html_cmd
282+
.arg("show")
283+
.arg("--format=html")
284+
.arg(format!("--instr-profile={}", profdata_path.display()))
285+
.arg(format!("--output-dir={}", html_dir.display()))
286+
.arg("--ignore-filename-regex=\\.cargo|rustc|target")
287+
.arg("--show-line-counts-or-regions")
288+
.arg("--show-instantiations");
289+
// Add test binaries as objects
290+
if let Some(first) = test_binaries.first() {
291+
html_cmd.arg(first);
292+
for binary in test_binaries.iter().skip(1) {
293+
html_cmd.arg(format!("--object={}", binary));
294+
}
295+
}
296+
297+
let html_output =
298+
super::execute_command(&mut html_cmd, config, "Generating HTML report")?;
299+
300+
if !html_output.status.success() {
301+
let stderr = String::from_utf8_lossy(&html_output.stderr);
302+
return Err(BuildError::Build(format!(
303+
"Failed to generate HTML coverage report: {}",
304+
stderr
305+
)));
306+
}
307+
308+
println!(
309+
"{} HTML coverage report written to {}",
310+
"✅".bright_green(),
311+
html_dir.display()
312+
);
313+
}
314+
119315
println!("{} Coverage analysis completed", "✅".bright_green());
120316
Ok(())
121317
}
122318

319+
/// Find an LLVM tool binary from the Rust toolchain
320+
fn find_llvm_tool(tool_name: &str) -> BuildResult<PathBuf> {
321+
// Get the sysroot
322+
let sysroot_output = Command::new("rustc")
323+
.arg("--print")
324+
.arg("sysroot")
325+
.output()
326+
.map_err(|e| BuildError::Tool(format!("Failed to run rustc --print sysroot: {}", e)))?;
327+
328+
if !sysroot_output.status.success() {
329+
return Err(BuildError::Tool(
330+
"Failed to determine Rust sysroot".to_string(),
331+
));
332+
}
333+
334+
let sysroot = String::from_utf8_lossy(&sysroot_output.stdout)
335+
.trim()
336+
.to_string();
337+
338+
// Get the host triple
339+
let version_output = Command::new("rustc")
340+
.arg("-vV")
341+
.output()
342+
.map_err(|e| BuildError::Tool(format!("Failed to run rustc -vV: {}", e)))?;
343+
344+
let version_str = String::from_utf8_lossy(&version_output.stdout);
345+
let host_triple = version_str
346+
.lines()
347+
.find_map(|line| {
348+
if line.starts_with("host:") {
349+
Some(line.trim_start_matches("host:").trim().to_string())
350+
} else {
351+
None
352+
}
353+
})
354+
.ok_or_else(|| {
355+
BuildError::Tool("Failed to determine host triple from rustc -vV".to_string())
356+
})?;
357+
358+
let tool_path = PathBuf::from(&sysroot)
359+
.join("lib")
360+
.join("rustlib")
361+
.join(&host_triple)
362+
.join("bin")
363+
.join(tool_name);
364+
365+
if tool_path.exists() {
366+
return Ok(tool_path);
367+
}
368+
369+
// Fallback: check if the tool is on PATH (e.g., installed via package manager)
370+
let which_output = Command::new("which")
371+
.arg(tool_name)
372+
.output();
373+
374+
if let Ok(output) = which_output {
375+
if output.status.success() {
376+
let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
377+
return Ok(PathBuf::from(path));
378+
}
379+
}
380+
381+
Err(BuildError::Tool(format!(
382+
"Could not find '{}'. Install it with: rustup component add llvm-tools-preview",
383+
tool_name
384+
)))
385+
}
386+
123387
/// Generate documentation (ported from xtask docs)
124388
pub fn generate_docs() -> BuildResult<()> {
125389
generate_docs_with_options(false, false)
@@ -908,8 +1172,11 @@ impl BuildSystem {
9081172
}
9091173

9101174
/// Run comprehensive coverage analysis using ported xtask logic
911-
pub fn run_coverage(&self) -> BuildResult<()> {
912-
xtask_port::run_coverage_analysis(&self.config)
1175+
///
1176+
/// When `html` is true, generates an HTML coverage report in
1177+
/// `target/coverage/html/` in addition to the lcov report.
1178+
pub fn run_coverage(&self, html: bool) -> BuildResult<()> {
1179+
xtask_port::run_coverage_analysis(&self.config, html)
9131180
}
9141181

9151182
/// Generate documentation using ported xtask logic

0 commit comments

Comments
 (0)