diem/diem · error

Unbound local {:?}

Error message

Unbound local {:?}

What it means

`copy_local` reads the abstract value of a local from the analyzer's `locals` map; if the temp index has no bound value it panics with 'Unbound local'. The analysis tracks abstract addresses per TempIndex, and every read local must have been bound by a prior `assign_local`, `borrow_loc`, or parameter initialization. This panic means the instruction stream referenced a local before (or without) binding it in the abstract state.

Source

Thrown at language/move-prover/bytecode/src/read_write_set_analysis.rs:203

                self.locals.bind_local_node(*ret, node, caller_fun_env)
            }
        }
        // (5) join caller and callee accesses
        // TODO: can we do a strong update here in some cases?
        self.accesses.join(&new_callee_accesses);
    }

    /// Copy the contents of `rhs_index` into `lhs_index`. Fails if `rhs_index` is not bound
    pub fn copy_local(
        &mut self,
        lhs_index: TempIndex,
        rhs_index: TempIndex,
        fun_env: &FunctionEnv,
    ) {
        let rhs_value = self
            .locals
            .get_local(rhs_index, fun_env)
            .unwrap_or_else(|| panic!("Unbound local {:?}", rhs_index))
            .clone();
        self.locals.bind_local(lhs_index, rhs_value, fun_env)
    }

    pub fn assign_local(
        &mut self,
        lhs_index: TempIndex,
        rhs_index: TempIndex,
        func_env: &FunctionEnv,
    ) {
        if let Some(rhs_data) = self.locals.get_local(rhs_index, func_env).cloned() {
            self.locals.bind_local(lhs_index, rhs_data, func_env);
            self.record_access(rhs_index, Access::Read, func_env)
        } else if let Some(rhs_node) = self.locals.get_local_node(rhs_index, func_env).cloned() {
            self.locals.bind_local_node(lhs_index, rhs_node, func_env);
        }
    }

View on GitHub (pinned to fc4714a8ea)

Solutions

  1. Verify the offending bytecode instruction's temp index against the function's local/parameter list in fun_env
  2. Ensure all locals are bound (via assign_local or parameter initialization) before `copy_local` reads them
  3. Re-run bytecode verification/stackless transformation with a matched toolchain version so temp indices are consistent
  4. If you added a bytecode pass, bind an abstract value for the local in the state before copying

Example fix

// before
let rhs_value = self.locals.get_local(rhs_index, fun_env)
    .unwrap_or_else(|| panic!("Unbound local {:?}", rhs_index)).clone();
// after
let rhs_value = self.locals.get_local(rhs_index, fun_env)
    .cloned()
    .unwrap_or_else(|| AbsAddr::default()); // or skip/bail on unbound locals
Defensive patterns

Strategy: validation

Validate before calling

// Before copy_local, check the source local is bound:
fn local_bound(state: &TransferFunctionState, idx: TempIndex, fun_env: &FunctionEnv) -> bool {
    idx < fun_env.get_local_count() && state.locals.get_local(idx, fun_env).is_some()
}

Type guard

fn is_bound(state: &TransferFunctionState, idx: TempIndex, env: &FunctionEnv) -> bool {
    state.locals.get_local(idx, env).is_some()
}

Prevention

When it happens

Trigger: `copy_local(lhs_index, rhs_index, fun_env)` called with `rhs_index` never bound in the current abstract state — e.g. bytecode `CopyLoc` of a local whose abstract value was dropped/not initialized, or an out-of-sync temp index after bytecode transformation.

Common situations: Running the RWSet analysis over bytecode generated or rewritten by an external tool; stackless-bytecode passes that remove initialization of dead locals but leave later reads; mismatches between function signature temps and the analyzed body.

Related errors


AI-assisted analysis of diem/diem@fc4714a8ea (2026-09-04). Data as JSON: /api/errors/87474c4b01af3505. Report an issue: GitHub.