1use rustc_hir::def_id::DefId;
11use rustc_middle::ty::TyCtxt;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::OnceLock;
15use syn::Expr;
16
17use crate::helpers::name::public_def_path;
18
19use super::types::{Property, PropertyKind};
20
21#[derive(Debug, Serialize, Deserialize, Clone)]
51pub(crate) struct JsonProperty {
52 #[serde(default)]
53 pub tag: String,
54 #[serde(default)]
55 pub args: Vec<String>,
56 #[serde(default)]
57 pub kind: Option<String>,
58 #[serde(default)]
61 pub any: Option<Vec<AnyItem>>,
62}
63
64#[derive(Debug, Serialize, Deserialize, Clone)]
69#[serde(untagged)]
70pub(crate) enum AnyItem {
71 Single(JsonProperty),
72 And(Vec<JsonProperty>),
73}
74
75pub(crate) fn get_std_contracts_from_json(
90 tcx: TyCtxt<'_>,
91 def_id: DefId,
92) -> Option<&'static [JsonProperty]> {
93 let lookup_def_id = resolve_trait_method(tcx, def_id);
94 let cleaned_path_name = public_def_path(tcx, lookup_def_id);
95 let db = load_std_contracts_json();
96
97 if let Some(entries) = db.get(&cleaned_path_name) {
99 return Some(entries.as_slice());
100 }
101
102 {
105 let stripped: Vec<&str> = cleaned_path_name
106 .split("::")
107 .filter(|s| !s.starts_with('[') && !s.starts_with('<'))
108 .collect();
109 if stripped.len() != cleaned_path_name.matches("::").count() + 1 {
110 let stripped_path = stripped.join("::");
111 if let Some(entries) = db.get(&stripped_path) {
112 return Some(entries.as_slice());
113 }
114 }
115 }
116
117 let mut segments: Vec<&str> = cleaned_path_name.split("::").collect();
119 for i in (1..segments.len()).rev() {
120 segments.truncate(i + 1);
121 segments[i] = "*";
122 let pattern = segments.join("::");
123 if let Some(entries) = db.get(&pattern) {
124 return Some(entries.as_slice());
125 }
126 }
127
128 if let Some(entries) = db.get("*") {
130 return Some(entries.as_slice());
131 }
132
133 None
134}
135
136pub(crate) fn std_contracts_has_entry(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
140 get_std_contracts_from_json(tcx, def_id).is_some()
141}
142
143fn resolve_trait_method(tcx: TyCtxt<'_>, def_id: DefId) -> DefId {
146 if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
147 if let Some(trait_def_id) = assoc_item.trait_item_def_id() {
148 return trait_def_id;
149 }
150 }
151 def_id
152}
153
154fn load_std_contracts_json() -> &'static HashMap<String, Vec<JsonProperty>> {
156 static STD_CONTRACTS: OnceLock<HashMap<String, Vec<JsonProperty>>> = OnceLock::new();
157 STD_CONTRACTS.get_or_init(|| {
158 serde_json::from_str(include_str!("assets/std-api-requires.json"))
159 .expect("failed to parse verify std contracts backup")
160 })
161}
162
163#[derive(Debug, Serialize, Deserialize, Clone)]
165pub(crate) struct TypeInvariantEntry {
166 pub invariants: Vec<JsonProperty>,
167}
168
169pub(crate) fn get_std_type_invariants() -> &'static HashMap<String, TypeInvariantEntry> {
172 static TYPE_INVARIANTS: OnceLock<HashMap<String, TypeInvariantEntry>> = OnceLock::new();
173 TYPE_INVARIANTS.get_or_init(|| {
174 serde_json::from_str(include_str!("assets/std-type-invariants.json"))
175 .expect("failed to parse std type invariants")
176 })
177}
178
179fn load_trait_ensures_json() -> &'static HashMap<String, Vec<JsonProperty>> {
181 static TRAIT_ENSURES: OnceLock<HashMap<String, Vec<JsonProperty>>> = OnceLock::new();
182 TRAIT_ENSURES.get_or_init(|| {
183 serde_json::from_str(include_str!("assets/std-trait-ensures.json"))
184 .expect("failed to parse std trait ensures")
185 })
186}
187
188pub(crate) fn query_trait_ensures(tcx: TyCtxt<'_>, trait_def_id: DefId) -> Vec<JsonProperty> {
197 let db = load_trait_ensures_json();
198 let key = tcx.def_path_str(trait_def_id);
199 if let Some(entries) = db.get(&key) {
200 return entries.clone();
201 }
202 let short = key.rsplit("::").next().unwrap_or(&key).to_string();
203 for (k, entries) in db.iter() {
204 if k.rsplit("::").next() == Some(short.as_str()) {
205 return entries.clone();
206 }
207 }
208 Vec::new()
209}
210
211pub(crate) fn entry_to_property<'tcx>(
220 tcx: TyCtxt<'tcx>,
221 def_id: DefId,
222 entry: &JsonProperty,
223 param_names: &[String],
224 has_names: bool,
225) -> Vec<Property<'tcx>> {
226 if let Some(disjuncts) = &entry.any {
227 if disjuncts.len() >= 2 {
228 let mut prop = any_entry_to_property(tcx, def_id, disjuncts, param_names, has_names);
229 prop.apply_kind(entry.kind.as_deref());
230 return vec![prop];
231 }
232 rap_error!(
233 "JSON any entry requires at least 2 disjuncts, got {}",
234 disjuncts.len()
235 );
236 return Vec::new();
237 }
238
239 let exprs = resolve_json_args(&entry.args, param_names, has_names, &entry.tag);
240 if exprs.len() != entry.args.len() {
241 rap_error!(
242 "Parse JSON API args error: Failed to parse arg '{:?}' for tag {}",
243 entry.args,
244 entry.tag
245 );
246 return Vec::new();
247 }
248
249 let properties = Property::parse_list(tcx, def_id, entry.tag.as_str(), &exprs);
250 let mut result = Vec::new();
251 for mut property in properties {
252 property.apply_kind(entry.kind.as_deref());
253 if matches!(property.kind(), Some(PropertyKind::Unknown)) {
254 rap_debug!(
255 "skip unsupported std safety contract tag '{}' for callee {:?}",
256 entry.tag,
257 def_id
258 );
259 continue;
260 }
261 result.push(property);
262 }
263 result
264}
265
266fn any_entry_to_property<'tcx>(
272 tcx: TyCtxt<'tcx>,
273 def_id: DefId,
274 disjuncts: &[AnyItem],
275 param_names: &[String],
276 has_names: bool,
277) -> Property<'tcx> {
278 let mut or_disjuncts: Vec<Property<'tcx>> = Vec::new();
279 for item in disjuncts {
280 match item {
281 AnyItem::Single(entry) => {
282 let group = resolve_entry_group(tcx, def_id, entry, param_names, has_names, false);
283 if !group.is_empty() {
284 or_disjuncts.push(Property::conjunction(group));
285 }
286 }
287 AnyItem::And(entries) => {
288 let mut group: Vec<Property<'tcx>> = Vec::new();
289 for entry in entries {
290 group.extend(resolve_entry_group(
291 tcx,
292 def_id,
293 entry,
294 param_names,
295 has_names,
296 true,
297 ));
298 }
299 if !group.is_empty() {
300 or_disjuncts.push(Property::conjunction(group));
301 }
302 }
303 }
304 }
305 Property::new_or(or_disjuncts)
306}
307
308fn resolve_entry_group<'tcx>(
310 tcx: TyCtxt<'tcx>,
311 def_id: DefId,
312 entry: &JsonProperty,
313 param_names: &[String],
314 has_names: bool,
315 in_group: bool,
316) -> Vec<Property<'tcx>> {
317 if entry.any.is_some() {
318 if in_group {
319 rap_error!("Nested 'any' inside 'any' group is not supported");
320 } else {
321 rap_error!("Nested 'any' inside 'any' is not supported in JSON contracts");
322 }
323 return Vec::new();
324 }
325 let exprs = resolve_json_args(&entry.args, param_names, has_names, &entry.tag);
326 if exprs.len() != entry.args.len() {
327 if in_group {
328 rap_error!(
329 "Parse any group entry arg error: failed to parse '{:?}' for tag {}",
330 entry.args,
331 entry.tag
332 );
333 } else {
334 rap_error!(
335 "Parse any entry arg error: Failed to parse arg '{:?}' for tag {}",
336 entry.args,
337 entry.tag
338 );
339 }
340 return Vec::new();
341 }
342 let props = Property::parse_list(tcx, def_id, entry.tag.as_str(), &exprs);
343 let mut group = Vec::new();
344 for mut prop in props {
345 prop.apply_kind(entry.kind.as_deref());
346 group.push(prop);
347 }
348 group
349}
350
351pub(crate) fn resolve_json_args(
358 args: &[String],
359 param_names: &[String],
360 has_names: bool,
361 tag: &str,
362) -> Vec<Expr> {
363 let mut exprs: Vec<Expr> = Vec::new();
364 for arg_str in args {
365 let resolved = if has_names {
366 resolve_json_param_name(arg_str, param_names)
367 } else {
368 arg_str.clone()
369 };
370 let normalized_arg = normalize_json_contract_arg(&resolved);
371 match syn::parse_str::<Expr>(&normalized_arg) {
372 Ok(expr) => exprs.push(expr),
373 Err(_) => {
374 if let Some(lifetime) = normalized_arg.strip_prefix('\'') {
375 if lifetime.chars().all(|c| c.is_alphabetic() || c == '_') {
376 match syn::parse_str::<Expr>(lifetime) {
377 Ok(expr) => exprs.push(expr),
378 Err(_) => {
379 rap_error!(
380 "JSON Contract Error: Failed to parse lifetime \
381 '{}' as Rust Expr for tag {}",
382 arg_str,
383 tag
384 );
385 }
386 }
387 } else {
388 rap_error!(
389 "JSON Contract Error: Failed to parse arg '{}' as Rust Expr for tag {}",
390 arg_str,
391 tag
392 );
393 }
394 } else {
395 rap_error!(
396 "JSON Contract Error: Failed to parse arg '{}' as Rust Expr for tag {}",
397 arg_str,
398 tag
399 );
400 }
401 }
402 }
403 }
404 exprs
405}
406
407pub(crate) fn resolve_json_param_name(arg: &str, param_names: &[String]) -> String {
412 if arg.starts_with("arg:")
413 || arg.starts_with("const:")
414 || arg.starts_with("ty:")
415 || arg.contains('(')
416 || arg.contains('.')
417 || arg.contains("::")
418 || arg.contains(' ')
419 || arg.starts_with('\'')
420 {
421 return arg.to_string();
422 }
423 if let Some(pos) = param_names.iter().position(|n| n == arg) {
424 format!("arg:{pos}")
425 } else {
426 arg.to_string()
427 }
428}
429
430pub(crate) fn normalize_json_contract_arg(arg: &str) -> String {
441 let bytes = arg.as_bytes();
442 let mut out = String::with_capacity(arg.len());
443 let mut i = 0;
444
445 while i < bytes.len() {
446 if arg[i..].starts_with("arg:") {
447 let start = i + "arg:".len();
448 let end = scan_while(arg, start, |ch| ch.is_ascii_digit());
449 if end > start {
450 out.push_str("Arg_");
451 out.push_str(&arg[start..end]);
452 i = end;
453 continue;
454 }
455 }
456
457 if arg[i..].starts_with("const:") {
458 let start = i + "const:".len();
459 let end = scan_while(arg, start, is_contract_token_char);
460 if end > start {
461 out.push_str(&arg[start..end]);
462 i = end;
463 continue;
464 }
465 }
466
467 if arg[i..].starts_with("ty:") {
468 let start = i + "ty:".len();
469 let end = scan_while(arg, start, is_contract_token_char);
470 if end > start {
471 out.push_str(&arg[start..end]);
472 i = end;
473 continue;
474 }
475 }
476
477 let ch = arg[i..].chars().next().unwrap();
478 out.push(ch);
479 i += ch.len_utf8();
480 }
481
482 out
483}
484
485fn scan_while(arg: &str, mut index: usize, predicate: impl Fn(char) -> bool) -> usize {
486 while index < arg.len() {
487 let ch = arg[index..].chars().next().unwrap();
488 if !predicate(ch) {
489 break;
490 }
491 index += ch.len_utf8();
492 }
493 index
494}
495
496fn is_contract_token_char(ch: char) -> bool {
497 ch.is_ascii_alphanumeric() || ch == '_' || ch == ':'
498}
499
500pub(crate) fn query_json_contracts<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Vec<Property<'tcx>> {
505 let Some(entries) = get_std_contracts_from_json(tcx, def_id) else {
506 return Vec::new();
507 };
508 let (param_names, _) = crate::helpers::name::parse_signature(tcx, def_id);
509 let has_names = !param_names.is_empty() && !param_names[0].chars().all(|c| c.is_ascii_digit());
510
511 let mut results = Vec::new();
512 for entry in entries {
513 results.extend(entry_to_property(
514 tcx,
515 def_id,
516 entry,
517 ¶m_names,
518 has_names,
519 ));
520 }
521 results
522}