From a5ca9fd4a7a78242455eca64545e4880aab0f2a5 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 28 Feb 2024 00:13:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E6=9C=AA=E4=BD=BF=E7=94=A8=E9=87=8F=E7=9A=84lint,=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BA=86skip=E6=8E=A5=E5=9D=97=E7=9A=84=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E7=9C=81=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了未连接的jump的lint --- Cargo.lock | 8 +- Cargo.toml | 2 +- tools/logic_lint/Cargo.toml | 2 +- tools/logic_lint/src/lib.rs | 34 +++-- tools/logic_lint/src/lints.rs | 241 ++++++++++++++++++++++++++++++-- tools/parser/Cargo.toml | 2 +- tools/parser/src/parser.lalrpop | 9 ++ tools/parser/tests/Cargo.toml | 2 +- tools/parser/tests/src/lib.rs | 13 ++ 9 files changed, 282 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61ce3c3..df2cad7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,7 +286,7 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "logic_lint" -version = "0.1.0" +version = "0.1.1" dependencies = [ "lazy-regex", "tag_code", @@ -301,7 +301,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mindustry_logic_bang_lang" -version = "0.14.19" +version = "0.14.20" dependencies = [ "display_source", "logic_lint", @@ -347,7 +347,7 @@ dependencies = [ [[package]] name = "parser" -version = "0.3.1" +version = "0.3.2" dependencies = [ "lalrpop", "lalrpop-util", @@ -358,7 +358,7 @@ dependencies = [ [[package]] name = "parser-tests" -version = "0.1.12" +version = "0.1.13" dependencies = [ "parser", "syntax", diff --git a/Cargo.toml b/Cargo.toml index 4411825..bb460f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mindustry_logic_bang_lang" -version = "0.14.19" +version = "0.14.20" edition = "2021" authors = ["A4-Tacks "] diff --git a/tools/logic_lint/Cargo.toml b/tools/logic_lint/Cargo.toml index f21fb82..b8408b0 100644 --- a/tools/logic_lint/Cargo.toml +++ b/tools/logic_lint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "logic_lint" -version = "0.1.0" +version = "0.1.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/logic_lint/src/lib.rs b/tools/logic_lint/src/lib.rs index 2d5f0fa..d67f221 100644 --- a/tools/logic_lint/src/lib.rs +++ b/tools/logic_lint/src/lib.rs @@ -1,11 +1,13 @@ pub mod lints; use core::fmt; -use std::{borrow::Cow, ops::Deref}; +use std::{borrow::Cow, collections::HashSet, ops::Deref}; -use crate::lints::{ShowLint, Lint}; +use lints::get_useds; use tag_code::mdt_logic_split_unwraped; +use crate::lints::{Lint, ShowLint}; + const LIGHT_ARGS_BEGIN: &str = "\x1b[7m"; const LIGHT_ARGS_END: &str = "\x1b[27m"; @@ -29,7 +31,7 @@ impl<'a> Line<'a> { } pub fn hint_args(&self, hints: &[usize]) -> Vec> { - self.args().into_iter() + self.args().iter() .enumerate() .map(|(i, arg)| if hints.contains(&i) { format!( @@ -52,23 +54,37 @@ impl<'a> Line<'a> { self.args.first().unwrap().lineno } - pub fn args(&self) -> &[Var<'_>] { + pub fn args(&self) -> &[Var<'a>] { self.args.as_ref() } + + pub fn len(&self) -> usize { + self.args().len() + } } #[derive(Debug)] pub struct Source<'a> { lines: Vec>, + used_vars: HashSet<&'a str>, } impl<'a> Source<'a> { pub fn from_str(s: &'a str) -> Self { let lines = s.lines().enumerate() .map(|(lineno, line)| Line::from_line(lineno, line)) - .collect(); + .collect::>(); + + let used_vars = FromIterator::from_iter(lines.iter() + .filter_map(get_useds) + .flatten() + .filter_map(|used| used.as_read().map(Var::value)) + .chain([ + "@counter", + ])); Self { lines, + used_vars, } } @@ -90,8 +106,7 @@ impl<'a> Source<'a> { pub fn lint(&self) -> Vec { self.lines.iter() - .map(|line| line.lint(self)) - .flatten() + .flat_map(|line| line.lint(self)) .collect() } @@ -113,13 +128,12 @@ impl<'a> Source<'a> { } } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Var<'a> { lineno: usize, arg_idx: usize, value: &'a str, } - impl<'a> Deref for Var<'a> { type Target = str; @@ -133,7 +147,7 @@ impl<'a> Var<'a> { Self { lineno, arg_idx, value } } - pub fn value(&self) -> &str { + pub fn value(&self) -> &'a str { self.value } diff --git a/tools/logic_lint/src/lints.rs b/tools/logic_lint/src/lints.rs index 6003bac..1aa5faa 100644 --- a/tools/logic_lint/src/lints.rs +++ b/tools/logic_lint/src/lints.rs @@ -1,9 +1,10 @@ use core::fmt; +use std::vec; use lazy_regex::regex_is_match; use var_utils::{AsVarType, VarType}; -use crate::{Var, Source}; +use crate::{Line, Source, Var}; macro_rules! color_str { ($fnum:literal $($num:literal)* : $str:literal) => { @@ -45,18 +46,198 @@ macro_rules! make_lints { }; } -fn check_assigh_var<'a>( +#[derive(Debug, Clone, Copy)] +pub enum VarUsedMethod { + /// 匹配将要向其写入 + Assign, + /// 匹配将要读取 + Read, +} +impl VarUsedMethod { + /// Returns `true` if the var used method is [`Assign`]. + /// + /// [`Assign`]: VarUsedMethod::Assign + #[must_use] + pub fn is_assign(&self) -> bool { + matches!(self, Self::Assign) + } + + /// Returns `true` if the var used method is [`Read`]. + /// + /// [`Read`]: VarUsedMethod::Read + #[must_use] + pub fn is_read(&self) -> bool { + matches!(self, Self::Read) + } +} + +#[derive(Debug)] +pub struct VarUsed<'a> { + method: VarUsedMethod, + var: Var<'a>, +} +impl<'a> VarUsed<'a> { + pub fn method(&self) -> VarUsedMethod { + self.method + } + + pub fn var(&self) -> &Var<'a> { + &self.var + } + + pub fn as_read(&self) -> Option<&Var<'a>> { + if self.method.is_read() { + Some(&self.var) + } else { + None + } + } + + pub fn as_assign(&self) -> Option<&Var<'a>> { + if self.method.is_assign() { + Some(&self.var) + } else { + None + } + } +} + +enum VarPat { + Catch(VarUsedMethod), + Lit(&'static str), + Any, +} +impl VarPat { + pub fn pat<'a>(&self, var: Var<'a>) -> Result>, ()> { + match *self { + VarPat::Catch(method) => Ok(VarUsed { + method, + var, + }.into()), + VarPat::Lit(s) if s == var.value() => Ok(None), + VarPat::Any => Ok(None), + VarPat::Lit(_) => Err(()), + } + } +} + +thread_local! { + static LINE_PAT: Vec> = { + macro_rules! pat { + (_) => { + VarPat::Any + }; + ($lit:literal) => { + VarPat::Lit($lit) + }; + (a) => { + VarPat::Catch(VarUsedMethod::Assign) + }; + (v) => { + VarPat::Catch(VarUsedMethod::Read) + }; + } + macro_rules! make_pats { + { + $([ + $($t:tt)* + ])* + } => { + vec![ + $( + vec![$(pat!($t)),*] + ),* + ] + }; + } + make_pats! { + ["read" v v v] + ["write" a v v] + ["draw" "clear" v v v] + ["draw" "color" v v v v] + ["draw" "col" v] + ["draw" "stroke" v] + ["draw" "line" v v v v] + ["draw" "rect" v v v v] + ["draw" "lineRect" v v v v] + ["draw" "poly" v v v v v] + ["draw" "linePoly" v v v v v] + ["draw" "triangle" v v v v v v] + ["draw" "image" v v v v v] + ["print" v] + ["drawflush" v] + ["printflush" v] + ["getlink" a v] + ["control" "shoot" v v v v] + ["control" "shootp" v v v] + ["control" _ v v] + ["radar" _ _ _ _ v v a] + ["sensor" a v v] + ["set" a v] + ["op" _ a v v] + ["lookup" _ a v] + ["packcolor" a v v v v] + ["wait" v] + ["stop"] + ["end"] + ["jump" _ _ v v] + ["ubind" v] + ["ucontrol" "within" v v v a] + ["ucontrol" _ v v v v v] + ["uradar" _ _ _ _ _ v a] + ["ulocate" "ore" _ _ v a a a] + ["ulocate" "building" _ v _ a a a a] + ["ulocate" "spawn" _ _ _ a a a a] + ["ulocate" "damaged" _ _ _ a a a a] + // 兜底, 对未录入的语句参数统一为使用 + [_ v v v v v v v v v v v v v v v v v v] + } + }; +} +pub fn get_useds<'a>(line: &Line<'a>) -> Option>> { + LINE_PAT.with(|pats| { + pats.iter() + .find_map(|pat| { + let mut useds = vec![]; + for (pat, var) in pat.iter().zip(line.args()) { + match pat.pat(*var) { + Ok(Some(used)) => useds.push(used), + Ok(None) => {}, + Err(()) => return None, + } + } + useds.into() + }) + }) +} + +fn vec_optiter(value: Option>) -> vec::IntoIter { + match value { + Some(x) => x.into_iter(), + None => Vec::new().into_iter(), + } +} +#[must_use] +fn check_assign_var<'a>( src: &'a crate::Source<'a>, line: &'a crate::Line<'a>, var: &'a Var<'a>, -) -> Option> { +) -> impl IntoIterator> + 'a { match var.as_var_type() { VarType::String(_) | VarType::Number(_) => { - Lint::new(var, WarningLint::AssignLiteral).into() + vec_optiter(vec![Lint::new(var, WarningLint::AssignLiteral)].into()) + }, + VarType::Var(_) => { + let mut lints = Vec::new(); + lints.extend(check_var(src, line, var)); + if !src.used_vars.contains(var.value()) { + lints.push(Lint::new(var, WarningLint::NeverUsed)); + } + vec_optiter(lints.into()) }, - VarType::Var(_) => check_var(src, line, var), } } +#[must_use] fn check_var<'a>( _src: &'a crate::Source<'a>, _line: &'a crate::Line<'a>, @@ -78,6 +259,7 @@ fn check_var<'a>( _ => None, } } +#[must_use] fn check_vars<'a>( src: &'a crate::Source<'a>, line: &'a crate::Line<'a>, @@ -86,6 +268,7 @@ fn check_vars<'a>( vars.into_iter() .filter_map(|var| check_var(src, line, var)) } +#[must_use] fn check_argc<'a>( _src: &'a crate::Source<'a>, line: &'a crate::Line<'a>, @@ -113,6 +296,11 @@ const OP_METHODS: &[&str] = &[ "floor", "ceil", "sqrt", "rand", "sin", "cos", "tan", "asin", "acos", "atan", ]; +const JUMP_METHODS: &[&str] = &[ + "equal", "notEqual", "lessThan", "lessThanEq", + "greaterThan", "greaterThanEq", "strictEqual", + "always", +]; const UNIT_CONTROL_METHODS: &[&str] = &[ "idle", "stop", "move", "approach", "pathfind", "autoPathfind", "boost", "target", "targetp", "itemDrop", @@ -125,7 +313,7 @@ make_lints! { let lints; "set" | "getlink" (2) { if let [_, result, ..] = line.args() { - lints.extend(check_assigh_var(src, line, result)) + lints.extend(check_assign_var(src, line, result)) } if let [_, _, var, ..] = line.args() { lints.extend(check_vars(src, line, [var])) @@ -138,7 +326,7 @@ make_lints! { } } if let [_, _, result, ..] = line.args() { - lints.extend(check_assigh_var(src, line, result)) + lints.extend(check_assign_var(src, line, result)) } if let [_, _, _, var, var1, ..] = line.args() { lints.extend(check_vars(src, line, [var, var1])) @@ -150,7 +338,7 @@ make_lints! { "block" | "unit" | "item" | "liquid" => (), _ => lints.push(Lint::new(mode, ErrorLint::InvalidOper)), } - lints.extend(check_assigh_var(src, line, result)); + lints.extend(check_assign_var(src, line, result)); lints.extend(check_vars(src, line, [index])); } } @@ -184,7 +372,7 @@ make_lints! { } "packcolor" (5) { if let [_, result, args @ ..] = line.args() { - lints.extend(check_assigh_var(src, line, result)); + lints.extend(check_assign_var(src, line, result)); lints.extend(check_vars(src, line, args)); } } @@ -199,7 +387,7 @@ make_lints! { } "read" (3) { if let [_, result, args @ ..] = line.args() { - lints.extend(check_assigh_var(src, line, result)); + lints.extend(check_assign_var(src, line, result)); lints.extend(check_vars(src, line, args)); } } @@ -229,13 +417,35 @@ make_lints! { => Lint::new(arg, ErrorLint::InvalidOper).into(), } } + fn check_order<'a>(arg: &'a Var<'a>) -> Option> { + match arg.value() { + | "distance" | "health" | "shield" + | "armor" | "maxHealth" + => None, + | _ + => Lint::new(arg, ErrorLint::InvalidOper).into(), + } + } if let [_, filt1, filt2, filt3, order, from, rev, result] = line.args() { lints.extend(check_filter(filt1)); lints.extend(check_filter(filt2)); lints.extend(check_filter(filt3)); - lints.extend(check_vars(src, line, [order, from, rev])); - lints.extend(check_assigh_var(src, line, result)); + lints.extend(check_order(order)); + lints.extend(check_vars(src, line, [from, rev])); + lints.extend(check_assign_var(src, line, result)); + } + } + "jump" (4) { + if let [_, target, method, a, b] + = line.args() { + if target.value() == "-1" { + lints.push(Lint::new(target, WarningLint::NoTargetJump)); + } + if !JUMP_METHODS.contains(&&**method) { + lints.push(Lint::new(method, ErrorLint::InvalidOper)); + } + lints.extend(check_vars(src, line, [a, b])); } } } @@ -273,7 +483,7 @@ impl ShowLint for Lint<'_> { arg_idx, )?; self.msg.show_lint(src, f)?; - write!(f, "\n")?; + writeln!(f)?; let (prelines, suflines) = src.view_lines(lineno, (2, 2)); @@ -345,6 +555,9 @@ pub enum WarningLint { AssignLiteral, /// 从命名来看疑似是未被替换的常量 SuspectedConstant, + /// 未被使用 + NeverUsed, + NoTargetJump, } impl ShowLint for WarningLint { fn show_lint( @@ -364,6 +577,8 @@ impl ShowLint for WarningLint { WarningLint::SuspectedConstant => { write!(f, "命名疑似未被替换的常量")? }, + WarningLint::NeverUsed => write!(f, "未被使用到的量")?, + WarningLint::NoTargetJump => write!(f, "没有目标的跳转")?, } Ok(()) } diff --git a/tools/parser/Cargo.toml b/tools/parser/Cargo.toml index af6e078..1e59553 100644 --- a/tools/parser/Cargo.toml +++ b/tools/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "parser" -version = "0.3.1" +version = "0.3.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/parser/src/parser.lalrpop b/tools/parser/src/parser.lalrpop index 5c22fa9..af66537 100644 --- a/tools/parser/src/parser.lalrpop +++ b/tools/parser/src/parser.lalrpop @@ -799,6 +799,15 @@ pub Control: LogicLine = { ]).into() }, + "skip" => { + let lab = meta.get_tag(); + Expand(vec![ + Goto(lab.clone(), JumpCmp::Always.into()).into(), + body.into(), + LogicLine::new_label(lab, meta), + ]).into() + }, + "while" CtrlStart => { let (break_lab, continue_lab) = ctrl; let [end, head] = [meta.get_tag(), meta.get_tag()]; diff --git a/tools/parser/tests/Cargo.toml b/tools/parser/tests/Cargo.toml index 330021b..518011b 100644 --- a/tools/parser/tests/Cargo.toml +++ b/tools/parser/tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "parser-tests" -version = "0.1.12" +version = "0.1.13" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/parser/tests/src/lib.rs b/tools/parser/tests/src/lib.rs index 68e9824..aabd044 100644 --- a/tools/parser/tests/src/lib.rs +++ b/tools/parser/tests/src/lib.rs @@ -223,6 +223,19 @@ fn control_test() { } "#).unwrap(), ); + + assert_eq!( + parse!(parser, r#" + skip _ { + print 1; + } + "#).unwrap(), + parse!(parser, r#" + skip { + print 1; + } + "#).unwrap(), + ); } #[test]