go-delve/delve · error

Expression is unreadable

Error message

Expression %q is unreadable: %v

What it means

setValue (used both for SetVariable and for copying arguments during function-call injection via funcCallCopyOneArg) refuses to write when the source expression evaluated to an unreadable variable — srcv.Unreadable was set while loading it (bad memory, optimized-out value, unreadable DWARF, etc.). The debugger surfaces the underlying unreadability reason rather than writing garbage to the destination.

Solutions

  1. Verify the source expression evaluates on its own first (EvalExpression); fix or replace it if it is unreadable.
  2. Use a literal value instead of copying from an unreadable variable (e.g. x = 0 instead of x = y when y is unreadable).
  3. Rebuild the debuggee without optimizations (-gcflags="all=-N -l") so variables have concrete locations.
  4. Evaluate in a different frame where the variable is actually live and addressable.

Example fix

// before
dbg.SetVariable("x", "optimizedOutVar") // Expression "optimizedOutVar" is unreadable
// after
val, err := dbg.EvalExpression("optimizedOutVar", cfg)
if err == nil && val.Unreadable == nil {
    dbg.SetVariable("x", "optimizedOutVar")
} else {
    dbg.SetVariable("x", "0")
}
Defensive patterns

Strategy: validation

Validate before calling

val, err := scope.EvalExpression(srcExpr, loadSingleValue)
if err != nil || val == nil || val.Unreadable != nil {
    return fmt.Errorf("source expression %q not readable, refusing assignment", srcExpr)
}

Type guard

func isReadable(v *proc.Variable) bool { return v != nil && v.Unreadable == nil }

Try / catch

if err := scope.SetVariable(name, value); err != nil {
    var uv *proc.Variable
    if strings.Contains(err.Error(), "is unreadable") {
        // fall back to a literal assignment or surface the reason to the user
        return fmt.Errorf("cannot copy from %s: %w", value, err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling SetVariable(name, value) or a function-call injection argument copy where the value expression resolves to a variable whose memory cannot be read (srcv.Unreadable != nil after loadValue), e.g. assigning one variable's value to another when the source variable is optimized out or lives in unreadable memory.

Common situations: Assigning from a variable that the compiler optimized into registers and DWARF cannot reconstruct; copying from a variable behind a nil or dangling pointer; source expression in an inlined frame whose location is unavailable; stripped/optimized binaries (-gcflags='-N -l' not used).

Related errors


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

Appendix: source

Thrown at pkg/proc/eval.go:635

//     containing a single pointer field) the type conversion to "interface {}"
//     is performed.
//   - If srcv and dstv have the same type and are both addressable then the
//     contents of srcv are copied byte-by-byte into dstv
func (scope *EvalScope) setValue(dstv, srcv *Variable, srcExpr string) error {
	srcv.loadValue(loadSingleValue)

	typerr := srcv.isType(dstv.RealType, dstv.Kind)
	if _, isTypeConvErr := typerr.(*typeConvErr); isTypeConvErr {
		// attempt iface -> eface and ptr-shaped -> eface conversions.
		return convertToEface(srcv, dstv)
	}
	if typerr != nil {
		return typerr
	}

	if srcv.Unreadable != nil {
		//lint:ignore ST1005 backwards compatibility
		return fmt.Errorf("Expression %q is unreadable: %v", srcExpr, srcv.Unreadable)
	}

	// Numerical types
	switch dstv.Kind {
	case reflect.Float32, reflect.Float64:
		f, _ := constant.Float64Val(srcv.Value)
		return dstv.writeFloatRaw(f, dstv.RealType.Size())
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		n, _ := constant.Int64Val(srcv.Value)
		return dstv.writeUint(uint64(n), dstv.RealType.Size())
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		n, _ := constant.Uint64Val(srcv.Value)
		return dstv.writeUint(n, dstv.RealType.Size())
	case reflect.Bool:
		return dstv.writeBool(constant.BoolVal(srcv.Value))
	case reflect.Complex64, reflect.Complex128:
		real, _ := constant.Float64Val(constant.Real(srcv.Value))
		imag, _ := constant.Float64Val(constant.Imag(srcv.Value))

View on GitHub (pinned to a23773e6c3)