From 11214b1e7a0103d83ed730a013d2b530bfebe11e Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Tue, 12 May 2026 11:49:56 +0200 Subject: [PATCH] Yul: dump source/target stacks when findStackTooDeep aborts Add an opt-in debug dump for the case described in argotorg/solidity#16704: when `findStackTooDeep` propagates a `YulAssertion` from `createStackLayout` (e.g. "Could not create stack layout after 1000 iterations"), the source and target stacks at that point are otherwise opaque to anyone debugging from a user-supplied .sol reproducer. When the environment variable `YUL_DEBUG_DUMP_SHUFFLE_FAILURE` is set to a non-empty value, the source and target stacks (rendered via the existing `stackToString(Stack, Dialect)` helper) are printed to stderr in the StackShufflingTest `.stack` format (see test/libyul/yulStackShuffling/*.stack), so the failing input can be dropped into the test suite verbatim. Example: // findStackTooDeep aborted (source.size=1, target.size=1237) [ RET ] [ RET RET[fun_double_27] RET[fun_double_27] ... ] // ==== // maximumStackDepth: 16 Mechanically: * `findStackTooDeep` gains a `Dialect const&` parameter (needed by `stackToString` for resolving function names in `RET[...]` / `TMP[...]` slots). All four call sites already have `m_evmDialect` available and pass it through. * The `::createStackLayout` invocation is wrapped in a `try` / `catch (YulAssertion const&)` that does the conditional stderr print and then `throw;`s -- so when the env var is unset, behavior is byte-identical to before. No Changelog entry: this is a developer-only debug aid and doesn't change any user-visible behavior. --- libyul/backends/evm/StackLayoutGenerator.cpp | 88 +++++++++++++------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/libyul/backends/evm/StackLayoutGenerator.cpp b/libyul/backends/evm/StackLayoutGenerator.cpp index 9a37adeb25bb..9fc00930614c 100644 --- a/libyul/backends/evm/StackLayoutGenerator.cpp +++ b/libyul/backends/evm/StackLayoutGenerator.cpp @@ -42,6 +42,9 @@ #include #include +#include +#include + using namespace solidity; using namespace solidity::yul; @@ -115,9 +118,13 @@ StackLayoutGenerator::StackLayoutGenerator( namespace { /// @returns all stack too deep errors that would occur when shuffling @a _source to @a _target. +/// If the underlying shuffler aborts (e.g. "Could not create stack layout after 1000 iterations"), +/// the source and target stacks are printed to stderr when the environment variable +/// YUL_DEBUG_DUMP_SHUFFLE_FAILURE is set, then the exception is rethrown unchanged. std::vector findStackTooDeep( Stack const& _source, Stack const& _target, + Dialect const& _dialect, size_t _reachableStackDepth ) { @@ -131,33 +138,52 @@ std::vector findStackTooDeep( result.push_back(variableSlot->variable.get().name); return result; }; - ::createStackLayout( - currentStack, - _target, - [&](unsigned _i) - { - if (_i > _reachableStackDepth) - stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ - _i - _reachableStackDepth, - getVariableChoices(currentStack | ranges::views::take_last(_i + 1)) - }); - }, - [&](StackSlot const& _slot) - { - if (canBeFreelyGenerated(_slot)) - return; - if ( - auto depth = util::findOffset(currentStack | ranges::views::reverse, _slot); - depth && *depth >= _reachableStackDepth - ) - stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ - *depth - (_reachableStackDepth - 1), - getVariableChoices(currentStack | ranges::views::take_last(*depth + 1)) - }); - }, - [&]() {}, - _reachableStackDepth - ); + try + { + ::createStackLayout( + currentStack, + _target, + [&](unsigned _i) + { + if (_i > _reachableStackDepth) + stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ + _i - _reachableStackDepth, + getVariableChoices(currentStack | ranges::views::take_last(_i + 1)) + }); + }, + [&](StackSlot const& _slot) + { + if (canBeFreelyGenerated(_slot)) + return; + if ( + auto depth = util::findOffset(currentStack | ranges::views::reverse, _slot); + depth && *depth >= _reachableStackDepth + ) + stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{ + *depth - (_reachableStackDepth - 1), + getVariableChoices(currentStack | ranges::views::take_last(*depth + 1)) + }); + }, + [&]() {}, + _reachableStackDepth + ); + } + catch (YulAssertion const&) + { + // Emit a self-contained snippet in the StackShufflingTest `.stack` format + // (see test/libyul/yulStackShuffling/*.stack) so the failing input can be + // dropped into the test suite verbatim. + if (std::getenv("YUL_DEBUG_DUMP_SHUFFLE_FAILURE")) + std::cerr + << "// findStackTooDeep aborted" + << " (source.size=" << _source.size() + << ", target.size=" << _target.size() << ")\n" + << stackToString(_source, _dialect) << "\n" + << stackToString(_target, _dialect) << "\n" + << "// ====\n" + << "// maximumStackDepth: " << _reachableStackDepth << std::endl; + throw; + } return stackTooDeepErrors; } @@ -348,7 +374,7 @@ Stack StackLayoutGenerator::propagateStackThroughBlock(Stack _exitStack, CFG::Ba for (auto&& [idx, operation]: _block.operations | ranges::views::enumerate | ranges::views::reverse) { Stack newStack = propagateStackThroughOperation(stack, operation, _aggressiveStackCompression); - if (!_aggressiveStackCompression && !findStackTooDeep(newStack, stack, reachableStackDepth()).empty()) + if (!_aggressiveStackCompression && !findStackTooDeep(newStack, stack, m_evmDialect, reachableStackDepth()).empty()) // If we had stack errors, run again with aggressive stack compression. return propagateStackThroughBlock(std::move(_exitStack), _block, true); stack = std::move(newStack); @@ -671,7 +697,7 @@ std::vector StackLayoutGenerator::reportStac { Stack& operationEntry = m_layout.operationEntryLayout.at(&operation); - stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry, reachableStackDepth()); + stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry, m_evmDialect, reachableStackDepth()); currentStack = operationEntry; for (size_t i = 0; i < operation.input.size(); i++) currentStack.pop_back(); @@ -685,7 +711,7 @@ std::vector StackLayoutGenerator::reportStac [&](CFG::BasicBlock::Jump const& _jump) { Stack const& targetLayout = m_layout.blockInfos.at(_jump.target).entryLayout; - stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, reachableStackDepth()); + stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, m_evmDialect, reachableStackDepth()); if (!_jump.backwards) _addChild(_jump.target); @@ -696,7 +722,7 @@ std::vector StackLayoutGenerator::reportStac m_layout.blockInfos.at(_conditionalJump.zero).entryLayout, m_layout.blockInfos.at(_conditionalJump.nonZero).entryLayout }) - stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, reachableStackDepth()); + stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, m_evmDialect, reachableStackDepth()); _addChild(_conditionalJump.zero); _addChild(_conditionalJump.nonZero);