Skip to main content

anodized_core/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro2::Span;
4use syn::{Error, Expr, ExprClosure, Meta, Pat};
5
6use crate::qualifiers::FnQualifiers;
7
8pub mod annotate;
9pub mod instrument;
10pub mod qualifiers;
11
12#[cfg(test)]
13mod test_util;
14
15/// Specifies the intended behavior of a function or method: `fn`.
16#[derive(Debug)]
17// TODO: Rename to `FnSpec` to reduce ambiguity.
18pub struct Spec {
19    /// Qualifiers that constrain the behavior of the computation.
20    pub qualifiers: FnQualifiers,
21    /// Preconditions: conditions that must hold when the function is called.
22    pub requires: Vec<PreCondition>,
23    /// Invariants: conditions that must hold both when the function is called and when it returns.
24    pub maintains: Vec<PreCondition>,
25    /// Captures: expressions to snapshot at function entry for use in postconditions.
26    pub captures: Vec<Capture>,
27    /// Postconditions: conditions that must hold when the function returns.
28    pub ensures: Vec<PostCondition>,
29    /// The span in the source code, from which this spec was parsed.
30    span: Span,
31}
32
33impl Spec {
34    /// Empty spec that contains no elements.
35    pub fn empty() -> Self {
36        Self {
37            qualifiers: FnQualifiers::empty(),
38            requires: vec![],
39            maintains: vec![],
40            captures: vec![],
41            ensures: vec![],
42            span: Span::call_site(),
43        }
44    }
45
46    /// Returns `true` if the spec is empty (specifies nothing), otherwise returns `false`.
47    pub fn is_empty(&self) -> bool {
48        self.qualifiers.is_empty()
49            && self.requires.is_empty()
50            && self.maintains.is_empty()
51            && self.ensures.is_empty()
52            && self.captures.is_empty()
53    }
54
55    /// Construct an error from the whole spec.
56    pub fn spec_err(&self, message: &str) -> Error {
57        Error::new::<&str>(self.span, message)
58    }
59}
60
61/// Specifies the intended behavior of a data type: `struct` or `enum`.
62#[derive(Debug)]
63pub struct DataSpec {
64    /// Invariants: conditions that must hold for all instances of the data type.
65    pub maintains: Vec<PreCondition>,
66    /// The span in the source code, from which this spec was parsed.
67    span: Span,
68}
69
70impl DataSpec {
71    /// Empty spec that contains no elements.
72    pub fn empty() -> Self {
73        Self {
74            maintains: vec![],
75            span: Span::call_site(),
76        }
77    }
78
79    /// Returns `true` if the spec is empty (specifies nothing), otherwise returns `false`.
80    pub fn is_empty(&self) -> bool {
81        self.maintains.is_empty()
82    }
83
84    /// Construct an error from the whole spec.
85    pub fn spec_err(&self, message: &str) -> Error {
86        Error::new::<&str>(self.span, message)
87    }
88}
89
90/// Specifies the intended behavior of a loop: `while` or `for`.
91#[derive(Debug)]
92pub struct LoopSpec {
93    /// Loop invariants: conditions that must hold both before and after the loop's body runs.
94    pub maintains: Vec<PreCondition>,
95    /// Loop variant: an expression that decreases with each run of the loop's body.
96    pub decreases: Option<LoopVariant>,
97    /// The span in the source code, from which this spec was parsed.
98    span: Span,
99}
100
101impl LoopSpec {
102    /// Empty spec that contains no elements.
103    pub fn empty() -> Self {
104        Self {
105            maintains: vec![],
106            decreases: None,
107            span: Span::call_site(),
108        }
109    }
110
111    /// Returns `true` if the spec is empty (specifies nothing), otherwise returns `false`.
112    pub fn is_empty(&self) -> bool {
113        self.maintains.is_empty() && self.decreases.is_none()
114    }
115
116    /// Construct an error from the whole spec.
117    pub fn spec_err(&self, message: &str) -> Error {
118        Error::new::<&str>(self.span, message)
119    }
120}
121
122/// A precondition represented by a `bool`-valued expression.
123#[derive(Debug)]
124// TODO: Rename to `Condition` for clarity.
125pub struct PreCondition {
126    /// The closure that validates the precondition,
127    /// takes no input, e.g. `|| input.is_valid()`.
128    pub closure: ExprClosure,
129    /// **Static analyzers can safely ignore this field.**
130    ///
131    /// Build configuration filter to decide whether to add runtime checks.
132    /// Passed to a `cfg!()` guard in the instrumented function.
133    pub cfg: Option<Meta>,
134}
135
136/// A postcondition represented by a closure that takes the return value as a reference.
137#[derive(Debug)]
138pub struct PostCondition {
139    /// The closure that validates the postcondition, taking the function's
140    /// return value by reference, e.g. `|output| *output > 0`.
141    pub closure: ExprClosure,
142    /// **Static analyzers can safely ignore this field.**
143    ///
144    /// Build configuration filter to decide whether to add runtime checks.
145    /// Passed to a `cfg!()` guard in the instrumented function.
146    pub cfg: Option<Meta>,
147}
148
149/// Captures an expression's value at function entry.
150#[derive(Debug)]
151pub struct Capture {
152    /// The expression to capture.
153    pub expr: Expr,
154    /// The pattern to bind/destructure the captured value.
155    pub pat: Pat,
156}
157
158/// Decreases with each run of a loop's body.
159#[derive(Debug)]
160pub struct LoopVariant {
161    /// The expression that defines the variant.
162    pub expr: Expr,
163    /// **Static analyzers can safely ignore this field.**
164    ///
165    /// Build configuration filter to decide whether to add runtime checks.
166    /// Passed to a `cfg!()` guard in the instrumented code.
167    pub cfg: Option<Meta>,
168}