Skip to content

Commit e30c369

Browse files
authored
Check that functions return (overridable with assume_always_returns pragma) (#108)
1 parent 4e6b301 commit e30c369

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
@@ -177,22 +177,56 @@ pub fn simplify_program(mut program: Program) -> SimpleProgram {
177177
v.clone()
178178
})
179179
.collect::<Vec<_>>();
180-
new_functions.insert(
181-
name.clone(),
182-
SimpleFunction {
183-
name: name.clone(),
184-
arguments,
185-
n_returned_vars: func.n_returned_vars,
186-
instructions: simplified_instructions,
187-
},
188-
);
180+
let simplified_function = SimpleFunction {
181+
name: name.clone(),
182+
arguments,
183+
n_returned_vars: func.n_returned_vars,
184+
instructions: simplified_instructions,
185+
};
186+
if !func.assume_always_returns {
187+
check_function_always_returns(&simplified_function);
188+
}
189+
new_functions.insert(name.clone(), simplified_function);
189190
const_malloc.map.clear();
190191
}
191192
SimpleProgram {
192193
functions: new_functions,
193194
}
194195
}
195196

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

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
@@ -44,6 +44,7 @@ fn test_wrong_n_returned_vars_1() {
4444
let program = r#"
4545
fn main() {
4646
a, b = f();
47+
return;
4748
}
4849
4950
fn f() -> 1 {
@@ -59,6 +60,7 @@ fn test_wrong_n_returned_vars_2() {
5960
let program = r#"
6061
fn main() {
6162
a = f();
63+
return;
6264
}
6365
6466
fn f() -> 1 {
@@ -68,6 +70,45 @@ fn test_wrong_n_returned_vars_2() {
6870
compile_and_run(program.to_string(), (&[], &[]), DEFAULT_NO_VEC_RUNTIME_MEMORY, false);
6971
}
7072

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

0 commit comments

Comments
 (0)