Skip to content

Commit 11214b1

Browse files
committed
Yul: dump source/target stacks when findStackTooDeep aborts
Add an opt-in debug dump for the case described in #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.
1 parent 38bd31b commit 11214b1

1 file changed

Lines changed: 57 additions & 31 deletions

File tree

libyul/backends/evm/StackLayoutGenerator.cpp

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
#include <range/v3/view/take_last.hpp>
4343
#include <range/v3/view/transform.hpp>
4444

45+
#include <cstdlib>
46+
#include <iostream>
47+
4548
using namespace solidity;
4649
using namespace solidity::yul;
4750

@@ -115,9 +118,13 @@ StackLayoutGenerator::StackLayoutGenerator(
115118
namespace
116119
{
117120
/// @returns all stack too deep errors that would occur when shuffling @a _source to @a _target.
121+
/// If the underlying shuffler aborts (e.g. "Could not create stack layout after 1000 iterations"),
122+
/// the source and target stacks are printed to stderr when the environment variable
123+
/// YUL_DEBUG_DUMP_SHUFFLE_FAILURE is set, then the exception is rethrown unchanged.
118124
std::vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(
119125
Stack const& _source,
120126
Stack const& _target,
127+
Dialect const& _dialect,
121128
size_t _reachableStackDepth
122129
)
123130
{
@@ -131,33 +138,52 @@ std::vector<StackLayoutGenerator::StackTooDeep> findStackTooDeep(
131138
result.push_back(variableSlot->variable.get().name);
132139
return result;
133140
};
134-
::createStackLayout(
135-
currentStack,
136-
_target,
137-
[&](unsigned _i)
138-
{
139-
if (_i > _reachableStackDepth)
140-
stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{
141-
_i - _reachableStackDepth,
142-
getVariableChoices(currentStack | ranges::views::take_last(_i + 1))
143-
});
144-
},
145-
[&](StackSlot const& _slot)
146-
{
147-
if (canBeFreelyGenerated(_slot))
148-
return;
149-
if (
150-
auto depth = util::findOffset(currentStack | ranges::views::reverse, _slot);
151-
depth && *depth >= _reachableStackDepth
152-
)
153-
stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{
154-
*depth - (_reachableStackDepth - 1),
155-
getVariableChoices(currentStack | ranges::views::take_last(*depth + 1))
156-
});
157-
},
158-
[&]() {},
159-
_reachableStackDepth
160-
);
141+
try
142+
{
143+
::createStackLayout(
144+
currentStack,
145+
_target,
146+
[&](unsigned _i)
147+
{
148+
if (_i > _reachableStackDepth)
149+
stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{
150+
_i - _reachableStackDepth,
151+
getVariableChoices(currentStack | ranges::views::take_last(_i + 1))
152+
});
153+
},
154+
[&](StackSlot const& _slot)
155+
{
156+
if (canBeFreelyGenerated(_slot))
157+
return;
158+
if (
159+
auto depth = util::findOffset(currentStack | ranges::views::reverse, _slot);
160+
depth && *depth >= _reachableStackDepth
161+
)
162+
stackTooDeepErrors.emplace_back(StackLayoutGenerator::StackTooDeep{
163+
*depth - (_reachableStackDepth - 1),
164+
getVariableChoices(currentStack | ranges::views::take_last(*depth + 1))
165+
});
166+
},
167+
[&]() {},
168+
_reachableStackDepth
169+
);
170+
}
171+
catch (YulAssertion const&)
172+
{
173+
// Emit a self-contained snippet in the StackShufflingTest `.stack` format
174+
// (see test/libyul/yulStackShuffling/*.stack) so the failing input can be
175+
// dropped into the test suite verbatim.
176+
if (std::getenv("YUL_DEBUG_DUMP_SHUFFLE_FAILURE"))
177+
std::cerr
178+
<< "// findStackTooDeep aborted"
179+
<< " (source.size=" << _source.size()
180+
<< ", target.size=" << _target.size() << ")\n"
181+
<< stackToString(_source, _dialect) << "\n"
182+
<< stackToString(_target, _dialect) << "\n"
183+
<< "// ====\n"
184+
<< "// maximumStackDepth: " << _reachableStackDepth << std::endl;
185+
throw;
186+
}
161187
return stackTooDeepErrors;
162188
}
163189

@@ -348,7 +374,7 @@ Stack StackLayoutGenerator::propagateStackThroughBlock(Stack _exitStack, CFG::Ba
348374
for (auto&& [idx, operation]: _block.operations | ranges::views::enumerate | ranges::views::reverse)
349375
{
350376
Stack newStack = propagateStackThroughOperation(stack, operation, _aggressiveStackCompression);
351-
if (!_aggressiveStackCompression && !findStackTooDeep(newStack, stack, reachableStackDepth()).empty())
377+
if (!_aggressiveStackCompression && !findStackTooDeep(newStack, stack, m_evmDialect, reachableStackDepth()).empty())
352378
// If we had stack errors, run again with aggressive stack compression.
353379
return propagateStackThroughBlock(std::move(_exitStack), _block, true);
354380
stack = std::move(newStack);
@@ -671,7 +697,7 @@ std::vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStac
671697
{
672698
Stack& operationEntry = m_layout.operationEntryLayout.at(&operation);
673699

674-
stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry, reachableStackDepth());
700+
stackTooDeepErrors += findStackTooDeep(currentStack, operationEntry, m_evmDialect, reachableStackDepth());
675701
currentStack = operationEntry;
676702
for (size_t i = 0; i < operation.input.size(); i++)
677703
currentStack.pop_back();
@@ -685,7 +711,7 @@ std::vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStac
685711
[&](CFG::BasicBlock::Jump const& _jump)
686712
{
687713
Stack const& targetLayout = m_layout.blockInfos.at(_jump.target).entryLayout;
688-
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, reachableStackDepth());
714+
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, m_evmDialect, reachableStackDepth());
689715

690716
if (!_jump.backwards)
691717
_addChild(_jump.target);
@@ -696,7 +722,7 @@ std::vector<StackLayoutGenerator::StackTooDeep> StackLayoutGenerator::reportStac
696722
m_layout.blockInfos.at(_conditionalJump.zero).entryLayout,
697723
m_layout.blockInfos.at(_conditionalJump.nonZero).entryLayout
698724
})
699-
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, reachableStackDepth());
725+
stackTooDeepErrors += findStackTooDeep(currentStack, targetLayout, m_evmDialect, reachableStackDepth());
700726

701727
_addChild(_conditionalJump.zero);
702728
_addChild(_conditionalJump.nonZero);

0 commit comments

Comments
 (0)