Skip to content

get_var_value silently returns wrong values for Var operations (composite / indexed / derived vars) #6629

@masenf

Description

@masenf

Describe the bug

State.get_var_value(var) returns an incorrect value, silently (no error), when var is the result of a Var operation — e.g. an arithmetic/concatenation expression, or an indexed/attribute access on a state var. Instead of evaluating the operation, it returns the value of the operation's first constituent state field.

The root cause is that get_var_value reads the var's state/field_name from var._get_all_var_data(), which recursively merges the var-data of every constituent var. For an operation var, the direct _var_data has no state/field_name, but _get_all_var_data() back-fills them from the first operand. get_var_value then does getattr(state, field_name), returning that operand's value rather than the operation's result.

To Reproduce

import asyncio
import reflex as rx


class State(rx.State):
    a: int = 1
    b: int = 2
    items: list[int] = [10, 20, 30]


async def main():
    s = State(_reflex_internal_init=True)
    print(await s.get_var_value(State.a + State.b))  # -> 1            (expected 3)
    print(await s.get_var_value(State.items[0]))     # -> [10, 20, 30] (expected 10)
    print(await s.get_var_value(State.a))            # -> 1            (correct, control)


asyncio.run(main())

Expected behavior

Either resolve the operation correctly, or raise UnretrievableVarValueError (which it already does for vars with no associated state). Silently returning a plausible-but-wrong value is the dangerous part.

Actual behavior

  • get_var_value(State.a + State.b)1 (the value of a), expected 3.
  • get_var_value(State.items[0])[10, 20, 30] (the whole list), expected 10.

Root cause

reflex/state.py::BaseState.get_var_value (~line 1750):

var_data = var._get_all_var_data()   # <-- recursive merge of ALL constituent var data
if var_data is None or not var_data.state:
    raise UnretrievableVarValueError(...)
...
value = getattr(other_state, var_data.field_name)

_get_all_var_data() recursively combines var-data from all constituent vars, so an operation var inherits the first operand's state/field_name. The guard therefore passes and the wrong field is read. The operation var's direct _var_data, by contrast, correctly has state=None/field_name=None.

Suggested fix

Resolve via the var's direct _var_data (not the recursive _get_all_var_data()): treat the var as a state-field reference only when its direct _var_data carries state + field_name (a plain field/computed reference), or when it is a LiteralVar. Otherwise raise UnretrievableVarValueError. (A fuller fix could actually evaluate resolvable operations, but at minimum it must not silently return a wrong value.)

Specifics

  • Reflex Version: main (0.9.4.post14.dev0); the logic is the same on 0.8.x / 0.9.x
  • Python Version: 3.x
  • OS: Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions