Skip to content

Commit c610dca

Browse files
author
Herman Venter
authored
Add fixed point iteration to visit_body (facebookexperimental#27)
* Add fixed point iteration to visit_body * Rename JoinedExpression to ConditionalExpression. Fix cut & paste error in AbstractValue::is_top. Replace "match value_map1.get" with "if !value_map1.contains_key" * Tweak comments in light of Sam's review. Fix cut & paste error in AbstractDomains::is_top. Rename ConditionalExpression::if_true to consequent. Rename ConditionalExpression::if_false to alternate. Try to get a stack trace for the crash that only happens in CI. Pin nightly to last known good version. * Tweak comments in light of Sam's review. Fix cut & paste error in AbstractDomains::is_top. Rename ConditionalExpression::if_true to consequent. Rename ConditionalExpression::if_false to alternate. Try to get a stack trace for the crash that only happens in CI. Pin nightly to last known semi good version (and disable Clippy).
1 parent c688881 commit c610dca

11 files changed

+552
-85
lines changed

.travis.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ addons:
88
- libssl-dev
99
language: rust
1010
rust:
11-
- nightly
11+
- nightly-2019-01-07
1212
cache: cargo
1313

1414
before_script:
@@ -29,7 +29,7 @@ script:
2929
# Build
3030
- cargo build
3131
# Run unit and integration tests
32-
- cargo test
32+
- RUST_BACKTRACE=1 cargo test
3333
# Run mirai on itself
3434
- cargo uninstall mirai || true
3535
- cargo install --debug --path .

documentation/DeveloperGuide.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ The instructions assume that you've cloned the mirai repository into your home d
55
## Building
66

77
You'll need to have Rust installed on your system. See
8-
[here](https://doc.rust-lang.org/book/2018-edition/ch01-01-installation.html) for instructions.
8+
[here](https://doc.rust-lang.org/book/ch01-01-installation.html) for instructions.
99

1010
Mirai depends on unstable private APIs of the rust compiler to do things like parsing, type checking and
1111
lowering to MIR. These can only be accessed by using the nightly build of the compiler. So use rustup to set

src/abstract_domains.rs

+178-8
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,102 @@ use constant_value::ConstantValue;
1010
/// A collection of abstract domains associated with a particular abstract value.
1111
/// The expression domain captures the precise expression that resulted in the abstract
1212
/// value. It can be used to derive any other kind of abstract domain on demand.
13-
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
13+
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
1414
pub struct AbstractDomains {
1515
pub expression_domain: ExpressionDomain,
1616
//todo: use cached getters to get other domains on demand
1717
}
1818

19-
/// The things all abstract domain instances have to be able to do
19+
/// A collection of abstract domains that all represent the impossible abstract value.
20+
/// I.e. the corresponding set of possible concrete values is empty.
21+
pub const BOTTOM: AbstractDomains = AbstractDomains {
22+
expression_domain: ExpressionDomain::Bottom,
23+
};
24+
25+
/// A collection of abstract domains that all represent the universal abstract value.
26+
/// I.e. the corresponding set of possible concrete values includes every possible concrete value.
27+
pub const TOP: AbstractDomains = AbstractDomains {
28+
expression_domain: ExpressionDomain::Top,
29+
};
30+
31+
impl AbstractDomains {
32+
/// True if all of the abstract domains in this collection correspond to an empty set of concrete values.
33+
pub fn is_bottom(&self) -> bool {
34+
self.expression_domain.is_bottom()
35+
}
36+
37+
/// True if all of the abstract domains in this collection correspond to the set of all possible concrete values.
38+
pub fn is_top(&self) -> bool {
39+
self.expression_domain.is_top()
40+
}
41+
42+
/// Joins all of the abstract domains in the two collections to form a single collection.
43+
/// In a context where the join condition is known to be true, the result can be refined to be
44+
/// just self, correspondingly if it is known to be false, the result can be refined to be just other.
45+
pub fn join(
46+
&self,
47+
other: &AbstractDomains,
48+
join_condition: &AbstractDomains,
49+
) -> AbstractDomains {
50+
AbstractDomains {
51+
expression_domain: self
52+
.expression_domain
53+
.join(&other.expression_domain, &join_condition),
54+
}
55+
}
56+
57+
/// Widen all of the abstract domains in the two collections to form a single collection.
58+
/// The join condition is supplied to inform the widen operation, but the result is not
59+
/// required to be in a form that can be refined using the join condition.
60+
pub fn widen(
61+
&self,
62+
other: &AbstractDomains,
63+
join_condition: &AbstractDomains,
64+
) -> AbstractDomains {
65+
AbstractDomains {
66+
expression_domain: self
67+
.expression_domain
68+
.widen(&other.expression_domain, &join_condition),
69+
}
70+
}
71+
72+
/// True if for each abstract domains in the self collection, all of its concrete values are
73+
/// elements of the corresponding set of the same domain in other.
74+
pub fn subset(&self, other: &AbstractDomains) -> bool {
75+
self.expression_domain.subset(&other.expression_domain)
76+
}
77+
}
78+
79+
/// An abstract domain defines a set of concrete values in some way.
2080
pub trait AbstractDomain {
21-
//todo: join, is_top, is_bottom, etc.
81+
/// True if the set of concrete values that correspond to this domain is empty.
82+
fn is_bottom(&self) -> bool;
83+
84+
/// True if all possible concrete values are elements of the set corresponding to this domain.
85+
fn is_top(&self) -> bool;
86+
87+
/// Returns a domain whose corresponding set of concrete values include all of the values
88+
/// corresponding to self and other.
89+
/// In a context where the join condition is known to be true, the result can be refined to be
90+
/// just self, correspondingly if it is known to be false, the result can be refined to be just other.
91+
fn join(&self, other: &Self, join_condition: &AbstractDomains) -> Self;
92+
93+
/// True if all of the concrete values that correspond to self also correspond to other.
94+
fn subset(&self, other: &Self) -> bool;
95+
96+
/// Returns a domain whose corresponding set of concrete values include all of the values
97+
/// corresponding to self and other.The set of values may be less precise (more inclusive) than
98+
/// the set returned by join. The chief requirement is that a small number of widen calls
99+
/// deterministically lead to Top.
100+
fn widen(&self, other: &Self, join_condition: &AbstractDomains) -> Self;
22101
}
23102

24-
/// An abstract domain uses a functional (side effect free) expression that precisely tracks
103+
/// An abstract domain that uses a functional (side effect free) expression that precisely tracks
25104
/// a single value.
26-
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
105+
/// Note: that value might not be known at compile time, so some operations on the domain
106+
/// may conservatively expand the set of of values that correspond to the domain to more than
107+
/// just one element.
108+
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
27109
pub enum ExpressionDomain {
28110
/// An expression that represents any possible value
29111
Top,
@@ -32,9 +114,97 @@ pub enum ExpressionDomain {
32114
/// that always panics.
33115
Bottom,
34116

35-
/// An expression that is a compile time constant value, such as a numeric literal or a
36-
/// function.
117+
/// An expression that is a compile time constant value, such as a numeric literal or a function.
37118
CompileTimeConstant(ConstantValue),
119+
120+
/// An expression that is either if_true or if_false, depending on the value of condition.
121+
ConditionalExpression {
122+
// A condition that results in a Boolean value
123+
condition: Box<AbstractDomains>,
124+
// The value of this expression if join_condition is true.
125+
consequent: Box<ExpressionDomain>,
126+
// The value of this expression if join_condition is false.
127+
alternate: Box<ExpressionDomain>,
128+
},
38129
}
39130

40-
// todo: implement the AbstractDomain trait for ExpressionDomain
131+
impl AbstractDomain for ExpressionDomain {
132+
/// True if the set of concrete values that correspond to this domain is empty.
133+
fn is_bottom(&self) -> bool {
134+
match self {
135+
ExpressionDomain::Bottom => true,
136+
_ => false,
137+
}
138+
}
139+
140+
/// True if all possible concrete values are elements of the set corresponding to this domain.
141+
fn is_top(&self) -> bool {
142+
match self {
143+
ExpressionDomain::Top => true,
144+
_ => false,
145+
}
146+
}
147+
148+
/// Returns a domain whose corresponding set of concrete values include all of the values
149+
/// corresponding to self and other.
150+
/// In a context where the join condition is known to be true, the result can be refined to be
151+
/// just self, correspondingly if it is known to be false, the result can be refined to be just other.
152+
fn join(&self, other: &ExpressionDomain, join_condition: &AbstractDomains) -> ExpressionDomain {
153+
ExpressionDomain::ConditionalExpression {
154+
condition: box join_condition.clone(),
155+
consequent: box self.clone(),
156+
alternate: box other.clone(),
157+
}
158+
}
159+
160+
/// True if all of the concrete values that correspond to self also correspond to other.
161+
/// Note: !x.subset(y) does not imply y.subset(x).
162+
fn subset(&self, other: &ExpressionDomain) -> bool {
163+
match (self, other) {
164+
// The empty set is a subset of every other set.
165+
(ExpressionDomain::Bottom, _) => true,
166+
// A non empty set is not a subset of the empty set.
167+
(_, ExpressionDomain::Bottom) => false,
168+
// Every set is a subset of the universal set.
169+
(_, ExpressionDomain::Top) => true,
170+
// The universal set is not a subset of any set other than the universal set.
171+
(ExpressionDomain::Top, _) => false,
172+
(
173+
ExpressionDomain::ConditionalExpression {
174+
consequent,
175+
alternate,
176+
..
177+
},
178+
_,
179+
) => {
180+
// This is a conservative answer. False does not imply other.subset(self).
181+
consequent.subset(other) && alternate.subset(other)
182+
}
183+
(
184+
_,
185+
ExpressionDomain::ConditionalExpression {
186+
consequent,
187+
alternate,
188+
..
189+
},
190+
) => {
191+
// This is a conservative answer. False does not imply other.subset(self).
192+
self.subset(consequent) || self.subset(alternate)
193+
}
194+
// {x} subset {y} iff x = y
195+
(
196+
ExpressionDomain::CompileTimeConstant(cv1),
197+
ExpressionDomain::CompileTimeConstant(cv2),
198+
) => cv1 == cv2,
199+
}
200+
}
201+
202+
/// Returns a domain whose corresponding set of concrete values include all of the values
203+
/// corresponding to self and other.The set of values may be less precise (more inclusive) than
204+
/// the set returned by join. The chief requirement is that a small number of widen calls
205+
/// deterministically lead to Top.
206+
fn widen(&self, _other: &Self, _join_condition: &AbstractDomains) -> ExpressionDomain {
207+
//todo: don't get to top quite this quickly.
208+
ExpressionDomain::Top
209+
}
210+
}

src/abstract_value.rs

+60-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This source code is licensed under the MIT license found in the
44
// LICENSE file in the root directory of this source tree.
55
//
6-
use abstract_domains::AbstractDomains;
6+
use abstract_domains::{self, AbstractDomains};
77
use syntax_pos::Span;
88

99
/// Mirai is an abstract interpreter and thus produces abstract values.
@@ -14,7 +14,7 @@ use syntax_pos::Span;
1414
/// When we do know everything about a value, it is concrete rather than
1515
/// abstract, but is convenient to just use this structure for concrete values
1616
/// as well, since all operations can be uniform.
17-
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
17+
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
1818
pub struct AbstractValue {
1919
/// An abstract value is the result of some expression.
2020
/// The source location of that expression is stored in provenance.
@@ -24,15 +24,67 @@ pub struct AbstractValue {
2424
/// expression (i.e. one with a provenance of its own) then a copy expression is created with
2525
/// the existing expression as argument, so that both locations are tracked.
2626
#[serde(skip)]
27-
pub provenance: Span,
27+
pub provenance: Span, //todo: perhaps this should be a list of spans
2828
/// Various approximations of the actual value.
2929
/// See https://github.com/facebookexperimental/MIRAI/blob/master/documentation/AbstractValues.md.
3030
pub value: AbstractDomains,
3131
}
3232

33+
/// An abstract value that can be used as the value for an operation that has no normal result.
34+
pub const BOTTOM: AbstractValue = AbstractValue {
35+
provenance: syntax_pos::DUMMY_SP,
36+
value: abstract_domains::BOTTOM,
37+
};
38+
39+
/// An abstract value to use when nothing is known about the value. All possible concrete values
40+
/// are members of the concrete set of values corresponding to this abstract value.
41+
pub const TOP: AbstractValue = AbstractValue {
42+
provenance: syntax_pos::DUMMY_SP,
43+
value: abstract_domains::TOP,
44+
};
45+
46+
impl AbstractValue {
47+
/// True if the set of concrete values that correspond to this abstract value is empty.
48+
pub fn is_bottom(&self) -> bool {
49+
self.value.is_bottom()
50+
}
51+
52+
/// True if all possible concrete values are elements of the set corresponding to this abstract value.
53+
pub fn is_top(&self) -> bool {
54+
self.value.is_top()
55+
}
56+
57+
/// Returns an abstract value whose corresponding set of concrete values include all of the values
58+
/// corresponding to self and other.
59+
/// In a context where the join condition is known to be true, the result can be refined to be
60+
/// just self, correspondingly if it is known to be false, the result can be refined to be just other.
61+
pub fn join(&self, other: &AbstractValue, join_condition: &AbstractValue) -> AbstractValue {
62+
AbstractValue {
63+
provenance: syntax_pos::DUMMY_SP,
64+
value: self.value.join(&other.value, &join_condition.value),
65+
}
66+
}
67+
68+
/// True if all of the concrete values that correspond to self also correspond to other.
69+
pub fn subset(&self, other: &AbstractValue) -> bool {
70+
self.value.subset(&other.value)
71+
}
72+
73+
/// Returns an abstract value whose corresponding set of concrete values include all of the values
74+
/// corresponding to self and other. The set of values may be less precise (more inclusive) than
75+
/// the set returned by join. The chief requirement is that a small number of widen calls
76+
/// deterministically lead to Top.
77+
pub fn widen(&self, other: &AbstractValue, join_condition: &AbstractValue) -> AbstractValue {
78+
AbstractValue {
79+
provenance: syntax_pos::DUMMY_SP,
80+
value: self.value.widen(&other.value, &join_condition.value),
81+
}
82+
}
83+
}
84+
3385
/// The name of a function or method, sufficiently qualified so that it uniquely identifies it
3486
/// among all functions and methods defined in the project corresponding to a summary store.
35-
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
87+
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
3688
pub enum Name {
3789
/// A root name in the current name space. Typically the name of a crate, used module, or
3890
/// used function or struct/trait/enum/type.
@@ -49,20 +101,20 @@ pub enum Name {
49101
/// A path represents a left hand side expression.
50102
/// When the actual expression is evaluated at runtime it will resolve to a particular memory
51103
/// location. During analysis it is used to keep track of state changes.
52-
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash)]
104+
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
53105
pub enum Path {
54106
/// The ordinal specifies a local variable or compiler temporary of the current function
55107
/// For example, in fn foo() { x: i32 = 0; x} we identify variable x with:
56108
/// Path::LocalVariable { ordinal: 0 }
57109
LocalVariable {
58-
ordinal: u32,
110+
ordinal: usize,
59111
},
60112

61113
/// The index specifies the position of the parameter in the parameter list of the current function
62114
/// For example, in fn foo(x: int, y: int) {} we identify parameter x with:
63115
/// Path::Parameter { index: 0 }
64116
Parameter {
65-
index: u32,
117+
index: usize,
66118
},
67119

68120
// The name uniquely identifies a static field within the current crate.
@@ -97,4 +149,5 @@ pub enum Path {
97149
qualifier: Box<Path>,
98150
index: Box<AbstractValue>,
99151
},
152+
//todo: need a path for a conditional CFG link
100153
}

0 commit comments

Comments
 (0)