go-delve/delve · warning

no address for escaped variable

Error message

no address for escaped variable

What it means

When simpleLocals post-processes '&var' entries (variables that were optimized to escapes and exposed via a pointer pseudo-variable), it dereferences the pointer. If the resulting variable has Addr == 0 and no other unreadable reason, delve cannot locate the escaped variable's storage and marks the variable Unreadable with this error.

Source

Thrown at pkg/proc/eval.go:553

		}
		depths = append(depths, depth)
	}

	if len(vars) == 0 {
		return vars, nil
	}

	sort.Stable(&variablesByDepthAndDeclLine{vars, depths})

	lvn := map[string]*Variable{} // lvn[n] is the last variable we saw named n

	for i, v := range vars {
		if name := v.Name; len(name) > 1 && name[0] == '&' {
			locationExpr := v.LocationExpr
			declLine := v.DeclLine
			v = v.maybeDereference()
			if v.Addr == 0 && v.Unreadable == nil {
				v.Unreadable = errors.New("no address for escaped variable")
			}
			v.Name = name[1:]
			v.Flags |= VariableEscaped
			// See https://github.com/go-delve/delve/issues/2049 for details
			if locationExpr != nil {
				locationExpr.isEscaped = true
				v.LocationExpr = locationExpr
			}
			v.DeclLine = declLine
			vars[i] = v
		}
		if otherv := lvn[v.Name]; otherv != nil {
			otherv.Flags |= VariableShadowed
		}
		lvn[v.Name] = v
	}

	return vars, nil

View on GitHub (pinned to a23773e6c3)

Solutions

  1. Rebuild with optimizations disabled: go build -gcflags="all=-N -l" so locals stay addressable.
  2. Step to a line where the variable is live (check DeclLine) and re-run locals.
  3. Read the variable via print &var or print var at a stop point where the compiler kept it addressable; check v.Unreadable in tooling and degrade gracefully.
  4. If scripting the API, treat Addr==0/Unreadable entries as unavailable and continue rather than aborting the whole locals list.

Example fix

// before
vars, err := scope.Locals(0)
for _, v := range vars { use(v) }
// after
vars, err := scope.Locals(0)
for _, v := range vars {
    if v.Unreadable != nil {
        continue // e.g. "no address for escaped variable": optimized out
    }
    use(v)
}
Defensive patterns

Strategy: type-guard

Validate before calling

vars, _ := scope.Locals(0)
usable := []*proc.Variable{}
for _, v := range vars {
    if v.Unreadable == nil { usable = append(usable, v) }
}

Type guard

func isReadable(v *proc.Variable) bool {
    return v != nil && v.Unreadable == nil && !(v.Addr == 0 && v.Kind == reflect.Ptr)
}

Try / catch

vars, err := scope.Locals(0)
for _, v := range vars {
    if v.Unreadable != nil && strings.Contains(v.Unreadable.Error(), "no address for escaped variable") {
        render(v.Name, "<optimized out>")
        continue
    }
    render(v.Name, v.ValueString())
}

Prevention

When it happens

Trigger: Listing locals where the compiler (register ABI / escape analysis) replaced a variable with &var pointing to heap/stack slots, but the pointer value read as 0 — e.g., variable moved to registers, dead at the current PC, or memory read failed silently.

Common situations: Inspecting variables heavily optimized by newer Go compilers (Go 1.15+ register ABI); inspecting a variable outside its live range; debugging optimized (non -N -l) builds where a local only exists as a dead pointer slot.

Related errors


AI-assisted analysis of go-delve/delve@a23773e6c3 (2026-08-31). Data as JSON: /api/errors/2364df5a050d0581. Report an issue: GitHub.