impl SemanticAnalyzer {
fn analyze_case_stmt(
&mut self,
word: &BashExpr,
arms: &[CaseArm],
scope: &mut ScopeInfo,
) -> SemanticResult<()> {
self.analyze_expression(word, scope)?;
for arm in arms {
self.analyze_body(&arm.body, scope)?;
}
Ok(())
}
fn analyze_select_stmt(
&mut self,
variable: &str,
body: &[BashStmt],
scope: &mut ScopeInfo,
) -> SemanticResult<()> {
scope.variables.insert(
variable.to_string(),
VarInfo {
name: variable.to_string(),
exported: false,
assigned: true,
used: false,
inferred_type: InferredType::String, },
);
self.analyze_body(body, scope)
}
fn analyze_body(&mut self, body: &[BashStmt], scope: &mut ScopeInfo) -> SemanticResult<()> {
for stmt in body {
self.analyze_statement(stmt, scope)?;
}
Ok(())
}
fn analyze_expression(&mut self, expr: &BashExpr, scope: &mut ScopeInfo) -> SemanticResult<()> {
match expr {
BashExpr::Variable(name) => {
Self::mark_var_used(scope, name);
}
BashExpr::CommandSubst(cmd) | BashExpr::CommandCondition(cmd) => {
self.analyze_statement(cmd, scope)?;
}
BashExpr::Array(items) | BashExpr::Concat(items) => {
for item in items {
self.analyze_expression(item, scope)?;
}
}
BashExpr::Test(test_expr) => {
self.analyze_test_expr(test_expr, scope)?;
}
BashExpr::Literal(_) | BashExpr::Glob(_) => {}
BashExpr::Arithmetic(arith) => {
self.analyze_arithmetic(arith, scope)?;
}
BashExpr::DefaultValue { variable, default }
| BashExpr::ErrorIfUnset {
variable,
message: default,
} => {
Self::mark_var_used(scope, variable);
self.analyze_expression(default, scope)?;
}
BashExpr::AlternativeValue {
variable,
alternative,
} => {
Self::mark_var_used(scope, variable);
self.analyze_expression(alternative, scope)?;
}
BashExpr::AssignDefault { variable, default } => {
self.analyze_assign_default(variable, default, scope)?;
}
BashExpr::StringLength { variable } => {
Self::mark_var_used(scope, variable);
}
BashExpr::RemoveSuffix { variable, pattern }
| BashExpr::RemovePrefix { variable, pattern }
| BashExpr::RemoveLongestPrefix { variable, pattern }
| BashExpr::RemoveLongestSuffix { variable, pattern } => {
Self::mark_var_used(scope, variable);
self.analyze_expression(pattern, scope)?;
}
}
Ok(())
}
fn mark_var_used(scope: &mut ScopeInfo, name: &str) {
if let Some(var) = scope.variables.get_mut(name) {
var.used = true;
}
}
fn analyze_assign_default(
&mut self,
variable: &str,
default: &BashExpr,
scope: &mut ScopeInfo,
) -> SemanticResult<()> {
if let Some(var) = scope.variables.get_mut(variable) {
var.used = true;
var.assigned = true;
} else {
scope.variables.insert(
variable.to_string(),
VarInfo {
name: variable.to_string(),
exported: false,
assigned: true,
used: true,
inferred_type: InferredType::Unknown,
},
);
}
self.analyze_expression(default, scope)
}
fn analyze_test_expr(&mut self, test: &TestExpr, scope: &mut ScopeInfo) -> SemanticResult<()> {
match test {
TestExpr::StringEq(a, b)
| TestExpr::StringNe(a, b)
| TestExpr::IntEq(a, b)
| TestExpr::IntNe(a, b)
| TestExpr::IntLt(a, b)
| TestExpr::IntLe(a, b)
| TestExpr::IntGt(a, b)
| TestExpr::IntGe(a, b) => {
self.analyze_expression(a, scope)?;
self.analyze_expression(b, scope)?;
}
TestExpr::FileExists(path)
| TestExpr::FileReadable(path)
| TestExpr::FileWritable(path)
| TestExpr::FileExecutable(path)
| TestExpr::FileDirectory(path) => {
self.analyze_expression(path, scope)?;
if let BashExpr::Literal(p) = path {
self.effects.file_reads.insert(p.clone());
}
}
TestExpr::StringEmpty(s) | TestExpr::StringNonEmpty(s) => {
self.analyze_expression(s, scope)?;
}
TestExpr::And(a, b) | TestExpr::Or(a, b) => {
self.analyze_test_expr(a, scope)?;
self.analyze_test_expr(b, scope)?;
}
TestExpr::Not(t) => {
self.analyze_test_expr(t, scope)?;
}
}
Ok(())
}
fn analyze_arithmetic(
&mut self,
arith: &ArithExpr,
scope: &mut ScopeInfo,
) -> SemanticResult<()> {
match arith {
ArithExpr::Variable(name) => {
if let Some(var) = scope.variables.get_mut(name) {
var.used = true;
}
}
ArithExpr::Add(a, b)
| ArithExpr::Sub(a, b)
| ArithExpr::Mul(a, b)
| ArithExpr::Div(a, b)
| ArithExpr::Mod(a, b) => {
self.analyze_arithmetic(a, scope)?;
self.analyze_arithmetic(b, scope)?;
}
ArithExpr::Number(_) => {}
}
Ok(())
}
fn infer_type(&self, expr: &BashExpr) -> InferredType {
match expr {
BashExpr::Literal(s) => {
if s.parse::<i64>().is_ok() {
InferredType::Integer
} else {
InferredType::String
}
}
BashExpr::Array(_) => InferredType::Array,
BashExpr::Arithmetic(_) => InferredType::Integer,
_ => InferredType::Unknown,
}
}
fn track_command_effects(&mut self, command: &str) {
match command {
"curl" | "wget" | "nc" | "telnet" | "ssh" => {
self.effects.network_access = true;
}
"rm" | "mv" | "cp" | "touch" | "mkdir" | "rmdir" => {
self.effects.file_writes.insert(command.to_string());
}
"cat" | "less" | "more" | "head" | "tail" | "grep" => {
self.effects.file_reads.insert(command.to_string());
}
_ => {}
}
}
}
include!("semantic_default.rs");