amber-lang / amber-lang/bash2amber

[Detail Bug] Bash-to-Amber: local variable shadowing leaks inner alias into outer scope

Open
#9 0 comments 0 reactions 0 assignees View on GitHub
bug detail
Dominant language
Rust
Stars
2
Forks
0
PR merge metrics
No merged PRs in 30d

Description

# Summary
- **Context**: The `RenderContext` manages variable aliasing and scoping during the conversion of Bash scripts to Amber.
- **Bug**: `merge_from_child` uses `HashMap::extend` to merge `var_aliases`, which causes shadowed variable aliases (created by `local` in blocks) to leak back into the parent scope.
- **Actual vs. expected**: When a variable is shadowed in a block, its new alias overwrites the parent's alias in the parent context after the block ends; it should instead preserve the parent's original alias.
- **Impact**: The generated Amber code attempts to use out-of-scope variable aliases (e.g., `x_2` instead of `x`), resulting in "Variable does not exist" errors when building the Amber output.

# Code with bug
```rust
pub(super) fn merge_from_child(&mut self, child: Self) {
self.var_aliases.extend(child.var_aliases); // <-- BUG 🔴 Overwrites parent aliases with shadowed child aliases
self.declared_vars.extend(child.declared_vars);
self.functions.extend(child.functions);
```

# Evidence
I created a reproduction Bash script `tests/bash/local_shadowing.sh`:
```bash
f() {
local x=1
if true; then
local x=2
echo "inner: $x"
fi
echo "outer: $x"
}
f
```
The converted Amber code was:
```amber
fun f() {
let x = 1
if trust $ true $ {
let x_2 = 2
echo("inner: {x_2}")
}
echo("outer: {x_2}")
}
f()
```
Attempting to build this Amber code failed with:
`ERROR Variable 'x_2' does not exist at /home/user/bash2amber/target/runtime-compare/local_shadowing_1.ab:7:19`

# Why has this bug gone undetected?
Shadowing a `local` variable with another `local` declaration within a block in the same function is relatively uncommon in Bash scripts. Furthermore, if the shadowed variable is not used again after the block ends, the leaked alias does not cause a visible error in the generated code.

# Recommended fix
Modify `merge_from_child` to only add aliases for variables that are not already present in the parent context:
```rust
pub(super) fn merge_from_child(&mut self, child: Self) {
for (raw, alias) in child.var_aliases { // <-- FIX 🟢
self.var_aliases.entry(raw).or_insert(alias);
}
self.declared_vars.extend(child.declared_vars);
```

# History
This bug was introduced in commit 9fd39e1 (@Ph0enixKM, 2026-02-07). This commit introduced `declare_local_var` to support `local` variable shadowing in Bash functions, which triggered the flawed `extend` merge logic in `RenderContext::merge_from_child` that had been present since the initial commit.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.