mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9373 from ethereum/develop
Merge develop into breaking.
This commit is contained in:
commit
9ef050af9a
@ -97,6 +97,7 @@ defaults:
|
|||||||
- test/tools/ossfuzz/strictasm_diff_ossfuzz
|
- test/tools/ossfuzz/strictasm_diff_ossfuzz
|
||||||
- test/tools/ossfuzz/strictasm_opt_ossfuzz
|
- test/tools/ossfuzz/strictasm_opt_ossfuzz
|
||||||
- test/tools/ossfuzz/yul_proto_diff_ossfuzz
|
- test/tools/ossfuzz/yul_proto_diff_ossfuzz
|
||||||
|
- test/tools/ossfuzz/yul_proto_diff_custom_mutate_ossfuzz
|
||||||
- test/tools/ossfuzz/yul_proto_ossfuzz
|
- test/tools/ossfuzz/yul_proto_ossfuzz
|
||||||
- test/tools/ossfuzz/sol_proto_ossfuzz
|
- test/tools/ossfuzz/sol_proto_ossfuzz
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ get_logfile_basename() {
|
|||||||
echo -ne "${filename}"
|
echo -ne "${filename}"
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_TEST_ARGS="--color_output=no --show_progress=yes --logger=JUNIT,error,test_results/`get_logfile_basename`.xml"
|
BOOST_TEST_ARGS="--color_output=no --show_progress=yes --logger=JUNIT,error,test_results/`get_logfile_basename`.xml ${BOOST_TEST_ARGS}"
|
||||||
SOLTEST_ARGS="--evm-version=$EVM $SOLTEST_FLAGS"
|
SOLTEST_ARGS="--evm-version=$EVM $SOLTEST_FLAGS"
|
||||||
test "${OPTIMIZE}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --optimize"
|
test "${OPTIMIZE}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --optimize"
|
||||||
test "${ABI_ENCODER_V2}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --abiencoderv2"
|
test "${ABI_ENCODER_V2}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --abiencoderv2"
|
||||||
|
@ -30,7 +30,7 @@ REPODIR="$(realpath $(dirname $0)/..)"
|
|||||||
|
|
||||||
for OPTIMIZE in 0 1; do
|
for OPTIMIZE in 0 1; do
|
||||||
for EVM in homestead byzantium constantinople petersburg istanbul; do
|
for EVM in homestead byzantium constantinople petersburg istanbul; do
|
||||||
EVM=$EVM OPTIMIZE=$OPTIMIZE ${REPODIR}/.circleci/soltest.sh
|
EVM=$EVM OPTIMIZE=$OPTIMIZE BOOST_TEST_ARGS="-t !@nooptions" ${REPODIR}/.circleci/soltest.sh
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -35,7 +35,7 @@ build/
|
|||||||
build*/
|
build*/
|
||||||
emscripten_build/
|
emscripten_build/
|
||||||
docs/_build
|
docs/_build
|
||||||
docs/utils/__pycache__
|
__pycache__
|
||||||
docs/utils/*.pyc
|
docs/utils/*.pyc
|
||||||
/deps/downloads/
|
/deps/downloads/
|
||||||
deps/install
|
deps/install
|
||||||
|
@ -26,7 +26,14 @@ Bugfixes:
|
|||||||
|
|
||||||
### 0.6.12 (unreleased)
|
### 0.6.12 (unreleased)
|
||||||
|
|
||||||
|
Compiler Features:
|
||||||
|
* Code Generator: Evaluate ``keccak256`` of string literals at compile-time.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Type Checker: Fix overload resolution in combination with ``{value: ...}``.
|
||||||
|
|
||||||
|
Compiler Features:
|
||||||
|
* Optimizer: Add rule to remove shifts inside the byte opcode.
|
||||||
|
|
||||||
|
|
||||||
### 0.6.11 (2020-07-07)
|
### 0.6.11 (2020-07-07)
|
||||||
|
@ -571,6 +571,20 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
|||||||
feasibilityFunction
|
feasibilityFunction
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rules.push_back({
|
||||||
|
Builtins::BYTE(A, Builtins::SHL(B, X)),
|
||||||
|
[=]() -> Pattern { return Builtins::BYTE(A.d() + B.d() / 8, X); },
|
||||||
|
false,
|
||||||
|
[=] { return B.d() % 8 == 0 && A.d() <= 32 && B.d() <= 256; }
|
||||||
|
});
|
||||||
|
|
||||||
|
rules.push_back({
|
||||||
|
Builtins::BYTE(A, Builtins::SHR(B, X)),
|
||||||
|
[=]() -> Pattern { return A.d() < B.d() / 8 ? Word(0) : Builtins::BYTE(A.d() - B.d() / 8, X); },
|
||||||
|
false,
|
||||||
|
[=] { return B.d() % 8 == 0 && A.d() < Pattern::WordSize / 8 && B.d() <= Pattern::WordSize; }
|
||||||
|
});
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2404,6 +2404,8 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
|
|||||||
{
|
{
|
||||||
solAssert(_functionCallOptions.options().size() == _functionCallOptions.names().size(), "Lengths of name & value arrays differ!");
|
solAssert(_functionCallOptions.options().size() == _functionCallOptions.names().size(), "Lengths of name & value arrays differ!");
|
||||||
|
|
||||||
|
_functionCallOptions.expression().annotation().arguments = _functionCallOptions.annotation().arguments;
|
||||||
|
|
||||||
_functionCallOptions.expression().accept(*this);
|
_functionCallOptions.expression().accept(*this);
|
||||||
|
|
||||||
auto expressionFunctionType = dynamic_cast<FunctionType const*>(type(_functionCallOptions.expression()));
|
auto expressionFunctionType = dynamic_cast<FunctionType const*>(type(_functionCallOptions.expression()));
|
||||||
|
@ -935,11 +935,6 @@ void ArrayUtils::clearStorageLoop(TypePointer _type) const
|
|||||||
}
|
}
|
||||||
// stack: end_pos pos
|
// stack: end_pos pos
|
||||||
|
|
||||||
// jump to and return from the loop to allow for duplicate code removal
|
|
||||||
evmasm::AssemblyItem returnTag = _context.pushNewTag();
|
|
||||||
_context << Instruction::SWAP2 << Instruction::SWAP1;
|
|
||||||
|
|
||||||
// stack: <return tag> end_pos pos
|
|
||||||
evmasm::AssemblyItem loopStart = _context.appendJumpToNew();
|
evmasm::AssemblyItem loopStart = _context.appendJumpToNew();
|
||||||
_context << loopStart;
|
_context << loopStart;
|
||||||
// check for loop condition
|
// check for loop condition
|
||||||
@ -959,11 +954,8 @@ void ArrayUtils::clearStorageLoop(TypePointer _type) const
|
|||||||
_context.appendJumpTo(loopStart);
|
_context.appendJumpTo(loopStart);
|
||||||
// cleanup
|
// cleanup
|
||||||
_context << zeroLoopEnd;
|
_context << zeroLoopEnd;
|
||||||
_context << Instruction::POP << Instruction::SWAP1;
|
_context << Instruction::POP;
|
||||||
// "return"
|
|
||||||
_context << Instruction::JUMP;
|
|
||||||
|
|
||||||
_context << returnTag;
|
|
||||||
solAssert(_context.stackHeight() == stackHeightStart - 1, "");
|
solAssert(_context.stackHeight() == stackHeightStart - 1, "");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -792,20 +792,24 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
TypePointer const& argType = arguments.front()->annotation().type;
|
TypePointer const& argType = arguments.front()->annotation().type;
|
||||||
solAssert(argType, "");
|
solAssert(argType, "");
|
||||||
arguments.front()->accept(*this);
|
arguments.front()->accept(*this);
|
||||||
|
if (auto const* stringLiteral = dynamic_cast<StringLiteralType const*>(argType))
|
||||||
|
// Optimization: Compute keccak256 on string literals at compile-time.
|
||||||
|
m_context << u256(keccak256(stringLiteral->value()));
|
||||||
|
else if (*argType == *TypeProvider::bytesMemory() || *argType == *TypeProvider::stringMemory())
|
||||||
|
{
|
||||||
// Optimization: If type is bytes or string, then do not encode,
|
// Optimization: If type is bytes or string, then do not encode,
|
||||||
// but directly compute keccak256 on memory.
|
// but directly compute keccak256 on memory.
|
||||||
if (*argType == *TypeProvider::bytesMemory() || *argType == *TypeProvider::stringMemory())
|
|
||||||
{
|
|
||||||
ArrayUtils(m_context).retrieveLength(*TypeProvider::bytesMemory());
|
ArrayUtils(m_context).retrieveLength(*TypeProvider::bytesMemory());
|
||||||
m_context << Instruction::SWAP1 << u256(0x20) << Instruction::ADD;
|
m_context << Instruction::SWAP1 << u256(0x20) << Instruction::ADD;
|
||||||
|
m_context << Instruction::KECCAK256;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
utils().fetchFreeMemoryPointer();
|
utils().fetchFreeMemoryPointer();
|
||||||
utils().packedEncode({argType}, TypePointers());
|
utils().packedEncode({argType}, TypePointers());
|
||||||
utils().toSizeAfterFreeMemoryPointer();
|
utils().toSizeAfterFreeMemoryPointer();
|
||||||
}
|
|
||||||
m_context << Instruction::KECCAK256;
|
m_context << Instruction::KECCAK256;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FunctionType::Kind::Log0:
|
case FunctionType::Kind::Log0:
|
||||||
|
@ -1020,6 +1020,15 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
|
|
||||||
ArrayType const* arrayType = TypeProvider::bytesMemory();
|
ArrayType const* arrayType = TypeProvider::bytesMemory();
|
||||||
|
|
||||||
|
if (auto const* stringLiteral = dynamic_cast<StringLiteralType const*>(arguments.front()->annotation().type))
|
||||||
|
{
|
||||||
|
// Optimization: Compute keccak256 on string literals at compile-time.
|
||||||
|
define(_functionCall) <<
|
||||||
|
("0x" + keccak256(stringLiteral->value()).hex()) <<
|
||||||
|
"\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
auto array = convert(*arguments[0], *arrayType);
|
auto array = convert(*arguments[0], *arrayType);
|
||||||
|
|
||||||
define(_functionCall) <<
|
define(_functionCall) <<
|
||||||
@ -1032,6 +1041,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
"(" <<
|
"(" <<
|
||||||
array.commaSeparatedList() <<
|
array.commaSeparatedList() <<
|
||||||
"))\n";
|
"))\n";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case FunctionType::Kind::ArrayPop:
|
case FunctionType::Kind::ArrayPop:
|
||||||
|
@ -10,6 +10,7 @@ git fetch origin
|
|||||||
error=0
|
error=0
|
||||||
for new_proof in $(git diff origin/develop --name-only test/formal/)
|
for new_proof in $(git diff origin/develop --name-only test/formal/)
|
||||||
do
|
do
|
||||||
|
if [ -e "$new_proof" ]; then
|
||||||
set +e
|
set +e
|
||||||
echo "Proving $new_proof..."
|
echo "Proving $new_proof..."
|
||||||
output=$(python3 "$new_proof")
|
output=$(python3 "$new_proof")
|
||||||
@ -21,6 +22,7 @@ do
|
|||||||
echo "Proof $(basename "$new_proof" ".py") failed: $output."
|
echo "Proof $(basename "$new_proof" ".py") failed: $output."
|
||||||
error=1
|
error=1
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "error" -eq 0 ]]
|
if [[ "error" -eq 0 ]]
|
||||||
|
29
test/formal/combine_byte_shl.py
Normal file
29
test/formal/combine_byte_shl.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from rule import Rule
|
||||||
|
from opcodes import *
|
||||||
|
|
||||||
|
"""
|
||||||
|
byte(A, shl(B, X))
|
||||||
|
given B % 8 == 0 && A <= 32 && B <= 256
|
||||||
|
->
|
||||||
|
byte(A + B / 8, X)
|
||||||
|
"""
|
||||||
|
|
||||||
|
rule = Rule()
|
||||||
|
|
||||||
|
n_bits = 256
|
||||||
|
|
||||||
|
# Input vars
|
||||||
|
X = BitVec('X', n_bits)
|
||||||
|
A = BitVec('A', n_bits)
|
||||||
|
B = BitVec('B', n_bits)
|
||||||
|
|
||||||
|
# Non optimized result
|
||||||
|
nonopt = BYTE(A, SHL(B, X))
|
||||||
|
# Optimized result
|
||||||
|
opt = BYTE(A + B / 8, X)
|
||||||
|
|
||||||
|
rule.require(B % 8 == 0)
|
||||||
|
rule.require(ULE(A, 32))
|
||||||
|
rule.require(ULE(B, 256))
|
||||||
|
|
||||||
|
rule.check(nonopt, opt)
|
30
test/formal/combine_byte_shr_1.py
Normal file
30
test/formal/combine_byte_shr_1.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from rule import Rule
|
||||||
|
from opcodes import *
|
||||||
|
|
||||||
|
"""
|
||||||
|
byte(A, shr(B, X))
|
||||||
|
given B % 8 == 0 && A < n_bits/8 && B <= n_bits && A >= B / 8
|
||||||
|
->
|
||||||
|
byte(A - B / 8, X)
|
||||||
|
"""
|
||||||
|
|
||||||
|
rule = Rule()
|
||||||
|
|
||||||
|
n_bits = 256
|
||||||
|
|
||||||
|
# Input vars
|
||||||
|
X = BitVec('X', n_bits)
|
||||||
|
A = BitVec('A', n_bits)
|
||||||
|
B = BitVec('B', n_bits)
|
||||||
|
|
||||||
|
# Non optimized result
|
||||||
|
nonopt = BYTE(A, SHR(B, X))
|
||||||
|
# Optimized result
|
||||||
|
opt = BYTE(A - B / 8, X)
|
||||||
|
|
||||||
|
rule.require(B % 8 == 0)
|
||||||
|
rule.require(ULT(A, n_bits/8))
|
||||||
|
rule.require(ULE(B, n_bits))
|
||||||
|
rule.require(UGE(A, DIV(B,8)))
|
||||||
|
|
||||||
|
rule.check(nonopt, opt)
|
30
test/formal/combine_byte_shr_2.py
Normal file
30
test/formal/combine_byte_shr_2.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from rule import Rule
|
||||||
|
from opcodes import *
|
||||||
|
|
||||||
|
"""
|
||||||
|
byte(A, shr(B, X))
|
||||||
|
given B % 8 == 0 && A < n_bits/8 && B <= n_bits && A < B / 8
|
||||||
|
->
|
||||||
|
0
|
||||||
|
"""
|
||||||
|
|
||||||
|
rule = Rule()
|
||||||
|
|
||||||
|
n_bits = 256
|
||||||
|
|
||||||
|
# Input vars
|
||||||
|
X = BitVec('X', n_bits)
|
||||||
|
A = BitVec('A', n_bits)
|
||||||
|
B = BitVec('B', n_bits)
|
||||||
|
|
||||||
|
# Non optimized result
|
||||||
|
nonopt = BYTE(A, SHR(B, X))
|
||||||
|
# Optimized result
|
||||||
|
opt = 0
|
||||||
|
|
||||||
|
rule.require(B % 8 == 0)
|
||||||
|
rule.require(ULT(A, n_bits/8))
|
||||||
|
rule.require(ULE(B, n_bits))
|
||||||
|
rule.require(ULT(A, DIV(B,8)))
|
||||||
|
|
||||||
|
rule.check(nonopt, opt)
|
@ -56,3 +56,7 @@ def SHR(x, y):
|
|||||||
|
|
||||||
def SAR(x, y):
|
def SAR(x, y):
|
||||||
return y >> x
|
return y >> x
|
||||||
|
|
||||||
|
def BYTE(i, x):
|
||||||
|
bit = (i + 1) * 8
|
||||||
|
return If(UGT(bit, x.size()), BitVecVal(0, x.size()), (LShR(x, (x.size() - bit))) & 0xff)
|
||||||
|
@ -61,18 +61,40 @@ bytes SolidityExecutionFramework::multiSourceCompileContract(
|
|||||||
evmasm::LinkerObject obj;
|
evmasm::LinkerObject obj;
|
||||||
if (m_compileViaYul)
|
if (m_compileViaYul)
|
||||||
{
|
{
|
||||||
|
// Try compiling twice: If the first run fails due to stack errors, forcefully enable
|
||||||
|
// the optimizer.
|
||||||
|
for (bool forceEnableOptimizer: {false, true})
|
||||||
|
{
|
||||||
|
OptimiserSettings optimiserSettings = m_optimiserSettings;
|
||||||
|
if (!forceEnableOptimizer && !optimiserSettings.runYulOptimiser)
|
||||||
|
{
|
||||||
|
// Enable some optimizations on the first run
|
||||||
|
optimiserSettings.runYulOptimiser = true;
|
||||||
|
optimiserSettings.yulOptimiserSteps = "uljmul jmul";
|
||||||
|
}
|
||||||
|
else if (forceEnableOptimizer)
|
||||||
|
optimiserSettings = OptimiserSettings::full();
|
||||||
|
|
||||||
yul::AssemblyStack asmStack(
|
yul::AssemblyStack asmStack(
|
||||||
m_evmVersion,
|
m_evmVersion,
|
||||||
yul::AssemblyStack::Language::StrictAssembly,
|
yul::AssemblyStack::Language::StrictAssembly,
|
||||||
// Ignore optimiser settings here because we need Yul optimisation to
|
optimiserSettings
|
||||||
// get code that does not exhaust the stack.
|
|
||||||
OptimiserSettings::full()
|
|
||||||
);
|
);
|
||||||
bool analysisSuccessful = asmStack.parseAndAnalyze("", m_compiler.yulIROptimized(contractName));
|
bool analysisSuccessful = asmStack.parseAndAnalyze("", m_compiler.yulIROptimized(contractName));
|
||||||
solAssert(analysisSuccessful, "Code that passed analysis in CompilerStack can't have errors");
|
solAssert(analysisSuccessful, "Code that passed analysis in CompilerStack can't have errors");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
asmStack.optimize();
|
asmStack.optimize();
|
||||||
obj = std::move(*asmStack.assemble(yul::AssemblyStack::Machine::EVM).bytecode);
|
obj = std::move(*asmStack.assemble(yul::AssemblyStack::Machine::EVM).bytecode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
if (forceEnableOptimizer || optimiserSettings == OptimiserSettings::full())
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
obj = m_compiler.object(contractName);
|
obj = m_compiler.object(contractName);
|
||||||
|
@ -16,6 +16,7 @@ contract C {
|
|||||||
return (x[199], y[203][1], z[170].a[1], z[170].b[99]);
|
return (x[199], y[203][1], z[170].a[1], z[170].b[99]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
// ----
|
// ----
|
||||||
// f() -> "A", 8, 4, "B"
|
// f() -> "A", 8, 4, "B"
|
||||||
|
17
test/libsolidity/semanticTests/array/delete_memory_array.sol
Normal file
17
test/libsolidity/semanticTests/array/delete_memory_array.sol
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
contract C {
|
||||||
|
|
||||||
|
function len() public returns (uint ret) {
|
||||||
|
uint[] memory data = new uint[](2);
|
||||||
|
data[0] = 234;
|
||||||
|
data[1] = 123;
|
||||||
|
delete data;
|
||||||
|
assembly {
|
||||||
|
ret := mload(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// len() -> 0
|
@ -0,0 +1,40 @@
|
|||||||
|
contract C {
|
||||||
|
uint[] data;
|
||||||
|
|
||||||
|
function len() public returns (uint ret) {
|
||||||
|
data.push(234);
|
||||||
|
data.push(123);
|
||||||
|
delete data;
|
||||||
|
assembly {
|
||||||
|
ret := sload(data_slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function val() public returns (uint ret) {
|
||||||
|
assembly {
|
||||||
|
sstore(0, 2)
|
||||||
|
mstore(0, 0)
|
||||||
|
sstore(keccak256(0, 32), 234)
|
||||||
|
sstore(add(keccak256(0, 32), 1), 123)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(data[0] == 234);
|
||||||
|
assert(data[1] == 123);
|
||||||
|
|
||||||
|
delete data;
|
||||||
|
|
||||||
|
uint size = 999;
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
size := sload(0)
|
||||||
|
mstore(0, 0)
|
||||||
|
ret := sload(keccak256(0, 32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// len() -> 0
|
||||||
|
// val() -> 0
|
@ -0,0 +1,14 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint x) external payable returns (uint) { return 1; }
|
||||||
|
function f(uint x, uint y) external payable returns (uint) { return 2; }
|
||||||
|
function call() public payable returns (uint v, uint x, uint y, uint z) {
|
||||||
|
v = this.f{value: 10}(2);
|
||||||
|
x = this.f{gas: 1000}(2, 3);
|
||||||
|
y = this.f{gas: 1000, value: 10}(2, 3);
|
||||||
|
z = this.f{gas: 1000}{value: 10}(2, 3);
|
||||||
|
}
|
||||||
|
receive() external payable {}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// (), 1 ether
|
||||||
|
// call() -> 1, 2, 2, 2
|
44
test/libsolidity/semanticTests/optimizer/shift_bytes.sol
Normal file
44
test/libsolidity/semanticTests/optimizer/shift_bytes.sol
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// This tests the optimizer rule
|
||||||
|
// byte(A, shl(B, X))
|
||||||
|
// ->
|
||||||
|
// byte(A + B / 8, X)
|
||||||
|
// given A <= 32 && B % 8 == 0 && B <= 256
|
||||||
|
//
|
||||||
|
// and the respective rule about shr
|
||||||
|
contract C {
|
||||||
|
function f(uint a) public returns (uint, uint, uint) {
|
||||||
|
uint x = a << (256 - 8);
|
||||||
|
assembly {
|
||||||
|
x := byte(0, x)
|
||||||
|
}
|
||||||
|
uint y = a << 8;
|
||||||
|
assembly {
|
||||||
|
y := byte(30, y)
|
||||||
|
}
|
||||||
|
uint z = a << 16;
|
||||||
|
assembly {
|
||||||
|
z := byte(1, z)
|
||||||
|
}
|
||||||
|
return (x, y, z);
|
||||||
|
}
|
||||||
|
function g(uint a) public returns (uint, uint, uint) {
|
||||||
|
uint x = a >> (256 - 16);
|
||||||
|
assembly {
|
||||||
|
x := byte(31, x)
|
||||||
|
}
|
||||||
|
uint y = a >> 8;
|
||||||
|
assembly {
|
||||||
|
y := byte(4, y)
|
||||||
|
}
|
||||||
|
uint z = a >> 16;
|
||||||
|
assembly {
|
||||||
|
z := byte(7, z)
|
||||||
|
}
|
||||||
|
return (x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f(uint256): 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f -> 0x1f, 0x1f, 3
|
||||||
|
// g(uint256): 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f -> 1, 3, 5
|
@ -0,0 +1,19 @@
|
|||||||
|
// tests compile-time evaluation of keccak256 on literal strings
|
||||||
|
contract C {
|
||||||
|
function short() public pure returns (bool) {
|
||||||
|
bytes32 a = keccak256("abcdefghijklmn");
|
||||||
|
bytes memory s = "abcdefghijklmn";
|
||||||
|
return a == keccak256(s);
|
||||||
|
}
|
||||||
|
bytes32 constant sc = keccak256("abcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmn");
|
||||||
|
function long() public pure returns (bool, bool) {
|
||||||
|
bytes32 a = keccak256("abcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmn");
|
||||||
|
bytes memory s = "abcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmn";
|
||||||
|
return (a == keccak256(s), sc == keccak256(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// short() -> true
|
||||||
|
// long() -> true, true
|
@ -1,9 +0,0 @@
|
|||||||
contract C {
|
|
||||||
function f(uint x) external payable { }
|
|
||||||
function f(uint x, uint y) external payable { }
|
|
||||||
function call() internal {
|
|
||||||
this.f{value: 10}(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ----
|
|
||||||
// TypeError 6675: (148-154): Member "f" not unique after argument-dependent lookup in contract C.
|
|
@ -29,7 +29,7 @@ using namespace std;
|
|||||||
namespace solidity::util::test
|
namespace solidity::util::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(IpfsHash)
|
BOOST_AUTO_TEST_SUITE(IpfsHash, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(test_small)
|
BOOST_AUTO_TEST_CASE(test_small)
|
||||||
{
|
{
|
||||||
|
@ -29,7 +29,7 @@ using namespace std;
|
|||||||
namespace solidity::util::test
|
namespace solidity::util::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(IterateReplacing)
|
BOOST_AUTO_TEST_SUITE(IterateReplacing, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(no_replacement)
|
BOOST_AUTO_TEST_CASE(no_replacement)
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ using namespace std;
|
|||||||
namespace solidity::util::test
|
namespace solidity::util::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(JsonTest)
|
BOOST_AUTO_TEST_SUITE(JsonTest, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(json_pretty_print)
|
BOOST_AUTO_TEST_CASE(json_pretty_print)
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,7 @@ using namespace std;
|
|||||||
namespace solidity::util::test
|
namespace solidity::util::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Keccak256)
|
BOOST_AUTO_TEST_SUITE(Keccak256, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(empty)
|
BOOST_AUTO_TEST_CASE(empty)
|
||||||
{
|
{
|
||||||
|
@ -59,7 +59,7 @@ T valueOf(LazyInit<T> _lazyInit)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(LazyInitTests)
|
BOOST_AUTO_TEST_SUITE(LazyInitTests, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(default_constructed_is_empty)
|
BOOST_AUTO_TEST_CASE(default_constructed_is_empty)
|
||||||
{
|
{
|
||||||
|
@ -33,7 +33,7 @@ using namespace std;
|
|||||||
namespace solidity::util::test
|
namespace solidity::util::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(StringUtils)
|
BOOST_AUTO_TEST_SUITE(StringUtils, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(test_similarity)
|
BOOST_AUTO_TEST_CASE(test_similarity)
|
||||||
{
|
{
|
||||||
|
@ -31,7 +31,7 @@ using namespace std;
|
|||||||
namespace solidity::util::test
|
namespace solidity::util::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(SwarmHash)
|
BOOST_AUTO_TEST_SUITE(SwarmHash, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
string bzzr0HashHex(string const& _input)
|
string bzzr0HashHex(string const& _input)
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ using namespace std;
|
|||||||
namespace solidity::util::test
|
namespace solidity::util::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(UTF8)
|
BOOST_AUTO_TEST_SUITE(UTF8, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ using namespace std;
|
|||||||
namespace solidity::util::test
|
namespace solidity::util::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(WhiskersTest)
|
BOOST_AUTO_TEST_SUITE(WhiskersTest, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(no_templates)
|
BOOST_AUTO_TEST_CASE(no_templates)
|
||||||
{
|
{
|
||||||
|
@ -43,7 +43,7 @@ string assemble(string const& _input)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(StackReuseCodegen)
|
BOOST_AUTO_TEST_SUITE(StackReuseCodegen, *boost::unit_test::label("nooptions"))
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(smoke_test)
|
BOOST_AUTO_TEST_CASE(smoke_test)
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,12 @@ add_dependencies(ossfuzz
|
|||||||
|
|
||||||
if (OSSFUZZ)
|
if (OSSFUZZ)
|
||||||
add_custom_target(ossfuzz_proto)
|
add_custom_target(ossfuzz_proto)
|
||||||
add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz sol_proto_ossfuzz)
|
add_dependencies(ossfuzz_proto
|
||||||
|
sol_proto_ossfuzz
|
||||||
|
yul_proto_ossfuzz
|
||||||
|
yul_proto_diff_ossfuzz
|
||||||
|
yul_proto_diff_custom_mutate_ossfuzz
|
||||||
|
)
|
||||||
|
|
||||||
add_custom_target(ossfuzz_abiv2)
|
add_custom_target(ossfuzz_abiv2)
|
||||||
add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz)
|
add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz)
|
||||||
@ -60,6 +65,22 @@ if (OSSFUZZ)
|
|||||||
)
|
)
|
||||||
set_target_properties(yul_proto_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
|
set_target_properties(yul_proto_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
|
||||||
|
|
||||||
|
add_executable(yul_proto_diff_custom_mutate_ossfuzz
|
||||||
|
yulProto_diff_ossfuzz.cpp
|
||||||
|
yulFuzzerCommon.cpp
|
||||||
|
protoToYul.cpp
|
||||||
|
yulProto.pb.cc
|
||||||
|
protomutators/YulProtoMutator.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(yul_proto_diff_custom_mutate_ossfuzz PRIVATE /usr/include/libprotobuf-mutator)
|
||||||
|
target_link_libraries(yul_proto_diff_custom_mutate_ossfuzz PRIVATE yul
|
||||||
|
yulInterpreter
|
||||||
|
protobuf-mutator-libfuzzer.a
|
||||||
|
protobuf-mutator.a
|
||||||
|
protobuf.a
|
||||||
|
)
|
||||||
|
set_target_properties(yul_proto_diff_custom_mutate_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
|
||||||
|
|
||||||
add_executable(abiv2_proto_ossfuzz
|
add_executable(abiv2_proto_ossfuzz
|
||||||
../../EVMHost.cpp
|
../../EVMHost.cpp
|
||||||
abiV2ProtoFuzzer.cpp
|
abiV2ProtoFuzzer.cpp
|
||||||
|
@ -178,6 +178,14 @@ bool ProtoConverter::functionCallNotPossible(FunctionCall_Returns _type)
|
|||||||
(_type == FunctionCall::MULTIASSIGN && !varDeclAvailable());
|
(_type == FunctionCall::MULTIASSIGN && !varDeclAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned ProtoConverter::numVarsInScope()
|
||||||
|
{
|
||||||
|
if (m_inFunctionDef)
|
||||||
|
return m_currentFuncVars.size();
|
||||||
|
else
|
||||||
|
return m_currentGlobalVars.size();
|
||||||
|
}
|
||||||
|
|
||||||
void ProtoConverter::visit(VarRef const& _x)
|
void ProtoConverter::visit(VarRef const& _x)
|
||||||
{
|
{
|
||||||
if (m_inFunctionDef)
|
if (m_inFunctionDef)
|
||||||
@ -827,18 +835,18 @@ void ProtoConverter::visitFunctionInputParams(FunctionCall const& _x, unsigned _
|
|||||||
case 4:
|
case 4:
|
||||||
visit(_x.in_param4());
|
visit(_x.in_param4());
|
||||||
m_output << ", ";
|
m_output << ", ";
|
||||||
BOOST_FALLTHROUGH;
|
[[fallthrough]];
|
||||||
case 3:
|
case 3:
|
||||||
visit(_x.in_param3());
|
visit(_x.in_param3());
|
||||||
m_output << ", ";
|
m_output << ", ";
|
||||||
BOOST_FALLTHROUGH;
|
[[fallthrough]];
|
||||||
case 2:
|
case 2:
|
||||||
visit(_x.in_param2());
|
visit(_x.in_param2());
|
||||||
m_output << ", ";
|
m_output << ", ";
|
||||||
BOOST_FALLTHROUGH;
|
[[fallthrough]];
|
||||||
case 1:
|
case 1:
|
||||||
visit(_x.in_param1());
|
visit(_x.in_param1());
|
||||||
BOOST_FALLTHROUGH;
|
[[fallthrough]];
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -963,23 +971,43 @@ void ProtoConverter::visit(FunctionCall const& _x)
|
|||||||
"Proto fuzzer: Function call with too many output params encountered."
|
"Proto fuzzer: Function call with too many output params encountered."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Return early if numOutParams > number of available variables
|
||||||
|
if (numOutParams > numVarsInScope())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Copy variables in scope in order to prevent repeated references
|
||||||
|
vector<string> variables;
|
||||||
|
if (m_inFunctionDef)
|
||||||
|
for (auto var: m_currentFuncVars)
|
||||||
|
variables.push_back(*var);
|
||||||
|
else
|
||||||
|
for (auto var: m_currentGlobalVars)
|
||||||
|
variables.push_back(*var);
|
||||||
|
|
||||||
|
auto refVar = [](vector<string>& _var, unsigned _rand, bool _comma = true) -> string
|
||||||
|
{
|
||||||
|
auto index = _rand % _var.size();
|
||||||
|
string ref = _var[index];
|
||||||
|
_var.erase(_var.begin() + index);
|
||||||
|
if (_comma)
|
||||||
|
ref += ", ";
|
||||||
|
return ref;
|
||||||
|
};
|
||||||
|
|
||||||
// Convert LHS of multi assignment
|
// Convert LHS of multi assignment
|
||||||
// We reverse the order of out param visits since the order does not matter.
|
// We reverse the order of out param visits since the order does not matter.
|
||||||
// This helps reduce the size of this switch statement.
|
// This helps reduce the size of this switch statement.
|
||||||
switch (numOutParams)
|
switch (numOutParams)
|
||||||
{
|
{
|
||||||
case 4:
|
case 4:
|
||||||
visit(_x.out_param4());
|
m_output << refVar(variables, _x.out_param4().varnum());
|
||||||
m_output << ", ";
|
[[fallthrough]];
|
||||||
BOOST_FALLTHROUGH;
|
|
||||||
case 3:
|
case 3:
|
||||||
visit(_x.out_param3());
|
m_output << refVar(variables, _x.out_param3().varnum());
|
||||||
m_output << ", ";
|
[[fallthrough]];
|
||||||
BOOST_FALLTHROUGH;
|
|
||||||
case 2:
|
case 2:
|
||||||
visit(_x.out_param2());
|
m_output << refVar(variables, _x.out_param2().varnum());
|
||||||
m_output << ", ";
|
m_output << refVar(variables, _x.out_param1().varnum(), false);
|
||||||
visit(_x.out_param1());
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters.");
|
yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters.");
|
||||||
|
@ -120,6 +120,8 @@ private:
|
|||||||
void closeFunctionScope();
|
void closeFunctionScope();
|
||||||
/// Adds @a _vars to current scope
|
/// Adds @a _vars to current scope
|
||||||
void addVarsToScope(std::vector<std::string> const& _vars);
|
void addVarsToScope(std::vector<std::string> const& _vars);
|
||||||
|
/// @returns number of variables that are in scope
|
||||||
|
unsigned numVarsInScope();
|
||||||
|
|
||||||
std::string createHex(std::string const& _hexBytes);
|
std::string createHex(std::string const& _hexBytes);
|
||||||
|
|
||||||
|
147
test/tools/ossfuzz/protomutators/YulProtoMutator.cpp
Normal file
147
test/tools/ossfuzz/protomutators/YulProtoMutator.cpp
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
#include <test/tools/ossfuzz/protomutators/YulProtoMutator.h>
|
||||||
|
|
||||||
|
#include <libyul/Exceptions.h>
|
||||||
|
|
||||||
|
#include <src/text_format.h>
|
||||||
|
|
||||||
|
using namespace solidity::yul::test::yul_fuzzer;
|
||||||
|
using namespace protobuf_mutator;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
using YPM = YulProtoMutator;
|
||||||
|
|
||||||
|
MutationInfo::MutationInfo(ProtobufMessage const* _message, string const& _info):
|
||||||
|
ScopeGuard([&]{ exitInfo(); }),
|
||||||
|
m_protobufMsg(_message)
|
||||||
|
{
|
||||||
|
writeLine("----------------------------------");
|
||||||
|
writeLine("YULMUTATOR: " + _info);
|
||||||
|
writeLine("Before");
|
||||||
|
writeLine(SaveMessageAsText(*m_protobufMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MutationInfo::exitInfo()
|
||||||
|
{
|
||||||
|
writeLine("After");
|
||||||
|
writeLine(SaveMessageAsText(*m_protobufMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize deterministic PRNG.
|
||||||
|
static YulRandomNumGenerator s_rand(1337);
|
||||||
|
|
||||||
|
/// Add m/sstore(0, variable)
|
||||||
|
static LPMPostProcessor<Block> addStoreToZero(
|
||||||
|
[](Block* _message, unsigned _seed)
|
||||||
|
{
|
||||||
|
if (_seed % YPM::s_highIP == 0)
|
||||||
|
{
|
||||||
|
MutationInfo m{_message, "Added store to zero"};
|
||||||
|
auto storeStmt = new StoreFunc();
|
||||||
|
storeStmt->set_st(YPM::EnumTypeConverter<StoreFunc_Storage>{}.enumFromSeed(s_rand()));
|
||||||
|
storeStmt->set_allocated_loc(YPM::litExpression(0));
|
||||||
|
storeStmt->set_allocated_val(YPM::refExpression(s_rand));
|
||||||
|
auto stmt = _message->add_statements();
|
||||||
|
stmt->set_allocated_storage_func(storeStmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Literal* YPM::intLiteral(unsigned _value)
|
||||||
|
{
|
||||||
|
auto lit = new Literal();
|
||||||
|
lit->set_intval(_value);
|
||||||
|
return lit;
|
||||||
|
}
|
||||||
|
|
||||||
|
VarRef* YPM::varRef(unsigned _seed)
|
||||||
|
{
|
||||||
|
auto varref = new VarRef();
|
||||||
|
varref->set_varnum(_seed);
|
||||||
|
return varref;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression* YPM::refExpression(YulRandomNumGenerator& _rand)
|
||||||
|
{
|
||||||
|
auto refExpr = new Expression();
|
||||||
|
refExpr->set_allocated_varref(varRef(_rand()));
|
||||||
|
return refExpr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression* YPM::litExpression(unsigned _value)
|
||||||
|
{
|
||||||
|
auto lit = intLiteral(_value);
|
||||||
|
auto expr = new Expression();
|
||||||
|
expr->set_allocated_cons(lit);
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T YPM::EnumTypeConverter<T>::validEnum(unsigned _seed)
|
||||||
|
{
|
||||||
|
auto ret = static_cast<T>(_seed % (enumMax() - enumMin() + 1) + enumMin());
|
||||||
|
if constexpr (std::is_same_v<std::decay_t<T>, FunctionCall_Returns>)
|
||||||
|
yulAssert(FunctionCall_Returns_IsValid(ret), "Yul proto mutator: Invalid enum");
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, StoreFunc_Storage>)
|
||||||
|
yulAssert(StoreFunc_Storage_IsValid(ret), "Yul proto mutator: Invalid enum");
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, NullaryOp_NOp>)
|
||||||
|
yulAssert(NullaryOp_NOp_IsValid(ret), "Yul proto mutator: Invalid enum");
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, BinaryOp_BOp>)
|
||||||
|
yulAssert(BinaryOp_BOp_IsValid(ret), "Yul proto mutator: Invalid enum");
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, UnaryOp_UOp>)
|
||||||
|
yulAssert(UnaryOp_UOp_IsValid(ret), "Yul proto mutator: Invalid enum");
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, LowLevelCall_Type>)
|
||||||
|
yulAssert(LowLevelCall_Type_IsValid(ret), "Yul proto mutator: Invalid enum");
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, Create_Type>)
|
||||||
|
yulAssert(Create_Type_IsValid(ret), "Yul proto mutator: Invalid enum");
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, UnaryOpData_UOpData>)
|
||||||
|
yulAssert(UnaryOpData_UOpData_IsValid(ret), "Yul proto mutator: Invalid enum");
|
||||||
|
else
|
||||||
|
static_assert(AlwaysFalse<T>::value, "Yul proto mutator: non-exhaustive visitor.");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
unsigned YPM::EnumTypeConverter<T>::enumMax()
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<std::decay_t<T>, FunctionCall_Returns>)
|
||||||
|
return FunctionCall_Returns_Returns_MAX;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, StoreFunc_Storage>)
|
||||||
|
return StoreFunc_Storage_Storage_MAX;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, NullaryOp_NOp>)
|
||||||
|
return NullaryOp_NOp_NOp_MAX;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, BinaryOp_BOp>)
|
||||||
|
return BinaryOp_BOp_BOp_MAX;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, UnaryOp_UOp>)
|
||||||
|
return UnaryOp_UOp_UOp_MAX;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, LowLevelCall_Type>)
|
||||||
|
return LowLevelCall_Type_Type_MAX;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, Create_Type>)
|
||||||
|
return Create_Type_Type_MAX;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, UnaryOpData_UOpData>)
|
||||||
|
return UnaryOpData_UOpData_UOpData_MAX;
|
||||||
|
else
|
||||||
|
static_assert(AlwaysFalse<T>::value, "Yul proto mutator: non-exhaustive visitor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
unsigned YPM::EnumTypeConverter<T>::enumMin()
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<std::decay_t<T>, FunctionCall_Returns>)
|
||||||
|
return FunctionCall_Returns_Returns_MIN;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, StoreFunc_Storage>)
|
||||||
|
return StoreFunc_Storage_Storage_MIN;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, NullaryOp_NOp>)
|
||||||
|
return NullaryOp_NOp_NOp_MIN;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, BinaryOp_BOp>)
|
||||||
|
return BinaryOp_BOp_BOp_MIN;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, UnaryOp_UOp>)
|
||||||
|
return UnaryOp_UOp_UOp_MIN;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, LowLevelCall_Type>)
|
||||||
|
return LowLevelCall_Type_Type_MIN;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, Create_Type>)
|
||||||
|
return Create_Type_Type_MIN;
|
||||||
|
else if constexpr (std::is_same_v<std::decay_t<T>, UnaryOpData_UOpData>)
|
||||||
|
return UnaryOpData_UOpData_UOpData_MIN;
|
||||||
|
else
|
||||||
|
static_assert(AlwaysFalse<T>::value, "Yul proto mutator: non-exhaustive visitor.");
|
||||||
|
}
|
98
test/tools/ossfuzz/protomutators/YulProtoMutator.h
Normal file
98
test/tools/ossfuzz/protomutators/YulProtoMutator.h
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <test/tools/ossfuzz/yulProto.pb.h>
|
||||||
|
|
||||||
|
#include <libsolutil/Common.h>
|
||||||
|
|
||||||
|
#include <src/libfuzzer/libfuzzer_macro.h>
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace solidity::yul::test::yul_fuzzer
|
||||||
|
{
|
||||||
|
|
||||||
|
using ProtobufMessage = google::protobuf::Message;
|
||||||
|
|
||||||
|
template <typename Proto>
|
||||||
|
using LPMPostProcessor = protobuf_mutator::libfuzzer::PostProcessorRegistration<Proto>;
|
||||||
|
|
||||||
|
class MutationInfo: public ScopeGuard
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MutationInfo(ProtobufMessage const* _message, std::string const& _info);
|
||||||
|
|
||||||
|
static void writeLine(std::string const& _str)
|
||||||
|
{
|
||||||
|
std::cout << _str << std::endl;
|
||||||
|
}
|
||||||
|
void exitInfo();
|
||||||
|
|
||||||
|
ProtobufMessage const* m_protobufMsg;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct YulRandomNumGenerator
|
||||||
|
{
|
||||||
|
using RandomEngine = std::minstd_rand;
|
||||||
|
|
||||||
|
explicit YulRandomNumGenerator(unsigned _seed): m_random(RandomEngine(_seed)) {}
|
||||||
|
|
||||||
|
unsigned operator()()
|
||||||
|
{
|
||||||
|
return m_random();
|
||||||
|
}
|
||||||
|
|
||||||
|
RandomEngine m_random;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct YulProtoMutator
|
||||||
|
{
|
||||||
|
/// @param _value: Value of the integer literal
|
||||||
|
/// @returns an integer literal protobuf message initialized with
|
||||||
|
/// the given value.
|
||||||
|
static Literal* intLiteral(unsigned _value);
|
||||||
|
|
||||||
|
/// @param _seed: Pseudo-random unsigned integer used as index
|
||||||
|
/// of variable to be referenced
|
||||||
|
/// @returns a variable reference protobuf message.
|
||||||
|
static VarRef* varRef(unsigned _seed);
|
||||||
|
|
||||||
|
/// @param _value: value of literal expression
|
||||||
|
/// @returns an expression protobuf message
|
||||||
|
static Expression* litExpression(unsigned _value);
|
||||||
|
|
||||||
|
/// @param _rand: Pseudo-random number generator
|
||||||
|
/// of variable to be referenced
|
||||||
|
/// @returns a variable reference protobuf message
|
||||||
|
static Expression* refExpression(YulRandomNumGenerator& _rand);
|
||||||
|
|
||||||
|
/// Helper type for type matching visitor.
|
||||||
|
template<class T> struct AlwaysFalse: std::false_type {};
|
||||||
|
|
||||||
|
/// Template struct for obtaining a valid enum value of
|
||||||
|
/// template type from a pseudo-random unsigned integer.
|
||||||
|
/// @param _seed: Pseudo-random integer
|
||||||
|
/// @returns Valid enum of enum type T
|
||||||
|
template <typename T>
|
||||||
|
struct EnumTypeConverter
|
||||||
|
{
|
||||||
|
T enumFromSeed(unsigned _seed)
|
||||||
|
{
|
||||||
|
return validEnum(_seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @returns a valid enum of type T from _seed
|
||||||
|
T validEnum(unsigned _seed);
|
||||||
|
/// @returns maximum enum value for enum of type T
|
||||||
|
static unsigned enumMax();
|
||||||
|
/// @returns minimum enum value for enum of type T
|
||||||
|
static unsigned enumMin();
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Modulo for mutations that should occur rarely
|
||||||
|
static constexpr unsigned s_lowIP = 31;
|
||||||
|
/// Modulo for mutations that should occur not too often
|
||||||
|
static constexpr unsigned s_mediumIP = 29;
|
||||||
|
/// Modulo for mutations that should occur often
|
||||||
|
static constexpr unsigned s_highIP = 23;
|
||||||
|
};
|
||||||
|
}
|
@ -121,7 +121,7 @@ protected:
|
|||||||
RandomisingAlgorithm m_algorithm;
|
RandomisingAlgorithm m_algorithm;
|
||||||
};
|
};
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(AlgorithmRunnerTest)
|
BOOST_AUTO_TEST_SUITE(AlgorithmRunnerTest)
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(run_should_call_runNextRound_once_per_round, AlgorithmRunnerFixture)
|
BOOST_FIXTURE_TEST_CASE(run_should_call_runNextRound_once_per_round, AlgorithmRunnerFixture)
|
||||||
|
@ -46,7 +46,7 @@ using namespace solidity::util;
|
|||||||
namespace solidity::phaser::test
|
namespace solidity::phaser::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(ChromosomeTest)
|
BOOST_AUTO_TEST_SUITE(ChromosomeTest)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(constructor_should_convert_from_string_to_optimisation_steps)
|
BOOST_AUTO_TEST_CASE(constructor_should_convert_from_string_to_optimisation_steps)
|
||||||
|
@ -66,7 +66,7 @@ map<string, TestEnum> const StringToTestEnumMap = invertMap(TestEnumToStringMap)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(CommonTest)
|
BOOST_AUTO_TEST_SUITE(CommonTest)
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(readLinesFromFile_should_return_all_lines_from_a_text_file_as_strings_without_newlines, ReadLinesFromFileFixture)
|
BOOST_FIXTURE_TEST_CASE(readLinesFromFile_should_return_all_lines_from_a_text_file_as_strings_without_newlines, ReadLinesFromFileFixture)
|
||||||
|
@ -95,7 +95,7 @@ protected:
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(FitnessMetricsTest)
|
BOOST_AUTO_TEST_SUITE(FitnessMetricsTest)
|
||||||
BOOST_AUTO_TEST_SUITE(ProgramBasedMetricTest)
|
BOOST_AUTO_TEST_SUITE(ProgramBasedMetricTest)
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ protected:
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(GeneticAlgorithmsTest)
|
BOOST_AUTO_TEST_SUITE(GeneticAlgorithmsTest)
|
||||||
BOOST_AUTO_TEST_SUITE(RandomAlgorithmTest)
|
BOOST_AUTO_TEST_SUITE(RandomAlgorithmTest)
|
||||||
|
|
||||||
@ -212,7 +212,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_generate_individuals_in_the_crossove
|
|||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
BOOST_AUTO_TEST_SUITE(ClassicGeneticAlgorithmTest)
|
BOOST_AUTO_TEST_SUITE(ClassicGeneticAlgorithmTest)
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_individuals_with_probability_proportional_to_fitness, ClassicGeneticAlgorithmFixture)
|
// FIXME: This test runs *very* slowly (tens of seconds). Investigate, fix and re-enable.
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_individuals_with_probability_proportional_to_fitness, ClassicGeneticAlgorithmFixture, *boost::unit_test::disabled())
|
||||||
{
|
{
|
||||||
constexpr double relativeTolerance = 0.1;
|
constexpr double relativeTolerance = 0.1;
|
||||||
constexpr size_t populationSize = 1000;
|
constexpr size_t populationSize = 1000;
|
||||||
@ -253,7 +254,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_individuals_with_probability_
|
|||||||
BOOST_TEST(abs(meanSquaredError(newFitness, expectedValue) - variance) < variance * relativeTolerance);
|
BOOST_TEST(abs(meanSquaredError(newFitness, expectedValue) - variance) < variance * relativeTolerance);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_only_individuals_existing_in_the_original_population, ClassicGeneticAlgorithmFixture)
|
// FIXME: This test runs *very* slowly (tens of seconds). Investigate, fix and re-enable.
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_select_only_individuals_existing_in_the_original_population, ClassicGeneticAlgorithmFixture, *boost::unit_test::disabled())
|
||||||
{
|
{
|
||||||
constexpr size_t populationSize = 1000;
|
constexpr size_t populationSize = 1000;
|
||||||
auto population = Population::makeRandom(m_fitnessMetric, populationSize, 1, 10);
|
auto population = Population::makeRandom(m_fitnessMetric, populationSize, 1, 10);
|
||||||
@ -297,7 +299,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_crossover, ClassicGeneticAlgorith
|
|||||||
BOOST_TEST(totalCrossed >= 2);
|
BOOST_TEST(totalCrossed >= 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_mutation, ClassicGeneticAlgorithmFixture)
|
// FIXME: This test runs *very* slowly (tens of seconds). Investigate, fix and re-enable.
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_mutation, ClassicGeneticAlgorithmFixture, *boost::unit_test::disabled())
|
||||||
{
|
{
|
||||||
m_options.mutationChance = 0.6;
|
m_options.mutationChance = 0.6;
|
||||||
ClassicGeneticAlgorithm algorithm(m_options);
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
@ -326,7 +329,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_mutation, ClassicGeneticAlgorithm
|
|||||||
BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance);
|
BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_deletion, ClassicGeneticAlgorithmFixture)
|
// FIXME: This test runs *very* slowly (tens of seconds). Investigate, fix and re-enable.
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_deletion, ClassicGeneticAlgorithmFixture, *boost::unit_test::disabled())
|
||||||
{
|
{
|
||||||
m_options.deletionChance = 0.6;
|
m_options.deletionChance = 0.6;
|
||||||
ClassicGeneticAlgorithm algorithm(m_options);
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
@ -355,7 +359,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_deletion, ClassicGeneticAlgorithm
|
|||||||
BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance);
|
BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_addition, ClassicGeneticAlgorithmFixture)
|
// FIXME: This test runs *very* slowly (tens of seconds). Investigate, fix and re-enable.
|
||||||
|
BOOST_FIXTURE_TEST_CASE(runNextRound_should_do_addition, ClassicGeneticAlgorithmFixture, *boost::unit_test::disabled())
|
||||||
{
|
{
|
||||||
m_options.additionChance = 0.6;
|
m_options.additionChance = 0.6;
|
||||||
ClassicGeneticAlgorithm algorithm(m_options);
|
ClassicGeneticAlgorithm algorithm(m_options);
|
||||||
|
@ -33,7 +33,7 @@ using namespace solidity::util;
|
|||||||
namespace solidity::phaser::test
|
namespace solidity::phaser::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(MutationsTest)
|
BOOST_AUTO_TEST_SUITE(MutationsTest)
|
||||||
BOOST_AUTO_TEST_SUITE(GeneRandomisationTest)
|
BOOST_AUTO_TEST_SUITE(GeneRandomisationTest)
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ using namespace std;
|
|||||||
namespace solidity::phaser::test
|
namespace solidity::phaser::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(PairSelectionsTest)
|
BOOST_AUTO_TEST_SUITE(PairSelectionsTest)
|
||||||
BOOST_AUTO_TEST_SUITE(RandomPairSelectionTest)
|
BOOST_AUTO_TEST_SUITE(RandomPairSelectionTest)
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ protected:
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(PhaserTest)
|
BOOST_AUTO_TEST_SUITE(PhaserTest)
|
||||||
BOOST_AUTO_TEST_SUITE(GeneticAlgorithmFactoryTest)
|
BOOST_AUTO_TEST_SUITE(GeneticAlgorithmFactoryTest)
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ protected:
|
|||||||
shared_ptr<FitnessMetric> m_fitnessMetric = make_shared<ChromosomeLengthMetric>();
|
shared_ptr<FitnessMetric> m_fitnessMetric = make_shared<ChromosomeLengthMetric>();
|
||||||
};
|
};
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(PopulationTest)
|
BOOST_AUTO_TEST_SUITE(PopulationTest)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(isFitter_should_use_fitness_as_the_main_criterion)
|
BOOST_AUTO_TEST_CASE(isFitter_should_use_fitness_as_the_main_criterion)
|
||||||
|
@ -57,7 +57,7 @@ namespace
|
|||||||
namespace solidity::phaser::test
|
namespace solidity::phaser::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(ProgramTest)
|
BOOST_AUTO_TEST_SUITE(ProgramTest)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(copy_constructor_should_make_deep_copy_of_ast)
|
BOOST_AUTO_TEST_CASE(copy_constructor_should_make_deep_copy_of_ast)
|
||||||
|
@ -70,7 +70,7 @@ protected:
|
|||||||
ProgramCache m_programCache{m_program};
|
ProgramCache m_programCache{m_program};
|
||||||
};
|
};
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(ProgramCacheTest)
|
BOOST_AUTO_TEST_SUITE(ProgramCacheTest)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(CacheStats_operator_plus_should_add_stats_together)
|
BOOST_AUTO_TEST_CASE(CacheStats_operator_plus_should_add_stats_together)
|
||||||
|
@ -34,7 +34,7 @@ using namespace solidity::util;
|
|||||||
namespace solidity::phaser::test
|
namespace solidity::phaser::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(SelectionsTest)
|
BOOST_AUTO_TEST_SUITE(SelectionsTest)
|
||||||
BOOST_AUTO_TEST_SUITE(RangeSelectionTest)
|
BOOST_AUTO_TEST_SUITE(RangeSelectionTest)
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ using namespace std;
|
|||||||
namespace solidity::phaser::test
|
namespace solidity::phaser::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(RandomTest)
|
BOOST_AUTO_TEST_SUITE(RandomTest)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(bernoulliTrial_should_produce_samples_with_right_expected_value_and_variance)
|
BOOST_AUTO_TEST_CASE(bernoulliTrial_should_produce_samples_with_right_expected_value_and_variance)
|
||||||
|
@ -34,7 +34,7 @@ namespace fs = boost::filesystem;
|
|||||||
namespace solidity::phaser::test
|
namespace solidity::phaser::test
|
||||||
{
|
{
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(Phaser)
|
BOOST_AUTO_TEST_SUITE(Phaser, *boost::unit_test::label("nooptions"))
|
||||||
BOOST_AUTO_TEST_SUITE(TestHelpersTest)
|
BOOST_AUTO_TEST_SUITE(TestHelpersTest)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(ChromosomeLengthMetric_evaluate_should_return_chromosome_length)
|
BOOST_AUTO_TEST_CASE(ChromosomeLengthMetric_evaluate_should_return_chromosome_length)
|
||||||
|
Loading…
Reference in New Issue
Block a user