Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions test/libyul/YulInterpreterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ YulInterpreterTest::YulInterpreterTest(std::string const& _filename):
m_source = m_reader.source();
m_expectation = m_reader.simpleExpectations();
m_simulateExternalCallsToSelf = m_reader.boolSetting("simulateExternalCall", false);
m_maxCost = m_reader.sizetSetting("maxCost", 0);
}

TestCase::TestResult YulInterpreterTest::run(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted)
Expand All @@ -76,6 +77,7 @@ std::string YulInterpreterTest::interpret(std::shared_ptr<Object const> const& _
state.maxTraceSize = 32;
state.maxSteps = 512;
state.maxExprNesting = 64;
state.maxCost = m_maxCost;
try
{
Interpreter::run(
Expand Down
1 change: 1 addition & 0 deletions test/libyul/YulInterpreterTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class YulInterpreterTest: public solidity::frontend::test::EVMVersionRestrictedT
std::string interpret(std::shared_ptr<Object const> const& _object);

bool m_simulateExternalCallsToSelf = false;
size_t m_maxCost = 0;
};

}
13 changes: 13 additions & 0 deletions test/libyul/yulInterpreterTests/cost_limit_not_exceeded.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
// Simple arithmetic with a generous maxCost should complete normally
// and not trigger InstructionLimitReached.
mstore(0, add(mul(3, 7), 1))
}
// ====
// maxCost: 10000
// ----
// Trace:
// Memory dump:
// 0: 0000000000000000000000000000000000000000000000000000000000000016
// Storage dump:
// Transient storage dump:
14 changes: 14 additions & 0 deletions test/libyul/yulInterpreterTests/instruction_limit_reached_exp.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// exp(2, not(0)) charges 1 + 256 = 257 cost units (1 + bits in exponent).
// With maxCost: 100, this must trigger InstructionLimitReached.
let x := exp(2, not(0))
mstore(0, x)
}
// ====
// maxCost: 100
// ----
// Trace:
// Instruction cost limit reached.
// Memory dump:
// Storage dump:
// Transient storage dump:
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// keccak256 charges 50 cost units per call (plus 1 for evalBuiltin base).
// Two calls = ~102 cost. With maxCost: 60, the second call triggers InstructionLimitReached.
let a := keccak256(0, 0)
let b := keccak256(0, 0)
mstore(0, xor(a, b))
}
// ====
// maxCost: 60
// ----
// Trace:
// Instruction cost limit reached.
// Memory dump:
// Storage dump:
// Transient storage dump:
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// Write distinct bytes on either side of the u256 wraparound boundary
mstore8(sub(0, 1), 0xaa) // byte at 0xfff...fff
mstore8(0, 0xbb) // byte at 0
// mload at sub(0,1) reads 32 bytes wrapping around zero:
// byte 0: memory[0xfff...fff] = 0xaa
// byte 1: memory[0] = 0xbb
// bytes 2-31: 0
sstore(0, mload(sub(0, 1)))
}
// ----
// Trace:
// Memory dump:
// 0: bb00000000000000000000000000000000000000000000000000000000000000
// FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0: 00000000000000000000000000000000000000000000000000000000000000aa
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: aabb000000000000000000000000000000000000000000000000000000000000
// Transient storage dump:
14 changes: 14 additions & 0 deletions test/libyul/yulInterpreterTests/large_memory_mstore8_wrap.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// mstore8 at max u256 address: single byte write, no range wrapping
mstore8(sub(0, 1), 0xab)
// mload at same address: reads 32 bytes wrapping around zero
// byte 0: memory[0xfff...fff] = 0xab, bytes 1-31: memory[0..30] = 0
sstore(0, mload(sub(0, 1)))
}
// ----
// Trace:
// Memory dump:
// FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0: 00000000000000000000000000000000000000000000000000000000000000ab
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: ab00000000000000000000000000000000000000000000000000000000000000
// Transient storage dump:
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// First mstore writes 0x11 at addresses 0..31
mstore(0, 0x1111111111111111111111111111111111111111111111111111111111111111)
// Second mstore at sub(0,1) writes 0x22 at addresses 0xfff...fff, 0, 1, ..., 30
// This overwrites bytes 0..30 with 0x22, leaving byte 31 as 0x11
mstore(sub(0, 1), 0x2222222222222222222222222222222222222222222222222222222222222222)
// mload(0) reads bytes 0..31: 31 bytes of 0x22 followed by 0x11
sstore(0, mload(0))
}
// ----
// Trace:
// Memory dump:
// 0: 2222222222222222222222222222222222222222222222222222222222222211
// FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0: 0000000000000000000000000000000000000000000000000000000000000022
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: 2222222222222222222222222222222222222222222222222222222222222211
// Transient storage dump:
13 changes: 13 additions & 0 deletions test/libyul/yulInterpreterTests/large_memory_no_wrap.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
// mstore at sub(0, 32) = 0xfff...ffe0: writes exactly 32 bytes ending at 0xfff...ffff,
// no address wrapping needed
mstore(sub(0, 32), 0xdeadbeef)
sstore(0, mload(sub(0, 32)))
}
// ----
// Trace:
// Memory dump:
// FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0: 00000000000000000000000000000000000000000000000000000000deadbeef
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: 00000000000000000000000000000000000000000000000000000000deadbeef
// Transient storage dump:
21 changes: 21 additions & 0 deletions test/libyul/yulInterpreterTests/storage_adjacent_large_keys.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
// Adjacent slots near max u256 are independent; no wraparound aliasing
sstore(sub(0, 1), 0x1111)
sstore(sub(0, 2), 0x2222)
sstore(sub(0, 3), 0x3333)
// Verify each slot holds its own value
sstore(0, sload(sub(0, 1)))
sstore(1, sload(sub(0, 2)))
sstore(2, sload(sub(0, 3)))
}
// ----
// Trace:
// Memory dump:
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000001111
// 0000000000000000000000000000000000000000000000000000000000000001: 0000000000000000000000000000000000000000000000000000000000002222
// 0000000000000000000000000000000000000000000000000000000000000002: 0000000000000000000000000000000000000000000000000000000000003333
// fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd: 0000000000000000000000000000000000000000000000000000000000003333
// fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe: 0000000000000000000000000000000000000000000000000000000000002222
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff: 0000000000000000000000000000000000000000000000000000000000001111
// Transient storage dump:
12 changes: 12 additions & 0 deletions test/libyul/yulInterpreterTests/storage_large_key.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
// sstore/sload roundtrip at the maximum storage slot (sub(0,1) = 0xfff...fff)
sstore(sub(0, 1), 0xdeadbeef)
sstore(0, sload(sub(0, 1)))
}
// ----
// Trace:
// Memory dump:
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: 00000000000000000000000000000000000000000000000000000000deadbeef
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff: 00000000000000000000000000000000000000000000000000000000deadbeef
// Transient storage dump:
14 changes: 14 additions & 0 deletions test/libyul/yulInterpreterTests/storage_overwrite_large_key.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// Overwriting a large slot: only the last written value persists
sstore(sub(0, 1), 0x1111)
sstore(sub(0, 1), 0x2222)
sstore(sub(0, 1), 0x3333)
sstore(0, sload(sub(0, 1)))
}
// ----
// Trace:
// Memory dump:
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000003333
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff: 0000000000000000000000000000000000000000000000000000000000003333
// Transient storage dump:
10 changes: 10 additions & 0 deletions test/libyul/yulInterpreterTests/storage_sload_uninitialized.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
// sload of a slot that was never written returns 0
sstore(0, sload(0xdeadbeef))
sstore(1, sload(sub(0, 1)))
}
// ----
// Trace:
// Memory dump:
// Storage dump:
// Transient storage dump:
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// Slot 0 and slot sub(0,1) are distinct; writing one does not affect the other
sstore(0, 0xaaaa)
sstore(sub(0, 1), 0xbbbb)
// Verify slot 0 is unchanged after writing to max slot
sstore(1, sload(0))
// Verify max slot is unchanged after writing to slot 0
sstore(2, sload(sub(0, 1)))
}
// ----
// Trace:
// Memory dump:
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: 000000000000000000000000000000000000000000000000000000000000aaaa
// 0000000000000000000000000000000000000000000000000000000000000001: 000000000000000000000000000000000000000000000000000000000000aaaa
// 0000000000000000000000000000000000000000000000000000000000000002: 000000000000000000000000000000000000000000000000000000000000bbbb
// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff: 000000000000000000000000000000000000000000000000000000000000bbbb
// Transient storage dump:
3 changes: 3 additions & 0 deletions test/tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ target_link_libraries(yulrun PRIVATE yulInterpreter libsolc evmasm Boost::boost
add_executable(solfuzzer afl_fuzzer.cpp fuzzer_common.cpp)
target_link_libraries(solfuzzer PRIVATE libsolc evmasm Boost::boost Boost::program_options ${Boost_SYSTEM_LIBRARY})

add_executable(const_opt_runner const_opt_runner.cpp fuzzer_common.cpp)
target_link_libraries(const_opt_runner PRIVATE libsolc evmasm Boost::boost Boost::program_options ${Boost_SYSTEM_LIBRARY})

add_executable(yulopti yulopti.cpp)
target_link_libraries(yulopti PRIVATE solidity Boost::boost Boost::program_options ${Boost_SYSTEM_LIBRARY})

Expand Down
57 changes: 57 additions & 0 deletions test/tools/const_opt_runner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
This file is part of solidity.

solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

/**
* Standalone runner for the constant optimizer fuzzer target.
* Reads a binary input file and runs it through testConstantOptimizer,
* exactly as the ossfuzz target would, so performance can be analyzed
* without the libFuzzer infrastructure.
*
* Usage: const_opt_runner <input-file>
*/

#include <test/tools/fuzzer_common.h>

#include <fstream>
#include <iostream>
#include <string>

int main(int argc, char** argv)
{
if (argc != 2)
{
std::cerr << "Usage: " << argv[0] << " <input-file>" << std::endl;
return 1;
}

std::ifstream file(argv[1], std::ios::binary);
if (!file)
{
std::cerr << "Error: cannot open file '" << argv[1] << "'" << std::endl;
return 1;
}

std::string input{
std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>()
};

FuzzerUtil::testConstantOptimizer(input, /*_quiet=*/false);

return 0;
}
9 changes: 4 additions & 5 deletions test/tools/fuzzer_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ using namespace solidity::frontend;
using namespace solidity::langutil;
using namespace solidity::util;

static auto constexpr s_evmVersions = EVMVersion::allVersions();

void FuzzerUtil::testCompilerJsonInterface(std::string const& _input, bool _optimize, bool _quiet)
{
if (!_quiet)
Expand Down Expand Up @@ -77,13 +75,12 @@ void FuzzerUtil::forceSMT(StringMap& _input)
void FuzzerUtil::testCompiler(
StringMap& _input,
bool _optimize,
unsigned _rand,
EVMVersion const& _evmVersion,
bool _forceSMT,
bool _compileViaYul
)
{
frontend::CompilerStack compiler;
EVMVersion evmVersion = s_evmVersions[_rand % s_evmVersions.size()];
frontend::OptimiserSettings optimiserSettings;
if (_optimize)
optimiserSettings = frontend::OptimiserSettings::standard();
Expand All @@ -109,9 +106,11 @@ void FuzzerUtil::testCompiler(
});
}
compiler.setSources(_input);
compiler.setEVMVersion(evmVersion);
compiler.setEVMVersion(_evmVersion);
compiler.setOptimiserSettings(optimiserSettings);
compiler.setViaIR(_compileViaYul);
// We need to set it to NoMetadata, or we would get UTF-8 issues, which are uninteresting
compiler.setMetadataFormat(frontend::CompilerStack::MetadataFormat::NoMetadata);
try
{
compiler.compile();
Expand Down
3 changes: 2 additions & 1 deletion test/tools/fuzzer_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <map>
#include <string>
#include <liblangutil/EVMVersion.h>

/**
* Functions to be used for fuzz-testing of various components.
Expand All @@ -40,7 +41,7 @@ struct FuzzerUtil
static void testCompiler(
solidity::StringMap& _input,
bool _optimize,
unsigned _rand,
solidity::langutil::EVMVersion const& _evmVersion,
bool _forceSMT,
bool _compileViaYul
);
Expand Down
31 changes: 31 additions & 0 deletions test/tools/ossfuzz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,34 @@ To be consistent and aid better evaluation of the utility of the fuzzing diction

[1]: https://github.com/google/oss-fuzz
[2]: https://github.com/google/oss-fuzz/issues/1114#issuecomment-360660201

## Executables generated

- `yulProto_diff_ossfuzz.cpp`: exe is `yul_diff_ssa_cfg_ossfuzz`. Generates
random YUL via Protobuf, compiles with and without optimisation, compares via YUL
interpreter
- `yulProtoFuzzer.cpp`: exe is`yul_proto_ossfuzz`. Generates random YUL via Protobuf,
runs different optimizer steps, and hopes it crashes
- `strictasm_diff_ossfuzz.cpp`: exe is `strictasm_diff_ossfuzz`. Interprets random characters
as strict assembly code, compiles with and without optimisation, compares via
YUL interpreter
- `strictasm_opt_ossfuzz.cpp`: exe is `strictasm_opt_ossfuzz`. Interprets
random characters as strict assembly code, runs the optimizer, and hopes it
crashes
- `strictasm_assembly_ossfuzz.cpp`: exe is `strictasm_assembly_ossfuzz`.
Interprets random characters as strict assembly code, assembles it, and hopes
it crashes
- `const_opt_ossfuzz.cpp`: exe is `const_opt_ossfuzz`. Interprets random characters as some kind
of constants, runs the constant optimizer, and hopes it crashes
- `solProtoFuzzer.cpp` exe is `sol_proto_ossfuzz`. Generates random Solidity via Protobuf.
This is actually generating pre-determined code that returns known constants.
Runs via evmone (in-memory), asserts that it does not revert, test() function must return 0,
i.e. all constants must be returned correctly.
- `solc_ossfuzz.cpp`. exe is`solc_ossfuzz`. Interprets random characters as Solidity test case,
compiles and hopes it crashes. NOTE: does not work well it seems
- `solc_ossfuzz.cpp`: exe is `solc_mutator_ossfuzz`. Same as above, but with mutator included.
I sincerely think this mutator thing is junk.
- `StackReuseCodegenFuzzer.cpp`: exe is `stack_reuse_codegen_ossfuzz`. Generates random Yul via
Protobuf, compiles with and without stack-reuse optimisation (i.e. `optimizeStackAllocation`),
executes both via evmone, and asserts that the resulting EVM state is identical. The goal is to
catch miscompilations introduced by the stack-reuse code-generation pass.
4 changes: 2 additions & 2 deletions test/tools/ossfuzz/StackReuseCodegenFuzzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ DEFINE_PROTO_FUZZER(Program const& _input)
if (_input.has_obj())
return;
bool filterStatefulInstructions = true;
bool filterUnboundedLoops = true;
bool filterOptimizationNoise = false;
ProtoConverter converter(
filterStatefulInstructions,
filterUnboundedLoops
filterOptimizationNoise
);
std::string yul_source = converter.programToString(_input);
// Do not fuzz the EVM Version field.
Expand Down
Loading