Repository navigation
Expand file tree
/
Copy pathsyntax_worker_runner.rs
More file actions
133 lines (130 loc) · 5.74 KB
/
Copy pathsyntax_worker_runner.rs
File metadata and controls
133 lines (130 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! 候选语法工作进程的父进程边界;失败只成为未完成,绝不成为源码违规。
use crate::syntax_worker_candidate_observation::SyntaxWorkerCandidateObservation;
use crate::syntax_worker_envelope::SyntaxWorkerEnvelope;
use codeguard_adapters::bundled_grammar_candidate;
use codeguard_core::{SyntaxFileObservation, SyntaxFileState, assess_syntax_precheck};
#[cfg(not(target_os = "linux"))]
use codeguard_runtime::run_process;
#[cfg(target_os = "linux")]
use codeguard_runtime::run_process_with_address_space_limit;
use codeguard_runtime::{ProcessSpec, Termination};
use sha2::{Digest, Sha256};
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
use std::time::Instant;
/// 启动同一二进制的私有解析进程并核验来源、字节身份、位置和状态。
/// 输入为绝对二进制路径、固定语种、相对源码路径、本轮源码字节、共同截止时间及取消标记。
/// 返回只具候选观察权威的状态;Linux 内存上限与正式检查覆盖仍待实测验收。
pub fn run_syntax_worker_candidate(
executable: &Path,
language: &str,
relative_path: &str,
source: &[u8],
deadline: Instant,
cancelled: &AtomicBool,
) -> Result<SyntaxWorkerCandidateObservation, String> {
if !executable.is_absolute() || source.len() > 1024 * 1024 {
return Err("syntax_worker_input_invalid".into());
}
let source_text =
std::str::from_utf8(source).map_err(|_| "syntax_worker_source_encoding_invalid")?;
let (asset, _) = bundled_grammar_candidate(language)
.map_err(|reason| format!("syntax_worker_candidate_unavailable:{reason}"))?;
let expected_sha = format!("{:x}", Sha256::digest(source));
let spec = ProcessSpec {
executable: executable.to_path_buf(),
args: vec![OsString::from("__syntax-worker"), OsString::from(language)],
cwd: PathBuf::from("/"),
env: BTreeMap::new(),
stdin: Some(source.to_vec()),
deadline,
output_limit_bytes: 64 * 1024,
};
// Linux 为整个私有进程设置地址空间硬上限;其它 Unix 平台尚无实测硬限制。
#[cfg(target_os = "linux")]
let process = run_process_with_address_space_limit(&spec, cancelled, 2 * 1024 * 1024 * 1024);
#[cfg(not(target_os = "linux"))]
let process = run_process(&spec, cancelled);
if process.termination != Termination::Exited(0) {
return Err(format!(
"syntax_worker_termination:{:?}",
process.termination
));
}
let value = codeguard_adapters::parse_unique_json(&process.stdout).map_err(str::to_owned)?;
if value["schema_version"] == "1.0.0" && value.get("structural_observations").is_some() {
return Err("syntax_worker_version_fields_mismatch".into());
}
let report: SyntaxWorkerEnvelope =
serde_json::from_value(value).map_err(|_| "syntax_worker_report_invalid")?;
if !matches!(report.schema_version.as_str(), "1.0.0" | "1.1.0" | "1.2.0")
|| (report.schema_version == "1.0.0" && !report.structural_observations.is_empty())
|| (report.schema_version == "1.1.0"
&& (language != "python" || report.structural_observations.is_empty()))
|| (report.schema_version == "1.2.0"
&& (language != "go" || report.structural_observations.len() != 1))
|| report.report_type != "syntax_worker_candidate"
|| report.language != language
|| report.grammar_sha256 != asset.sha256
|| report.grammar_abi_version != asset.abi_version
|| report.source_sha256 != expected_sha
|| report.recoveries.len() > 128
|| report.recoveries.len() + report.structural_observations.len() > 128
|| report
.structural_observations
.iter()
.any(|row| !row.valid(language, source))
{
return Err("syntax_worker_identity_mismatch".into());
}
for recovery in &report.recoveries {
if !matches!(recovery.kind.as_str(), "ERROR" | "MISSING")
|| recovery.group_id == 0
|| recovery.syntax_kind.is_empty()
|| recovery.syntax_kind.len() > 128
|| recovery.syntax_kind.chars().any(char::is_control)
|| recovery.start_byte > recovery.end_byte
|| recovery.end_byte > source.len()
|| !source_text.is_char_boundary(recovery.start_byte)
|| !source_text.is_char_boundary(recovery.end_byte)
|| position(source, recovery.start_byte)
!= Some((recovery.start_row, recovery.start_column_byte))
|| position(source, recovery.end_byte)
!= Some((recovery.end_row, recovery.end_column_byte))
{
return Err("syntax_worker_recovery_invalid".into());
}
}
let precheck = assess_syntax_precheck(
&[SyntaxFileObservation {
path: relative_path.into(),
state: SyntaxFileState::Checked {
recoveries: report.recoveries.len(),
grammar_qualified: false,
truncated: report.truncated,
},
}],
true,
false,
)
.map_err(str::to_owned)?;
Ok(SyntaxWorkerCandidateObservation {
source_sha256: expected_sha,
grammar_sha256: asset.sha256.clone(),
grammar_qualified: false,
recoveries: report.recoveries,
structural_observations: report.structural_observations,
precheck,
})
}
fn position(source: &[u8], offset: usize) -> Option<(usize, usize)> {
let preceding = source.get(..offset)?;
let row = preceding.iter().filter(|byte| **byte == b'\n').count();
let column = preceding
.rsplit(|byte| *byte == b'\n')
.next()
.map_or(0, <[u8]>::len);
Some((row, column))
}