|
| 1 | +//! 有界编译数据库配置观察,不执行项目命令。 |
| 2 | +
|
| 3 | +use crate::discovery::DiscoveryReport; |
| 4 | +use codeguard_adapters::{CheckerConfiguration, parse_c_family_compilation_database}; |
| 5 | +use codeguard_core::ObservationPort; |
| 6 | +use sha2::{Digest, Sha256}; |
| 7 | +use std::path::Path; |
| 8 | + |
| 9 | +/// 保存配置摘要与未完成原因;参数为同次发现报告、观察端口和数据库路径。 |
| 10 | +pub(crate) fn observe<P: ObservationPort>( |
| 11 | + report: &mut DiscoveryReport, |
| 12 | + observation: &P, |
| 13 | + path: &Path, |
| 14 | + relative: &str, |
| 15 | +) { |
| 16 | + let mut reason = "compilation_database_unreadable"; |
| 17 | + if let Ok(bytes) = observation.read_bounded(path, 1024 * 1024) { |
| 18 | + if observation |
| 19 | + .read_bounded(path, 1024 * 1024) |
| 20 | + .is_ok_and(|current| current == bytes) |
| 21 | + { |
| 22 | + report |
| 23 | + .checker_config_sha256 |
| 24 | + .insert(relative.into(), format!("{:x}", Sha256::digest(&bytes))); |
| 25 | + reason = match parse_c_family_compilation_database(&bytes) { |
| 26 | + Ok(_) => "compilation_database_arguments_observed_execution_context_unverified", |
| 27 | + Err(reason) => reason, |
| 28 | + }; |
| 29 | + } else { |
| 30 | + reason = "compilation_database_changed"; |
| 31 | + } |
| 32 | + } |
| 33 | + if reason != "compilation_database_arguments_observed_execution_context_unverified" { |
| 34 | + report.observation_complete = false; |
| 35 | + report.blocked_paths.push(relative.into()); |
| 36 | + } |
| 37 | + report |
| 38 | + .unknown_conditions |
| 39 | + .push(format!("{reason}:{relative}")); |
| 40 | + let build_root = relative.rsplit_once('/').map_or(".", |(parent, _)| parent); |
| 41 | + report.checker_configurations.push(CheckerConfiguration { |
| 42 | + build_root: build_root.into(), |
| 43 | + checker_id: "c_family.compilation_database".into(), |
| 44 | + category: "lint".into(), |
| 45 | + configuration: "unknown".into(), |
| 46 | + configuration_ref: relative.into(), |
| 47 | + reason: reason.into(), |
| 48 | + next_action: "核验原编译数据库的工具、目录、参数、响应文件和输入身份;不要执行command字符串,不猜标准,不以配置可读证明项目检查通过".into(), |
| 49 | + }); |
| 50 | +} |
| 51 | + |
| 52 | +#[cfg(test)] |
| 53 | +mod tests { |
| 54 | + use codeguard_core::{ObservationPort, ObservedPathKind}; |
| 55 | + use std::io; |
| 56 | + use std::path::{Path, PathBuf}; |
| 57 | + use std::cell::Cell; |
| 58 | + |
| 59 | + struct DatabaseObservation { |
| 60 | + bytes: &'static [u8], |
| 61 | + changed: bool, |
| 62 | + reads: Cell<u32>, |
| 63 | + } |
| 64 | + impl ObservationPort for DatabaseObservation { |
| 65 | + fn classify(&self, _: &Path) -> io::Result<ObservedPathKind> { |
| 66 | + Ok(ObservedPathKind::File) |
| 67 | + } |
| 68 | + fn children(&self, _: &Path) -> io::Result<Vec<PathBuf>> { |
| 69 | + Ok(vec![]) |
| 70 | + } |
| 71 | + fn read_bounded(&self, _: &Path, limit: u64) -> io::Result<Vec<u8>> { |
| 72 | + assert_eq!(limit, 1024 * 1024); |
| 73 | + self.reads.set(self.reads.get() + 1); |
| 74 | + if self.changed && self.reads.get() > 1 { |
| 75 | + return Ok(b"[]".to_vec()); |
| 76 | + } |
| 77 | + Ok(self.bytes.to_vec()) |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + #[test] |
| 82 | + fn discovered_database_keeps_unverified_config_and_bound_digest() { |
| 83 | + let mut report = crate::discovery::empty_report(Path::new("project")); |
| 84 | + let observation = DatabaseObservation { bytes: br#"[{"directory":"/build","file":"a.cpp","arguments":["clang++","-Iinclude","a.cpp"]}]"#, changed:false, reads:Cell::new(0) }; |
| 85 | + crate::discovery::observe_file( |
| 86 | + &mut report, |
| 87 | + &codeguard_adapters::legacy_registry().unwrap(), |
| 88 | + &observation, |
| 89 | + Path::new("project/build/compile_commands.json"), |
| 90 | + "build/compile_commands.json", |
| 91 | + &std::collections::BTreeSet::new(), |
| 92 | + ); |
| 93 | + assert!( |
| 94 | + report |
| 95 | + .checker_config_sha256 |
| 96 | + .contains_key("build/compile_commands.json") |
| 97 | + ); |
| 98 | + assert_eq!(report.checker_configurations[0].configuration, "unknown"); |
| 99 | + assert_eq!(report.checker_configurations[0].build_root, "build"); |
| 100 | + assert!(report.declared_versions.is_empty()); |
| 101 | + assert!(report.build_roots.is_empty()); |
| 102 | + } |
| 103 | + |
| 104 | + #[test] |
| 105 | + fn unstable_or_command_only_database_never_becomes_verified_config() { |
| 106 | + for (bytes, changed) in [ |
| 107 | + ( |
| 108 | + br#"[{"directory":"/build","file":"a.cpp","command":"clang++ a.cpp"}]"#.as_slice(), |
| 109 | + false, |
| 110 | + ), |
| 111 | + ( |
| 112 | + br#"[{"directory":"/build","file":"a.cpp","arguments":["clang++","a.cpp"]}]"# |
| 113 | + .as_slice(), |
| 114 | + true, |
| 115 | + ), |
| 116 | + ] { |
| 117 | + let mut report = crate::discovery::empty_report(Path::new("project")); |
| 118 | + let observation = DatabaseObservation { |
| 119 | + bytes, |
| 120 | + changed, |
| 121 | + reads: Cell::new(0), |
| 122 | + }; |
| 123 | + super::observe( |
| 124 | + &mut report, |
| 125 | + &observation, |
| 126 | + Path::new("project/compile_commands.json"), |
| 127 | + "compile_commands.json", |
| 128 | + ); |
| 129 | + assert!(!report.observation_complete); |
| 130 | + assert_eq!(report.blocked_paths, ["compile_commands.json"]); |
| 131 | + assert_eq!(report.checker_configurations[0].configuration, "unknown"); |
| 132 | + if changed { |
| 133 | + assert!(report.checker_config_sha256.is_empty()); |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | +} |
0 commit comments