Injection: ret and call
Replace the return of the current function; call a firmware function and get its result.
Methods
API 3 changes (0.5.0)
call is awaited in async; timeout= bounds the call (0.5.0). ret does not execute the function body.
Recommended
After ret, continue to caller processing and check consumption, skipped effects and the next input (tests/controlled-input). SRAM readback does not prove consumption. For a buffer: a packet in an experiment-owned buffer, call through Pointer with the exact length, ordering, rejection, re-entry and the next ordinary packet (tests/buffer-consumption). ret checks the caller; call uses an explicit timeout. In the 0.6.0 candidate namespace/static require one symbol; A4.2 supports explicit this and exact signature; A4.3a adds Pointer/Reference with type/SRAM checks. A4.3b adjusts this/Pointer/Reference to an unambiguous non-virtual DWARF base; converting Pointer requires count=1. Compare the subobject address with an independent oracle. Count does not constrain the function body. Check outcome/return_state before return_value; pass logical time as an argument.
t.reach("sample_input")
result = t.ret(1)
t.check("forced", result["outcome"], "forced")
t.reach("after_processing") # Caller must run before checking consumption.
t.check("caller consumed the value", t.read("input_state.consumed"), 1)
value = t.call("app_checksum", 7, timeout=2) # async def: await t.call(...)
t.check("returned", value["outcome"], "returned")
t.check("value available", value["return_state"], "available")
t.check("checksum(7)", value["return_value"], 22)
# Separate tests/logical-time ELF: ticks are not host seconds.
t.reach("ready")
t.call("start", 100, 10, timeout=2)
result = t.call("poll", 110, timeout=2)
t.check("returned", result["outcome"], "returned")
t.check("value available", result["return_state"], "available")
t.check("DONE", result["return_value"], 2)Avoid
A call inside an expression is refused; a call without a timeout is bounded only by the scenario limit. ret does not restore an interrupted call. Software substitution does not establish a physical button press or peripheral failure. Writing a buffer without a call is not consumption; calling a handler does not test DMA or the interrupt.
t.check("DONE", t.call("poll", 110), 2) # dict is not the returned number
t.read("app_checksum(7)") # refused: expression_call_forbidden
t.call("app_wait_forever") # no timeout: only the scenario limit stops it
t.call("app_log", "text") # string argument: unsupported_argument
t.call("demo::overloaded", 1) # ambiguous_function, no overload guessing
t.call("Probe::add", 1) # unsupported_method: explicit this is requiredThe fragments show refused or discouraged patterns, not a working recipe.
Fragments for a scenario body; firmware symbols are illustrative.