diff --git a/CONFIGURING.md b/CONFIGURING.md index 1f5e6fdd..a4a7c8e5 100644 --- a/CONFIGURING.md +++ b/CONFIGURING.md @@ -43,9 +43,12 @@ Raised by DreamChecker: * `no_typehint_implicit_new` - Raised on the use of `new` where no typehint is avaliable * `field_access_static_type` - Raised on using `.field_name` on a variable with no typehint * `proc_call_static_type` - Raised on using `.proc_name()` on a variable with no typehint +* `proc_has_no_parent` - Raised on calling `..()` in a proc with no parent. * `no_operator_overload` - Raised on using a unary operator on a non-primative that doesn't define it's own override, eg `somemob++` +* `unreachable_code` - Raised on finding code that can never be executed * `control_condition_static` - Raised on a control condition such as `if`/`while` having a static condition such as `1` or `"string"` * `if_condition_determinate` - Raised on if condition being always true or always false +* `loop_condition_determinate` - Raised on loop condition such as in `for` being always true or always false Raised by Lexer: diff --git a/src/dreamchecker/lib.rs b/src/dreamchecker/lib.rs index 96b0a62b..307e9c46 100644 --- a/src/dreamchecker/lib.rs +++ b/src/dreamchecker/lib.rs @@ -1143,6 +1143,7 @@ impl<'o, 's> AnalyzeProc<'o, 's> { for stmt in block.iter() { if term.terminates() { error(stmt.location,"possible unreachable code here") + .with_errortype("unreachable_code") .register(self.context); return term // stop evaluating } @@ -1156,10 +1157,12 @@ impl<'o, 's> AnalyzeProc<'o, 's> { match expression.is_truthy() { Some(true) => { error(location,"loop condition is always true") + .with_errortype("loop_condition_determinate") .register(self.context); }, Some(false) => { error(location,"loop condition is always false") + .with_errortype("loop_condition_determinate") .register(self.context); } _ => () @@ -1169,7 +1172,8 @@ impl<'o, 's> AnalyzeProc<'o, 's> { fn visit_control_condition(&mut self, location: Location, expression: &'o Expression) { if expression.is_const_eval() { error(location,"control flow condition is a constant evalutation") - .register(self.context); + .with_errortype("control_condition_static") + .register(self.context); } else if let Some(term) = expression.as_term() { if term.is_static() { @@ -1223,6 +1227,7 @@ impl<'o, 's> AnalyzeProc<'o, 's> { Statement::Throw(expr) => { self.visit_expression(location, expr, None, local_vars); }, Statement::While { condition, block } => { let mut scoped_locals = local_vars.clone(); + // We don't check for static/determine conditions because while(TRUE) is so common. self.visit_expression(location, condition, None, &mut scoped_locals); let mut state = self.visit_block(block, &mut scoped_locals); state.end_loop(); @@ -1249,6 +1254,7 @@ impl<'o, 's> AnalyzeProc<'o, 's> { self.visit_control_condition(condition.location, &condition.elem); if alwaystrue { error(condition.location,"unreachable if block, preceeding if/elseif condition(s) are always true") + .with_errortype("unreachable_code") .register(self.context); } self.visit_expression(condition.location, &condition.elem, None, &mut scoped_locals); @@ -1273,6 +1279,7 @@ impl<'o, 's> AnalyzeProc<'o, 's> { if alwaystrue { if let Some(else_expr) = else_arm.first() { error(else_expr.location ,"unreachable else block, preceeding if/elseif condition(s) are always true") + .with_errortype("unreachable_code") .register(self.context); } } @@ -1668,6 +1675,7 @@ impl<'o, 's> AnalyzeProc<'o, 's> { self.visit_call(location, src, proc, args, true, local_vars) } else { error(location, format!("proc has no parent: {}", self.proc_ref)) + .with_errortype("proc_has_no_parent") .register(self.context); Analysis::empty() } diff --git a/src/dreamchecker/tests/branch_eval_tests.rs b/src/dreamchecker/tests/branch_eval_tests.rs index 3f382715..e7ee1a6d 100644 --- a/src/dreamchecker/tests/branch_eval_tests.rs +++ b/src/dreamchecker/tests/branch_eval_tests.rs @@ -72,3 +72,24 @@ fn do_while() { "##.trim(); check_errors_match(code, DO_WHILE_ERRORS); } + +pub const FOR_LOOP_CONDITION_ERRORS: &[(u32, u16, &str)] = &[ + (2, 5, "loop condition is always true"), + (2, 5, "control flow condition is a static term"), + (4, 5, "control flow condition is a constant evalutation"), +]; + +#[test] +fn for_loop_condition() { + let code = r##" +/proc/test() + for(var/x = 0; 1; x++) + break + for(var/y = 0; 5 <= 7; y++) + break + for(var/z = 1; z <= 6; z++) // Legit, should have no error + break + return +"##.trim(); + check_errors_match(code, FOR_LOOP_CONDITION_ERRORS); +} diff --git a/src/dreamchecker/tests/proc_tests.rs b/src/dreamchecker/tests/proc_tests.rs new file mode 100644 index 00000000..ec6545e9 --- /dev/null +++ b/src/dreamchecker/tests/proc_tests.rs @@ -0,0 +1,18 @@ + +extern crate dreamchecker as dc; + +use dc::test_helpers::check_errors_match; + +pub const NO_PARENT_ERRORS: &[(u32, u16, &str)] = &[ + (2, 5, "proc has no parent: /mob/proc/test"), +]; + +#[test] +fn no_parent() { + let code = r##" +/mob/proc/test() + ..() + return +"##.trim(); + check_errors_match(code, NO_PARENT_ERRORS); +}