Detect infinite loops
This commit is contained in:
parent
2271d67f8f
commit
ad66fbdfa2
@ -16,7 +16,7 @@ use crate::diag::{
|
||||
};
|
||||
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
|
||||
use crate::syntax::ast::AstNode;
|
||||
use crate::syntax::{ast, Source, SourceId, Span, Spanned, Unit};
|
||||
use crate::syntax::{ast, Source, SourceId, Span, Spanned, SyntaxKind, SyntaxNode, Unit};
|
||||
use crate::util::{format_eco, EcoString, PathExt};
|
||||
use crate::World;
|
||||
|
||||
@ -994,12 +994,25 @@ impl Eval for ast::WhileLoop {
|
||||
type Output = Value;
|
||||
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
const MAX_ITERS: usize = 10_000;
|
||||
|
||||
let flow = vm.flow.take();
|
||||
let mut output = Value::None;
|
||||
let mut i = 0;
|
||||
|
||||
let condition = self.condition();
|
||||
let body = self.body();
|
||||
|
||||
while condition.eval(vm)?.cast::<bool>().at(condition.span())? {
|
||||
let body = self.body();
|
||||
if i == 0
|
||||
&& is_invariant(condition.as_untyped())
|
||||
&& !can_diverge(body.as_untyped())
|
||||
{
|
||||
bail!(condition.span(), "condition is always true");
|
||||
} else if i >= MAX_ITERS {
|
||||
bail!(self.span(), "loop seems to be infinite");
|
||||
}
|
||||
|
||||
let value = body.eval(vm)?;
|
||||
output = ops::join(output, value).at(body.span())?;
|
||||
|
||||
@ -1012,6 +1025,8 @@ impl Eval for ast::WhileLoop {
|
||||
Some(Flow::Return(..)) => break,
|
||||
None => {}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if flow.is_some() {
|
||||
@ -1022,6 +1037,24 @@ impl Eval for ast::WhileLoop {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the expression always evaluates to the same value.
|
||||
fn is_invariant(expr: &SyntaxNode) -> bool {
|
||||
match expr.cast() {
|
||||
Some(ast::Expr::Ident(_)) => false,
|
||||
Some(ast::Expr::MethodCall(call)) => {
|
||||
is_invariant(call.target().as_untyped())
|
||||
&& is_invariant(call.args().as_untyped())
|
||||
}
|
||||
_ => expr.children().all(is_invariant),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the expression contains a break or return.
|
||||
fn can_diverge(expr: &SyntaxNode) -> bool {
|
||||
matches!(expr.kind(), SyntaxKind::Break | SyntaxKind::Return)
|
||||
|| expr.children().any(can_diverge)
|
||||
}
|
||||
|
||||
impl Eval for ast::ForLoop {
|
||||
type Output = Value;
|
||||
|
||||
|
@ -34,11 +34,13 @@
|
||||
#while [nope] [nope]
|
||||
|
||||
---
|
||||
// Make sure that we terminate and don't complain multiple times.
|
||||
#while true {
|
||||
// Error: 3-7 unknown variable
|
||||
nope
|
||||
}
|
||||
// Error: 8-25 condition is always true
|
||||
#while 2 < "hello".len() {}
|
||||
|
||||
---
|
||||
// Error: 2:1-2:24 loop seems to be infinite
|
||||
#let i = 1
|
||||
#while i > 0 { i += 1 }
|
||||
|
||||
---
|
||||
// Error: 7 expected expression
|
||||
|
Loading…
x
Reference in New Issue
Block a user