Skip to content

Commit b148ead

Browse files
morganthomasTomWambsgans
authored andcommitted
Check that functions return (overridable with assume_always_returns pragma) (#108)
1 parent 09deb81 commit b148ead

File tree

5 files changed

+97
-11
lines changed

5 files changed

+97
-11
lines changed

crates/lean_compiler/src/a_simplify_lang.rs

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,56 @@ pub fn simplify_program(mut program: Program) -> SimpleProgram {
175175
v.clone()
176176
})
177177
.collect::<Vec<_>>();
178-
new_functions.insert(
179-
name.clone(),
180-
SimpleFunction {
181-
name: name.clone(),
182-
arguments,
183-
n_returned_vars: func.n_returned_vars,
184-
instructions: simplified_instructions,
185-
},
186-
);
178+
let simplified_function = SimpleFunction {
179+
name: name.clone(),
180+
arguments,
181+
n_returned_vars: func.n_returned_vars,
182+
instructions: simplified_instructions,
183+
};
184+
if !func.assume_always_returns {
185+
check_function_always_returns(&simplified_function);
186+
}
187+
new_functions.insert(name.clone(), simplified_function);
187188
const_malloc.map.clear();
188189
}
189190
SimpleProgram {
190191
functions: new_functions,
191192
}
192193
}
193194

195+
/// Analyzes a simplified function to verify that it returns on each code path.
196+
fn check_function_always_returns(func: &SimpleFunction) {
197+
check_block_always_returns(&func.name, &func.instructions);
198+
}
199+
200+
fn check_block_always_returns(function_name: &String, instructions: &[SimpleLine]) {
201+
match instructions.last() {
202+
Some(SimpleLine::Match { value: _, arms }) => {
203+
for arm in arms {
204+
check_block_always_returns(function_name, arm);
205+
}
206+
}
207+
Some(SimpleLine::IfNotZero {
208+
condition: _,
209+
then_branch,
210+
else_branch,
211+
line_number: _,
212+
}) => {
213+
check_block_always_returns(function_name, then_branch);
214+
check_block_always_returns(function_name, else_branch);
215+
}
216+
Some(SimpleLine::FunctionRet { return_data: _ }) => {
217+
// good
218+
}
219+
Some(SimpleLine::Panic) => {
220+
// good
221+
}
222+
_ => {
223+
panic!("Cannot prove that function always returns: {function_name}");
224+
}
225+
}
226+
}
227+
194228
/// Analyzes the program to verify that each variable is defined in each context where it is used.
195229
fn check_program_scoping(program: &Program) {
196230
for (_, function) in program.functions.iter() {
@@ -1844,6 +1878,7 @@ fn handle_const_arguments_helper(
18441878
inlined: false,
18451879
body: new_body,
18461880
n_returned_vars: func.n_returned_vars,
1881+
assume_always_returns: func.assume_always_returns,
18471882
},
18481883
);
18491884
}

crates/lean_compiler/src/grammar.pest

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ program = { SOI ~ constant_declaration* ~ function+ ~ EOI }
77
constant_declaration = { "const" ~ identifier ~ "=" ~ expression ~ ";" }
88

99
// Functions
10-
function = { "fn" ~ identifier ~ "(" ~ parameter_list? ~ ")" ~ inlined_statement? ~ return_count? ~ "{" ~ statement* ~ "}" }
10+
function = { pragma? ~ "fn" ~ identifier ~ "(" ~ parameter_list? ~ ")" ~ inlined_statement? ~ return_count? ~ "{" ~ statement* ~ "}" }
11+
pragma = { "#![assume_always_returns]" }
1112
parameter_list = { parameter ~ ("," ~ parameter)* }
1213
parameter = { (const_keyword)? ~ identifier }
1314
const_keyword = { "const" }

crates/lean_compiler/src/lang.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub struct Function {
1919
pub inlined: bool,
2020
pub n_returned_vars: usize,
2121
pub body: Vec<Line>,
22+
pub assume_always_returns: bool,
2223
}
2324

2425
impl Function {

crates/lean_compiler/src/parser/parsers/function.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ pub struct FunctionParser;
1717

1818
impl Parse<Function> for FunctionParser {
1919
fn parse(pair: ParsePair<'_>, ctx: &mut ParseContext) -> ParseResult<Function> {
20-
let mut inner = pair.into_inner();
20+
let mut inner = pair.into_inner().peekable();
21+
let assume_always_returns = match inner.peek().map(|x| x.as_rule()) {
22+
Some(Rule::pragma) => {
23+
inner.next();
24+
true
25+
}
26+
_ => false,
27+
};
2128
let name = next_inner_pair(&mut inner, "function name")?.as_str().to_string();
2229

2330
let mut arguments = Vec::new();
@@ -53,6 +60,7 @@ impl Parse<Function> for FunctionParser {
5360
inlined,
5461
n_returned_vars,
5562
body,
63+
assume_always_returns,
5664
})
5765
}
5866
}

crates/lean_compiler/tests/test_compiler.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ fn test_wrong_n_returned_vars_1() {
4242
let program = r#"
4343
fn main() {
4444
a, b = f();
45+
return;
4546
}
4647
4748
fn f() -> 1 {
@@ -57,6 +58,7 @@ fn test_wrong_n_returned_vars_2() {
5758
let program = r#"
5859
fn main() {
5960
a = f();
61+
return;
6062
}
6163
6264
fn f() -> 1 {
@@ -66,6 +68,45 @@ fn test_wrong_n_returned_vars_2() {
6668
compile_and_run(program.to_string(), (&[], &[]), false);
6769
}
6870

71+
#[test]
72+
#[should_panic]
73+
fn test_no_return() {
74+
let program = r#"
75+
fn main() {
76+
a = f();
77+
return;
78+
}
79+
80+
fn f() -> 1 {
81+
}
82+
83+
fn g() -> 1 {
84+
return 0;
85+
}
86+
"#;
87+
compile_and_run(program.to_string(), (&[], &[]), DEFAULT_NO_VEC_RUNTIME_MEMORY, false);
88+
}
89+
90+
#[test]
91+
fn test_assumed_return() {
92+
let program = r#"
93+
fn main() {
94+
a = f();
95+
return;
96+
}
97+
98+
#![assume_always_returns]
99+
fn f() -> 1 {
100+
if 1 == 1 {
101+
return 0;
102+
} else {
103+
print(1);
104+
}
105+
}
106+
"#;
107+
compile_and_run(program.to_string(), (&[], &[]), DEFAULT_NO_VEC_RUNTIME_MEMORY, false);
108+
}
109+
69110
#[test]
70111
fn test_fibonacci_program() {
71112
// a program to check the value of the 30th Fibonacci number (832040)

0 commit comments

Comments
 (0)