argotorg / argotorg/solidity

SSA CFG JSON leaks instruction type to block

Open
#16,821 0 comments 0 reactions 1 assignee Claimed by @blishko View on GitHub
bug :bug: experimental low impact medium effort
Dominant language
C++
Stars
25.7k
Forks
6.2k
Avg merge
2d 19h
Merged PRs (30d)
29

Description

## Description

The SSA CFG JSON exporter (`--yul-cfg-json`) leaks an instruction's kind into the enclosing block's JSON object. In `libyul/backends/evm/ssa/io/JSONExporter.cpp`, the operation serializer is passed the parent block JSON object (`_ret`) and writes `_ret["type"] = "BuiltinCall"` or `_ret["type"] = "FunctionCall"` on that parent instead of writing the kind into the per-instruction JSON.

As a result, only blocks that contain at least one operation gain a bogus top-level `"type": "BuiltinCall"` (or `"FunctionCall"`) field, while operation-free blocks omit it. Consumers cannot treat `type` as a real block schema property.

Relevant code:

```cpp
// libyul/backends/evm/ssa/io/JSONExporter.cpp:46
Json toJson(Json& _ret, SSACFG const& _cfg, SSACFG::Operation const& _operation, ControlFlowGraphs const& _controlFlow)
{
Json opJson = Json::object();
std::visit(solidity::util::GenericVisitor{
[&](SSACFG::Call const& _call) {
_ret["type"] = "FunctionCall";
opJson["op"] = _controlFlow.functionGraph(_call.graphID)->name;
},
[&](SSACFG::BuiltinCall const& _call) {
_ret["type"] = "BuiltinCall";
```

```cpp
// libyul/backends/evm/ssa/io/JSONExporter.cpp:116-117
for (auto const opId: block.operations)
blockJson["instructions"].push_back(toJson(blockJson, _cfg, _cfg.operation(opId), _controlFlow));
```

Expected: the operation kind should be written into the instruction JSON (`opJson["type"]`), not into the enclosing block object. Block-level fields should remain limited to the documented schema (`id`, `exit`, `instructions`, `liveness`).

## Environment

- Compiler version: 0.8.35-develop.2026.5.5+commit.47b9dedd.Linux.g++
- Operating system: Linux Ubuntu Jammy

## Steps to Reproduce

Compile the following Yul input with `--yul-cfg-json`:

```yul
{
let x := calldataload(0)
if x { pop(x) }
}
```

In the resulting JSON, the block containing the `calldataload` operation has a top-level `"type": "BuiltinCall"` field, while operation-free blocks do not. The instruction kind leaks onto the parent block object instead of staying on the instruction JSON.

Reproduction via the verification script:

```bash
$ bash research/scripts/verify_ssa_cfg_block_type_leak.sh \
research/compile_tests/Ebab680_ssa_cfg_block_type_leak.input.yul
OK: SSA CFG JSON leaks instruction kind into parent block type field.
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.