@@ -5774,8 +5774,49 @@ impl Interpreter {
57745774 self . parse_arithmetic_impl ( & expanded, 0 )
57755775 }
57765776
5777+ /// Recursively resolve a variable value in arithmetic context.
5778+ /// In bash arithmetic, bare variable names are recursively evaluated:
5779+ /// if b=a and a=3, then $((b)) evaluates b -> "a" -> 3.
5780+ /// If x='1 + 2', then $((x)) evaluates x -> "1 + 2" -> 3 (as sub-expression).
5781+ /// THREAT[TM-DOS-026]: `depth` prevents infinite recursion.
5782+ fn resolve_arith_var ( & self , value : & str , depth : usize ) -> String {
5783+ if depth >= Self :: MAX_ARITHMETIC_DEPTH {
5784+ return "0" . to_string ( ) ;
5785+ }
5786+ let trimmed = value. trim ( ) ;
5787+ if trimmed. is_empty ( ) {
5788+ return "0" . to_string ( ) ;
5789+ }
5790+ // If value is a simple integer, return it directly
5791+ if trimmed. parse :: < i64 > ( ) . is_ok ( ) {
5792+ return trimmed. to_string ( ) ;
5793+ }
5794+ // If value looks like a variable name, recursively dereference
5795+ if Self :: is_valid_var_name ( trimmed) {
5796+ let inner = self . expand_variable ( trimmed) ;
5797+ return self . resolve_arith_var ( & inner, depth + 1 ) ;
5798+ }
5799+ // Value contains an expression (e.g. "1 + 2") — expand vars in it
5800+ // and wrap in parens to preserve grouping
5801+ let expanded = self . expand_arithmetic_vars_depth ( trimmed, depth + 1 ) ;
5802+ format ! ( "({})" , expanded)
5803+ }
5804+
57775805 /// Expand variables in arithmetic expression (no $ needed in $((...)))
57785806 fn expand_arithmetic_vars ( & self , expr : & str ) -> String {
5807+ self . expand_arithmetic_vars_depth ( expr, 0 )
5808+ }
5809+
5810+ /// Inner implementation with depth tracking for recursive expansion.
5811+ /// THREAT[TM-DOS-026]: `depth` prevents stack overflow via recursive variable values.
5812+ fn expand_arithmetic_vars_depth ( & self , expr : & str , depth : usize ) -> String {
5813+ if depth >= Self :: MAX_ARITHMETIC_DEPTH {
5814+ return "0" . to_string ( ) ;
5815+ }
5816+
5817+ // Strip double quotes — "$x" in arithmetic is the same as $x
5818+ let expr = expr. replace ( '"' , "" ) ;
5819+
57795820 let mut result = String :: new ( ) ;
57805821 let mut chars = expr. chars ( ) . peekable ( ) ;
57815822 // Track whether we're in a numeric literal context (after # or 0x)
@@ -5794,6 +5835,8 @@ impl Interpreter {
57945835 }
57955836 }
57965837 if !name. is_empty ( ) {
5838+ // $var is direct text substitution — no recursive arithmetic eval.
5839+ // Only bare names (without $) get recursive resolution.
57975840 let value = self . expand_variable ( & name) ;
57985841 if value. is_empty ( ) {
57995842 result. push ( '0' ) ;
@@ -5833,12 +5876,46 @@ impl Interpreter {
58335876 break ;
58345877 }
58355878 }
5836- // Expand the variable
5837- let value = self . expand_variable ( & name) ;
5838- if value. is_empty ( ) {
5839- result. push ( '0' ) ;
5879+ // Check for array access: name[expr]
5880+ if chars. peek ( ) == Some ( & '[' ) {
5881+ chars. next ( ) ; // consume '['
5882+ let mut index_expr = String :: new ( ) ;
5883+ let mut bracket_depth = 1 ;
5884+ while let Some ( & c) = chars. peek ( ) {
5885+ chars. next ( ) ;
5886+ if c == '[' {
5887+ bracket_depth += 1 ;
5888+ index_expr. push ( c) ;
5889+ } else if c == ']' {
5890+ bracket_depth -= 1 ;
5891+ if bracket_depth == 0 {
5892+ break ;
5893+ }
5894+ index_expr. push ( c) ;
5895+ } else {
5896+ index_expr. push ( c) ;
5897+ }
5898+ }
5899+ // Evaluate the index expression as arithmetic
5900+ let idx = self . evaluate_arithmetic ( & index_expr) ;
5901+ // Look up array element
5902+ if let Some ( arr) = self . arrays . get ( & name) {
5903+ let idx_usize: usize = idx. try_into ( ) . unwrap_or ( 0 ) ;
5904+ let value = arr. get ( & idx_usize) . cloned ( ) . unwrap_or_default ( ) ;
5905+ result. push_str ( & self . resolve_arith_var ( & value, depth) ) ;
5906+ } else {
5907+ // Not an array — treat as scalar (index 0 returns the var value)
5908+ let value = self . expand_variable ( & name) ;
5909+ if idx == 0 {
5910+ result. push_str ( & self . resolve_arith_var ( & value, depth) ) ;
5911+ } else {
5912+ result. push ( '0' ) ;
5913+ }
5914+ }
58405915 } else {
5841- result. push_str ( & value) ;
5916+ // Expand the variable with recursive arithmetic resolution
5917+ let value = self . expand_variable ( & name) ;
5918+ result. push_str ( & self . resolve_arith_var ( & value, depth) ) ;
58425919 }
58435920 } else {
58445921 in_numeric_literal = false ;
0 commit comments