Implement compile time warning for datum[] (#397)

Lummox recently made this a RUNTIME error, but also irrespective of
that this tends to mask HELL vars[] security holes. We really should be
linting for it.

I had to implement operator overloading support for the builtin parser,
which was far more annoying then I expected. Bit scuffed, but it works.
(I am unsure of how to parse for "" though so I left that unimplemented)

This will likely throw errors on most codebases, I'm unsure of how to
handle that.
This commit is contained in:
LemonInTheDark 2025-12-18 16:32:08 -08:00 committed by GitHub
parent 660457818e
commit 70176ca27d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 122 additions and 1 deletions

View file

@ -50,6 +50,7 @@ Raised by DreamChecker:
* `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
* `improper_index` - Raised on accessing a non list with []
Raised by Lexer:

View file

@ -11,6 +11,7 @@ use syn::*;
struct Header {
attrs: Vec<Attribute>,
path: Vec<Ident>,
operator_overload_target: Option<String>,
}
impl Header {
@ -24,7 +25,110 @@ impl Header {
input.parse::<Token![/]>()?;
self.path.push(Ident::parse_any(input)?);
}
if let Some(final_ident) = self.path.last() {
// If we find an operator{some token}() pattern we allow the some token part
if final_ident == "operator" {
self.parse_operator(input)?;
}
}
Ok(())
}
fn parse_operator(&mut self, input: ParseStream) -> Result<()> {
let text_token: Option<&str> = if input.parse::<Token![%]>().is_ok() {
if input.parse::<Token![%]>().is_ok() {
Some("%%")
} else if input.parse::<Token![%=]>().is_ok() {
Some("%%=")
} else {
Some("%")
}
} else if input.parse::<Token![&]>().is_ok() {
Some("&")
} else if input.parse::<Token![&=]>().is_ok() {
Some("&=")
} else if input.parse::<Token![*]>().is_ok() {
if input.parse::<Token![*]>().is_ok() {
Some("**")
} else {
Some("*")
}
} else if input.parse::<Token![*=]>().is_ok() {
Some("*=")
} else if input.parse::<Token![/]>().is_ok() {
Some("/")
} else if input.parse::<Token![/=]>().is_ok() {
Some("/=")
} else if input.parse::<Token![+]>().is_ok() {
if input.parse::<Token![+]>().is_ok() {
Some("++")
} else {
Some("+")
}
} else if input.parse::<Token![+=]>().is_ok() {
Some("+=")
} else if input.parse::<Token![-]>().is_ok() {
if input.parse::<Token![-]>().is_ok() {
Some("--")
} else {
Some("-")
}
} else if input.parse::<Token![-=]>().is_ok() {
Some("-=")
} else if input.parse::<Token![<]>().is_ok() {
Some("<")
} else if input.parse::<Token![<<]>().is_ok() {
Some("<<")
} else if input.parse::<Token![<<=]>().is_ok() {
Some("<<=")
} else if input.parse::<Token![<=]>().is_ok() {
Some("<=")
} else if input.parse::<Token![>=]>().is_ok() {
Some(">=")
} else if input.parse::<Token![>>]>().is_ok() {
Some(">>")
} else if input.parse::<Token![>>=]>().is_ok() {
Some(">>=")
} else if input.parse::<Token![^]>().is_ok() {
Some("^")
} else if input.parse::<Token![^=]>().is_ok() {
Some("^=")
} else if input.parse::<Token![|]>().is_ok() {
Some("|")
} else if input.parse::<Token![|=]>().is_ok() {
Some("|=")
} else if input.parse::<Token![~]>().is_ok() {
if input.parse::<Token![=]>().is_ok() {
Some("~=")
} else {
Some("~")
}
} else if input.parse::<Token![~]>().is_ok() {
Some("~")
} else if input.peek(Token![:]) && input.peek2(Token![=]) {
input.parse::<Token![:]>()?;
input.parse::<Token![=]>()?;
Some(":=")
} else if self.brackets_next(input).is_ok() {
if input.parse::<Token![=]>().is_ok() {
Some("[]=")
} else {
Some("[]")
}
} else {
// Todo: Implement operator""() support. Unsure how to expect an empty string
None
};
if let Some(text) = text_token {
self.operator_overload_target = Some(text.to_string());
}
Ok(())
}
fn brackets_next(&mut self, input: ParseStream) -> Result<()> {
// Sorry
let _bracket_dummy;
bracketed!(_bracket_dummy in input);
Ok(())
}
}
@ -143,7 +247,11 @@ pub fn builtins_table(input: TokenStream) -> TokenStream {
let mut output = Vec::new();
for entry in builtins {
let span = entry.header.path.first().unwrap().span();
let lit_strs: Vec<_> = entry.header.path.into_iter().map(|x| LitStr::new(&x.to_string(), x.span())).collect();
let mut lit_strs: Vec<_> = entry.header.path.into_iter().map(|x| LitStr::new(&x.to_string(), x.span())).collect();
if let Some(operator) = entry.header.operator_overload_target {
let last_entry = lit_strs.pop().unwrap();
lit_strs.push(LitStr::new((last_entry.value() + operator.as_str()).as_str(), last_entry.span()));
}
let path = quote! {
&[ #(#lit_strs),* ]
};

View file

@ -2105,6 +2105,14 @@ impl<'o, 's> AnalyzeProc<'o, 's> {
}
res
},
StaticType::Type(typeref) => {
if typeref.get_proc("operator[]").is_none() {
error(location, format!("invalid list access on {}", typeref.path))
.with_errortype("improper_index")
.register(self.context);
}
lhs.clone()
},
_ => lhs.clone() // carry through fix_hint
}
},

View file

@ -626,6 +626,7 @@ pub fn register_builtins(tree: &mut ObjectTreeBuilder) {
list/var/const/parent_type;
list/var/tag;
list/var/const/list/vars;
list/proc/operator[]();
list/proc/Add(Item1, Item2/*,...*/);
list/proc/Copy(Start=1, End=0);
list/proc/Cut(Start=1, End=0);
@ -642,6 +643,7 @@ pub fn register_builtins(tree: &mut ObjectTreeBuilder) {
alist/var/const/type;
alist/var/const/parent_type;
alist/var/tag;
alist/proc/operator[]();
alist/proc/Add(Item1, Item2/*,...*/);
alist/proc/Copy(Start=1, End=0);
alist/proc/Cut(Start=1, End=0);
@ -1110,6 +1112,7 @@ pub fn register_builtins(tree: &mut ObjectTreeBuilder) {
savefile/var/list/dir;
savefile/var/eof;
savefile/var/name;
savefile/proc/operator[]();
savefile/proc/ExportText(/* path=cd, file */);
savefile/proc/Flush();
savefile/proc/ImportText(/* path=cd, file */);
@ -1282,6 +1285,7 @@ pub fn register_builtins(tree: &mut ObjectTreeBuilder) {
vector/var/y;
vector/var/z;
vector/proc/operator[]();
vector/proc/Cross(B);
vector/proc/Dot(B);
vector/proc/Interpolate(B, t);