Skip to content

bug: EvaluatorResponse.Type() misclassifies results — Extism integers report ERROR, Risor nil reports "null" #161

Description

@robbyt

Summary

Two engines' EvaluatorResponse.Type() implementations return wrong data.Types values for common results. Callers switching on Type() — the cross-engine contract — misbehave depending on which engine produced the result.

1. Extism: integer results report data.ERROR

engines/extism/evaluator/response.go:53:

case int32, int64, uint32, uint64:
	return data.INT

The Extism decode path (evaluator.go:127-136internal.FixJSONNumberTypesconvertJSONNumber in engines/extism/internal/jsonHelpers.go:13-24) converts JSON integers to plain int, which is absent from the switch. A guest returning a bare JSON number falls to default and reports data.ERROR (and logs Unknown type ... type=int).

Repro (confirmed): guest output 42 → decoded Go intType() == data.ERROR. Existing tests cover only int32/int64/uint32/uint64, which the decode path never produces.

Fix: add case int: (and arguably int8, int16, uint, uint8, uint16) returning data.INT.

2. Risor: nil result reports "null", never data.NONE

engines/risor/evaluator/response.go:46-48:

func (r *execResult) Type() data.Types {
	return data.Types(r.Object.Type())   // Risor's raw type string, passed through
}

Risor v2's nil type string is "null" (pkg/object.NIL), which is outside the data.Types enum (data.NONE == "none"). Starlark and Extism normalize nil to data.NONE; Risor does not.

Repro (confirmed): script nilresp.Type() == data.NONE is false. Other Risor type strings ("byte", "time", "module") similarly leak through as non-enum values.

Fix: map "null"data.NONE and normalize (or deliberately handle) the other non-enum Risor type strings instead of a blind cast.

Impact

Medium — data.Types is part of the public EvaluatorResponse contract and the mechanism for engine-agnostic result handling. Both bugs break it for ordinary values (integers, nil).

Fix

Add the missing int cases to the Extism switch; replace the Risor blind cast with an explicit mapping (at minimum "null"data.NONE). Add per-engine tests for integer and nil results, and a cross-engine test asserting the same conceptual result yields identical Type().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions