From 3754a86ab203095c880e93aae3a12bf28be1fbcb Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Wed, 8 Apr 2020 17:08:49 -0500 Subject: [PATCH 01/55] Add support for interfaceID. --- Changelog.md | 2 +- docs/units-and-global-variables.rst | 9 +++ libsolidity/analysis/TypeChecker.cpp | 2 + libsolidity/analysis/ViewPureChecker.cpp | 1 + libsolidity/ast/AST.cpp | 16 +++-- libsolidity/ast/AST.h | 6 +- libsolidity/ast/Types.cpp | 4 +- libsolidity/codegen/ExpressionCompiler.cpp | 9 +++ .../codegen/ir/IRGeneratorForStatements.cpp | 13 ++++ .../semanticTests/interfaceID/homer.sol | 36 ++++++++++ .../interfaceID/homer_interfaceId.sol | 36 ++++++++++ .../interfaceID/interfaceId_events.sol | 21 ++++++ .../semanticTests/interfaceID/interfaces.sol | 67 +++++++++++++++++++ .../semanticTests/interfaceID/lisa.sol | 47 +++++++++++++ .../interfaceID/lisa_interfaceId.sol | 47 +++++++++++++ 15 files changed, 304 insertions(+), 12 deletions(-) create mode 100644 test/libsolidity/semanticTests/interfaceID/homer.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/homer_interfaceId.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/interfaceId_events.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/interfaces.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/lisa.sol create mode 100644 test/libsolidity/semanticTests/interfaceID/lisa_interfaceId.sol diff --git a/Changelog.md b/Changelog.md index 2ddb74055..607a0f02b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,11 +4,11 @@ Important Bugfixes: * Fix tuple assignments with components occupying multiple stack slots and different stack size on left- and right-hand-side. Language Features: + * Add support for EIP 165 interface identifiers with `type(I).interfaceId`. Compiler Features: - Bugfixes: * AST export: Export `immutable` property in the field `mutability`. * SMTChecker: Fix internal error in the CHC engine when calling inherited functions internally. diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 6c625e997..caf3bebac 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -310,3 +310,12 @@ available for a contract type ``C``: regular calls. The same restrictions as with ``.creationCode`` also apply for this property. + +In addition to the properties above, the following properties are available +for an interface type ``I``: + +``type(I).interfaceId``: + A ``bytes4`` value containing the `EIP-165 `_ + interface identifier of the given interface ``I``. This identifier is defined as the ``XOR`` of all + function selectors defined within the interface itself - excluding all inherited functions. + diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 2bdc706a2..a252189dd 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2617,6 +2617,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) } else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name") annotation.isPure = true; + else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId") + annotation.isPure = true; } return false; diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 2468b0338..8fe35b1bc 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -355,6 +355,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::MetaType, "creationCode"}, {MagicType::Kind::MetaType, "runtimeCode"}, {MagicType::Kind::MetaType, "name"}, + {MagicType::Kind::MetaType, "interfaceId"}, }; set static const payableMembers{ {MagicType::Kind::Message, "value"} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index d9425937f..740317b15 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -96,9 +96,9 @@ bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const return util::contains(annotation().linearizedBaseContracts, &_base); } -map, FunctionTypePointer> ContractDefinition::interfaceFunctions() const +map, FunctionTypePointer> ContractDefinition::interfaceFunctions(bool _includeInheritedFunctions) const { - auto exportedFunctionList = interfaceFunctionList(); + auto exportedFunctionList = interfaceFunctionList(_includeInheritedFunctions); map, FunctionTypePointer> exportedFunctions; for (auto const& it: exportedFunctionList) @@ -174,14 +174,16 @@ vector const& ContractDefinition::interfaceEvents() cons return *m_interfaceEvents; } -vector, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList() const +vector, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const { - if (!m_interfaceFunctionList) + if (!m_interfaceFunctionList[_includeInheritedFunctions]) { set signaturesSeen; - m_interfaceFunctionList = make_unique, FunctionTypePointer>>>(); + m_interfaceFunctionList[_includeInheritedFunctions] = make_unique, FunctionTypePointer>>>(); for (ContractDefinition const* contract: annotation().linearizedBaseContracts) { + if (_includeInheritedFunctions == false && contract != this) + continue; vector functions; for (FunctionDefinition const* f: contract->definedFunctions()) if (f->isPartOfExternalInterface()) @@ -199,12 +201,12 @@ vector, FunctionTypePointer>> const& ContractDefinition: { signaturesSeen.insert(functionSignature); util::FixedHash<4> hash(util::keccak256(functionSignature)); - m_interfaceFunctionList->emplace_back(hash, fun); + m_interfaceFunctionList[_includeInheritedFunctions]->emplace_back(hash, fun); } } } } - return *m_interfaceFunctionList; + return *m_interfaceFunctionList[_includeInheritedFunctions]; } TypePointer ContractDefinition::type() const diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index c0f7e6dc9..684c86906 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -488,8 +488,8 @@ public: /// @returns a map of canonical function signatures to FunctionDefinitions /// as intended for use by the ABI. - std::map, FunctionTypePointer> interfaceFunctions() const; - std::vector, FunctionTypePointer>> const& interfaceFunctionList() const; + std::map, FunctionTypePointer> interfaceFunctions(bool _includeInheritedFunctions = true) const; + std::vector, FunctionTypePointer>> const& interfaceFunctionList(bool _includeInheritedFunctions = true) const; /// @returns a list of all declarations in this contract std::vector declarations() const { return filteredNodes(m_subNodes); } @@ -528,7 +528,7 @@ private: ContractKind m_contractKind; bool m_abstract{false}; - mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; + mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList[2]; mutable std::unique_ptr> m_interfaceEvents; }; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 2238e86f3..279666b7d 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -3719,7 +3719,9 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const {"name", TypeProvider::stringMemory()}, }); else - return {}; + return MemberList::MemberMap({ + {"interfaceId", TypeProvider::fixedBytes(4)}, + }); } } solAssert(false, "Unknown kind of magic."); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index a02497561..ab4e120c0 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1580,6 +1580,15 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; utils().storeStringData(contract.name()); } + else if (member == "interfaceId") + { + TypePointer arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); + ContractDefinition const& contract = dynamic_cast(*arg).contractDefinition(); + uint64_t result{0}; + for (auto const& function: contract.interfaceFunctionList(false)) + result ^= fromBigEndian(function.first.ref()); + m_context << (u256{result} << (256 - 32)); + } else if ((set{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member)) { // no-op diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 57c8cb152..d14d58753 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -754,6 +754,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } break; } + case FunctionType::Kind::MetaType: + { + break; + } default: solUnimplemented("FunctionKind " + toString(static_cast(functionType->kind())) + " not yet implemented"); } @@ -904,6 +908,15 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { solUnimplementedAssert(false, ""); } + else if (member == "interfaceId") + { + TypePointer arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); + ContractDefinition const& contract = dynamic_cast(*arg).contractDefinition(); + uint64_t result{0}; + for (auto const& function: contract.interfaceFunctionList(false)) + result ^= fromBigEndian(function.first.ref()); + define(_memberAccess) << formatNumber(u256{result} << (256 - 32)) << "\n"; + } else if (set{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member)) { // no-op diff --git a/test/libsolidity/semanticTests/interfaceID/homer.sol b/test/libsolidity/semanticTests/interfaceID/homer.sol new file mode 100644 index 000000000..243cba0b4 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/homer.sol @@ -0,0 +1,36 @@ +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +interface Simpson { + function is2D() external returns (bool); + function skinColor() external returns (string memory); +} + +contract Homer is ERC165, Simpson { + function supportsInterface(bytes4 interfaceID) external view override returns (bool) { + return + interfaceID == this.supportsInterface.selector || // ERC165 + interfaceID == this.is2D.selector ^ this.skinColor.selector; // Simpson + } + + function is2D() external override returns (bool) { + return true; + } + + function skinColor() external override returns (string memory) { + return "yellow"; + } +} + +// ---- +// supportsInterface(bytes4): left(0x01ffc9a0) -> false +// supportsInterface(bytes4): left(0x01ffc9a7) -> true +// supportsInterface(bytes4): left(0x73b6b492) -> true +// supportsInterface(bytes4): left(0x70b6b492) -> false diff --git a/test/libsolidity/semanticTests/interfaceID/homer_interfaceId.sol b/test/libsolidity/semanticTests/interfaceID/homer_interfaceId.sol new file mode 100644 index 000000000..d2fa2e821 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/homer_interfaceId.sol @@ -0,0 +1,36 @@ +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +interface Simpson { + function is2D() external returns (bool); + function skinColor() external returns (string memory); +} + +contract Homer is ERC165, Simpson { + function supportsInterface(bytes4 interfaceID) external view override returns (bool) { + return + interfaceID == type(ERC165).interfaceId || + interfaceID == type(Simpson).interfaceId; + } + + function is2D() external override returns (bool) { + return true; + } + + function skinColor() external override returns (string memory) { + return "yellow"; + } +} + +// ---- +// supportsInterface(bytes4): left(0x01ffc9a0) -> false +// supportsInterface(bytes4): left(0x01ffc9a7) -> true +// supportsInterface(bytes4): left(0x73b6b492) -> true +// supportsInterface(bytes4): left(0x70b6b492) -> false diff --git a/test/libsolidity/semanticTests/interfaceID/interfaceId_events.sol b/test/libsolidity/semanticTests/interfaceID/interfaceId_events.sol new file mode 100644 index 000000000..09cb6c0d4 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/interfaceId_events.sol @@ -0,0 +1,21 @@ +interface HelloWorld { + function hello() external pure; + function world(int) external pure; +} + +interface HelloWorldWithEvent { + event Event(); + function hello() external pure; + function world(int) external pure; +} + +contract Test { + bytes4 public hello_world = type(HelloWorld).interfaceId; + bytes4 public hello_world_with_event = type(HelloWorldWithEvent).interfaceId; +} + +// ==== +// compileViaYul: also +// ---- +// hello_world() -> left(0xc6be8b58) +// hello_world_with_event() -> left(0xc6be8b58) diff --git a/test/libsolidity/semanticTests/interfaceID/interfaces.sol b/test/libsolidity/semanticTests/interfaceID/interfaces.sol new file mode 100644 index 000000000..ba63cfb12 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/interfaces.sol @@ -0,0 +1,67 @@ +interface HelloWorld { + function hello() external pure; + function world(int) external pure; +} + +interface HelloWorldDerived is HelloWorld { + function other() external pure; +} + +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +contract Test { + bytes4 public ghello_world_interfaceId = type(HelloWorld).interfaceId; + bytes4 public ERC165_interfaceId = type(ERC165).interfaceId; + + function hello() public pure returns (bytes4 data){ + HelloWorld i; + return i.hello.selector; + } + + function world() public pure returns (bytes4 data){ + HelloWorld i; + return i.world.selector; + } + + function hello_world() public pure returns (bytes4 data){ + // HelloWorld i; + // return i.hello.selector ^ i.world.selector; // = 0xc6be8b58 + return 0xc6be8b58; + } + + function hello_world_interfaceId() public pure returns (bytes4 data){ + return type(HelloWorld).interfaceId; + } + + function other() public pure returns (bytes4 data){ + HelloWorldDerived i; + return i.other.selector; + } + + function hello_world_derived_interfaceId() public pure returns (bytes4 data){ + return type(HelloWorldDerived).interfaceId; + } +} + +// ==== +// compileViaYul: also +// ---- +// hello() -> left(0x19ff1d21) +// world() -> left(0xdf419679) +// +// ERC165_interfaceId() -> left(0x01ffc9a7) +// +// hello_world() -> left(0xc6be8b58) +// hello_world_interfaceId() -> left(0xc6be8b58) +// ghello_world_interfaceId() -> left(0xc6be8b58) +// +// other() -> left(0x85295877) +// hello_world_derived_interfaceId() -> left(0x85295877) diff --git a/test/libsolidity/semanticTests/interfaceID/lisa.sol b/test/libsolidity/semanticTests/interfaceID/lisa.sol new file mode 100644 index 000000000..ea844dd81 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/lisa.sol @@ -0,0 +1,47 @@ +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +contract ERC165MappingImplementation is ERC165 { + /// @dev You must not set element 0xffffffff to true + mapping(bytes4 => bool) internal supportedInterfaces; + + constructor() internal { + supportedInterfaces[this.supportsInterface.selector] = true; + } + + function supportsInterface(bytes4 interfaceID) external view override returns (bool) { + return supportedInterfaces[interfaceID]; + } +} + +interface Simpson { + function is2D() external returns (bool); + function skinColor() external returns (string memory); +} + +contract Lisa is ERC165MappingImplementation, Simpson { + constructor() public { + supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true; + } + + function is2D() external override returns (bool) { + return true; + } + + function skinColor() external override returns (string memory) { + return "yellow"; + } +} + +// ---- +// supportsInterface(bytes4): left(0x01ffc9a0) -> false +// supportsInterface(bytes4): left(0x01ffc9a7) -> true +// supportsInterface(bytes4): left(0x73b6b492) -> true +// supportsInterface(bytes4): left(0x70b6b492) -> false diff --git a/test/libsolidity/semanticTests/interfaceID/lisa_interfaceId.sol b/test/libsolidity/semanticTests/interfaceID/lisa_interfaceId.sol new file mode 100644 index 000000000..9c9dd5eb7 --- /dev/null +++ b/test/libsolidity/semanticTests/interfaceID/lisa_interfaceId.sol @@ -0,0 +1,47 @@ +interface ERC165 { + /// @notice Query if a contract implements an interface + /// @param interfaceID The interface identifier, as specified in ERC-165 + /// @dev Interface identification is specified in ERC-165. This function + /// uses less than 30,000 gas. + /// @return `true` if the contract implements `interfaceID` and + /// `interfaceID` is not 0xffffffff, `false` otherwise + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} + +contract ERC165MappingImplementation is ERC165 { + /// @dev You must not set element 0xffffffff to true + mapping(bytes4 => bool) internal supportedInterfaces; + + constructor() internal { + supportedInterfaces[this.supportsInterface.selector] = true; + } + + function supportsInterface(bytes4 interfaceID) external view override returns (bool) { + return supportedInterfaces[interfaceID]; + } +} + +interface Simpson { + function is2D() external returns (bool); + function skinColor() external returns (string memory); +} + +contract Lisa is ERC165MappingImplementation, Simpson { + constructor() public { + supportedInterfaces[type(Simpson).interfaceId] = true; + } + + function is2D() external override returns (bool) { + return true; + } + + function skinColor() external override returns (string memory) { + return "yellow"; + } +} + +// ---- +// supportsInterface(bytes4): left(0x01ffc9a0) -> false +// supportsInterface(bytes4): left(0x01ffc9a7) -> true +// supportsInterface(bytes4): left(0x73b6b492) -> true +// supportsInterface(bytes4): left(0x70b6b492) -> false From 1ada2a52fb40a6cd4e8b4890243a419d79083a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 11 Mar 2020 03:49:16 +0100 Subject: [PATCH 02/55] [yul-phaser] Mutations: Add two-point and uniform crossover operators --- test/yulPhaser/Mutations.cpp | 179 +++++++++++++++++++++++++++++++++- tools/yulPhaser/Mutations.cpp | 135 +++++++++++++++++++++++++ tools/yulPhaser/Mutations.h | 18 ++++ 3 files changed, 331 insertions(+), 1 deletion(-) diff --git a/test/yulPhaser/Mutations.cpp b/test/yulPhaser/Mutations.cpp index 33c623f84..27e57be08 100644 --- a/test/yulPhaser/Mutations.cpp +++ b/test/yulPhaser/Mutations.cpp @@ -18,15 +18,17 @@ #include #include - #include +#include + #include #include #include using namespace std; +using namespace solidity::util; namespace solidity::phaser::test { @@ -434,6 +436,181 @@ BOOST_AUTO_TEST_CASE(fixedPointCrossover_should_always_use_position_zero_as_spli BOOST_CHECK(crossover10(empty, splittable) == splittable); } +BOOST_AUTO_TEST_CASE(randomTwoPointCrossover_should_swap_chromosome_parts_between_two_random_points) +{ + function crossover = randomTwoPointCrossover(); + + SimulationRNG::reset(1); + Chromosome result1 = crossover(Chromosome("aaaaaaaaaa"), Chromosome("cccccc")); + BOOST_TEST(result1 == Chromosome("aaacccaaaa")); + + SimulationRNG::reset(1); + Chromosome result2 = crossover(Chromosome("cccccc"), Chromosome("aaaaaaaaaa")); + BOOST_TEST(result2 == Chromosome("cccaaa")); +} + +BOOST_AUTO_TEST_CASE(symmetricRandomTwoPointCrossover_should_swap_chromosome_parts_at_random_point) +{ + function crossover = symmetricRandomTwoPointCrossover(); + + SimulationRNG::reset(1); + tuple result1 = crossover(Chromosome("aaaaaaaaaa"), Chromosome("cccccc")); + tuple expectedPair1 = {Chromosome("aaacccaaaa"), Chromosome("cccaaa")}; + BOOST_TEST(result1 == expectedPair1); + + tuple result2 = crossover(Chromosome("cccccc"), Chromosome("aaaaaaaaaa")); + tuple expectedPair2 = {Chromosome("ccccca"), Chromosome("aaaaacaaaa")}; + BOOST_TEST(result2 == expectedPair2); +} + +BOOST_AUTO_TEST_CASE(randomTwoPointCrossover_should_only_consider_points_available_on_both_chromosomes) +{ + function crossover = randomTwoPointCrossover(); + + for (size_t i = 0; i < 30; ++i) + { + Chromosome result1 = crossover(Chromosome("aaa"), Chromosome("TTTTTTTTTTTTTTTTTTTT")); + Chromosome result2 = crossover(Chromosome("TTTTTTTTTTTTTTTTTTTT"), Chromosome("aaa")); + BOOST_TEST(( + result1 == Chromosome("aaa") || + result1 == Chromosome("Taa") || + result1 == Chromosome("TTa") || + result1 == Chromosome("TTT") || + result1 == Chromosome("aTa") || + result1 == Chromosome("aTT") || + result1 == Chromosome("aaT") + )); + BOOST_TEST(( + result2 == Chromosome("TTTTTTTTTTTTTTTTTTTT") || + result2 == Chromosome("aTTTTTTTTTTTTTTTTTTT") || + result2 == Chromosome("aaTTTTTTTTTTTTTTTTTT") || + result2 == Chromosome("aaaTTTTTTTTTTTTTTTTT") || + result2 == Chromosome("TaTTTTTTTTTTTTTTTTTT") || + result2 == Chromosome("TaaTTTTTTTTTTTTTTTTT") || + result2 == Chromosome("TTaTTTTTTTTTTTTTTTTT") + )); + } +} + +BOOST_AUTO_TEST_CASE(uniformCrossover_should_swap_randomly_selected_genes) +{ + function crossover = uniformCrossover(0.7); + + SimulationRNG::reset(1); + Chromosome result1 = crossover(Chromosome("aaaaaaaaaa"), Chromosome("cccccc")); + BOOST_TEST(result1 == Chromosome("caaacc")); + + SimulationRNG::reset(1); + Chromosome result2 = crossover(Chromosome("cccccc"), Chromosome("aaaaaaaaaa")); + BOOST_TEST(result2 == Chromosome("acccaaaaaa")); +} + +BOOST_AUTO_TEST_CASE(symmetricUniformCrossover_should_swap_randomly_selected_genes) +{ + function crossover = symmetricUniformCrossover(0.7); + + SimulationRNG::reset(1); + tuple result1 = crossover(Chromosome("aaaaaaaaaa"), Chromosome("cccccc")); + tuple expectedPair1 = {Chromosome("caaacc"), Chromosome("acccaaaaaa")}; + BOOST_TEST(result1 == expectedPair1); + + tuple result2 = crossover(Chromosome("cccccc"), Chromosome("aaaaaaaaaa")); + tuple expectedPair2 = {Chromosome("caaaaaaaaa"), Chromosome("accccc")}; + BOOST_TEST(result2 == expectedPair2); +} + +BOOST_AUTO_TEST_CASE(uniformCrossover_should_only_consider_points_available_on_both_chromosomes) +{ + function crossover = uniformCrossover(0.7); + + set expectedPatterns = { + "TTTTTTTTTTTTTTTTTTTT", + "aTTTTTTTTTTTTTTTTTTT", + "TaTTTTTTTTTTTTTTTTTT", + "TTaTTTTTTTTTTTTTTTTT", + "aaTTTTTTTTTTTTTTTTTT", + "TaaTTTTTTTTTTTTTTTTT", + "aTaTTTTTTTTTTTTTTTTT", + "aaaTTTTTTTTTTTTTTTTT", + "aaa", + "Taa", + "aTa", + "aaT", + "TTa", + "aTT", + "TaT", + "TTT", + }; + + for (size_t i = 0; i < 30; ++i) + { + Chromosome result1 = crossover(Chromosome("aaa"), Chromosome("TTTTTTTTTTTTTTTTTTTT")); + Chromosome result2 = crossover(Chromosome("TTTTTTTTTTTTTTTTTTTT"), Chromosome("aaa")); + BOOST_TEST(expectedPatterns.count(toString(result1)) == 1); + BOOST_TEST(expectedPatterns.count(toString(result2)) == 1); + } +} + +BOOST_AUTO_TEST_CASE(uniformCrossover_should_not_swap_anything_if_chance_is_zero) +{ + BOOST_TEST(uniformCrossover(0.0)(Chromosome("aaaaaaaaaa"), Chromosome("cccccc")) == Chromosome("aaaaaaaaaa")); + BOOST_TEST(uniformCrossover(0.0)(Chromosome("cccccc"), Chromosome("aaaaaaaaaa")) == Chromosome("cccccc")); +} + +BOOST_AUTO_TEST_CASE(uniformCrossover_should_swap_whole_chromosomes_if_chance_is_one) +{ + BOOST_TEST(uniformCrossover(1.0)(Chromosome("aaaaaaaaaa"), Chromosome("cccccc")) == Chromosome("cccccc")); + BOOST_TEST(uniformCrossover(1.0)(Chromosome("cccccc"), Chromosome("aaaaaaaaaa")) == Chromosome("aaaaaaaaaa")); +} + +BOOST_AUTO_TEST_CASE(uniformCrossover_should_swap_genes_with_uniform_probability) +{ + constexpr size_t operationCount = 1000; + constexpr double swapChance = 0.8; + constexpr double relativeTolerance = 0.05; + double const expectedValue = swapChance; + double const variance = swapChance * (1 - swapChance); + + function crossover = uniformCrossover(swapChance); + Chromosome chromosome1("aaaaaaaaaa"); + Chromosome chromosome2("cccccccccc"); + + vector bernoulliTrials; + for (size_t i = 0; i < operationCount; ++i) + { + string genes = toString(crossover(chromosome1, chromosome2)); + for (size_t j = 0; j < chromosome1.length(); ++j) + bernoulliTrials.push_back(static_cast(genes[j] == 'c')); + } + + BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance); +} + +BOOST_AUTO_TEST_CASE(uniformCrossover_should_swap_tail_with_uniform_probability) +{ + constexpr size_t operationCount = 1000; + constexpr double swapChance = 0.3; + constexpr double relativeTolerance = 0.05; + double const expectedValue = swapChance; + double const variance = swapChance * (1 - swapChance); + + function crossover = uniformCrossover(swapChance); + Chromosome chromosome1("aaaaa"); + Chromosome chromosome2("cccccccccc"); + + vector bernoulliTrials; + for (size_t i = 0; i < operationCount; ++i) + { + string genes = toString(crossover(chromosome1, chromosome2)); + BOOST_REQUIRE(genes.size() == 5 || genes.size() == 10); + bernoulliTrials.push_back(static_cast(genes.size() == 10)); + } + + BOOST_TEST(abs(mean(bernoulliTrials) - expectedValue) < expectedValue * relativeTolerance); + BOOST_TEST(abs(meanSquaredError(bernoulliTrials, expectedValue) - variance) < variance * relativeTolerance); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() diff --git a/tools/yulPhaser/Mutations.cpp b/tools/yulPhaser/Mutations.cpp index 98689a810..7313cd45b 100644 --- a/tools/yulPhaser/Mutations.cpp +++ b/tools/yulPhaser/Mutations.cpp @@ -180,3 +180,138 @@ function phaser::fixedPointCrossover(double _crossoverPoint) return get<0>(fixedPointSwap(_chromosome1, _chromosome2, concretePoint)); }; } + +namespace +{ + +ChromosomePair fixedTwoPointSwap( + Chromosome const& _chromosome1, + Chromosome const& _chromosome2, + size_t _crossoverPoint1, + size_t _crossoverPoint2 +) +{ + assert(_crossoverPoint1 <= _chromosome1.length()); + assert(_crossoverPoint1 <= _chromosome2.length()); + assert(_crossoverPoint2 <= _chromosome1.length()); + assert(_crossoverPoint2 <= _chromosome2.length()); + + size_t lowPoint = min(_crossoverPoint1, _crossoverPoint2); + size_t highPoint = max(_crossoverPoint1, _crossoverPoint2); + + auto begin1 = _chromosome1.optimisationSteps().begin(); + auto begin2 = _chromosome2.optimisationSteps().begin(); + auto end1 = _chromosome1.optimisationSteps().end(); + auto end2 = _chromosome2.optimisationSteps().end(); + + return { + Chromosome( + vector(begin1, begin1 + lowPoint) + + vector(begin2 + lowPoint, begin2 + highPoint) + + vector(begin1 + highPoint, end1) + ), + Chromosome( + vector(begin2, begin2 + lowPoint) + + vector(begin1 + lowPoint, begin1 + highPoint) + + vector(begin2 + highPoint, end2) + ), + }; +} + +} + +function phaser::randomTwoPointCrossover() +{ + return [=](Chromosome const& _chromosome1, Chromosome const& _chromosome2) + { + size_t minLength = min(_chromosome1.length(), _chromosome2.length()); + + // Don't use position 0 (because this just swaps the values) unless it's the only choice. + size_t minPoint = (minLength > 0 ? 1 : 0); + assert(minPoint <= minLength); + + size_t randomPoint1 = SimulationRNG::uniformInt(minPoint, minLength); + size_t randomPoint2 = SimulationRNG::uniformInt(randomPoint1, minLength); + return get<0>(fixedTwoPointSwap(_chromosome1, _chromosome2, randomPoint1, randomPoint2)); + }; +} + +function phaser::symmetricRandomTwoPointCrossover() +{ + return [=](Chromosome const& _chromosome1, Chromosome const& _chromosome2) + { + size_t minLength = min(_chromosome1.length(), _chromosome2.length()); + + // Don't use position 0 (because this just swaps the values) unless it's the only choice. + size_t minPoint = (minLength > 0 ? 1 : 0); + assert(minPoint <= minLength); + + size_t randomPoint1 = SimulationRNG::uniformInt(minPoint, minLength); + size_t randomPoint2 = SimulationRNG::uniformInt(randomPoint1, minLength); + return fixedTwoPointSwap(_chromosome1, _chromosome2, randomPoint1, randomPoint2); + }; +} + +namespace +{ + +ChromosomePair uniformSwap(Chromosome const& _chromosome1, Chromosome const& _chromosome2, double _swapChance) +{ + vector steps1; + vector steps2; + + size_t minLength = min(_chromosome1.length(), _chromosome2.length()); + for (size_t i = 0; i < minLength; ++i) + if (SimulationRNG::bernoulliTrial(_swapChance)) + { + steps1.push_back(_chromosome2.optimisationSteps()[i]); + steps2.push_back(_chromosome1.optimisationSteps()[i]); + } + else + { + steps1.push_back(_chromosome1.optimisationSteps()[i]); + steps2.push_back(_chromosome2.optimisationSteps()[i]); + } + + auto begin1 = _chromosome1.optimisationSteps().begin(); + auto begin2 = _chromosome2.optimisationSteps().begin(); + auto end1 = _chromosome1.optimisationSteps().end(); + auto end2 = _chromosome2.optimisationSteps().end(); + + bool swapTail = SimulationRNG::bernoulliTrial(_swapChance); + if (_chromosome1.length() > minLength) + { + if (swapTail) + steps2.insert(steps2.end(), begin1 + minLength, end1); + else + steps1.insert(steps1.end(), begin1 + minLength, end1); + } + + if (_chromosome2.length() > minLength) + { + if (swapTail) + steps1.insert(steps1.end(), begin2 + minLength, end2); + else + steps2.insert(steps2.end(), begin2 + minLength, end2); + } + + return {Chromosome(steps1), Chromosome(steps2)}; +} + +} + +function phaser::uniformCrossover(double _swapChance) +{ + return [=](Chromosome const& _chromosome1, Chromosome const& _chromosome2) + { + return get<0>(uniformSwap(_chromosome1, _chromosome2, _swapChance)); + }; +} + +function phaser::symmetricUniformCrossover(double _swapChance) +{ + return [=](Chromosome const& _chromosome1, Chromosome const& _chromosome2) + { + return uniformSwap(_chromosome1, _chromosome2, _swapChance); + }; +} diff --git a/tools/yulPhaser/Mutations.h b/tools/yulPhaser/Mutations.h index d545aa093..04e7050af 100644 --- a/tools/yulPhaser/Mutations.h +++ b/tools/yulPhaser/Mutations.h @@ -80,4 +80,22 @@ std::function symmetricRandomPointCrossover(); /// unless there is no other choice (i.e. one of the chromosomes is empty). std::function fixedPointCrossover(double _crossoverPoint); +/// Creates a crossover operator that randomly selects two points between 0 and 1 and swaps genes +/// from the resulting interval. The interval may be empty in which case no genes are swapped. +std::function randomTwoPointCrossover(); + +/// Symmetric version of @a randomTwoPointCrossover(). Creates an operator that returns a pair +/// containing both possible results for the same crossover points. +std::function symmetricRandomTwoPointCrossover(); + +/// Creates a crossover operator that goes over the length of the shorter chromosomes and for +/// each gene independently decides whether to swap it or not (with probability given by +/// @a _swapChance). The tail of the longer chromosome (the part that's past the length of the +/// shorter one) is treated as a single gene and can potentially be swapped too. +std::function uniformCrossover(double _swapChance); + +/// Symmetric version of @a uniformCrossover(). Creates an operator that returns a pair +/// containing both possible results for the same set or swap decisions. +std::function symmetricUniformCrossover(double _swapChance); + } From ad89b477c86cd4f9ac632260a87549a6b7f132c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 11 Mar 2020 03:38:31 +0100 Subject: [PATCH 03/55] [yul-phaser] GeneticAlgorithms: Add methods for selecting a crossover operator --- tools/yulPhaser/GeneticAlgorithms.cpp | 39 +++++++++++++++++++++++++++ tools/yulPhaser/GeneticAlgorithms.h | 20 ++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/tools/yulPhaser/GeneticAlgorithms.cpp b/tools/yulPhaser/GeneticAlgorithms.cpp index 701bdf28c..7dcc44ed4 100644 --- a/tools/yulPhaser/GeneticAlgorithms.cpp +++ b/tools/yulPhaser/GeneticAlgorithms.cpp @@ -21,8 +21,47 @@ #include using namespace std; +using namespace solidity; using namespace solidity::phaser; +function phaser::buildCrossoverOperator( + CrossoverChoice _choice, + optional _uniformCrossoverSwapChance +) +{ + switch (_choice) + { + case CrossoverChoice::SinglePoint: + return randomPointCrossover(); + case CrossoverChoice::TwoPoint: + return randomTwoPointCrossover(); + case CrossoverChoice::Uniform: + assert(_uniformCrossoverSwapChance.has_value()); + return uniformCrossover(_uniformCrossoverSwapChance.value()); + default: + assertThrow(false, solidity::util::Exception, "Invalid CrossoverChoice value."); + }; +} + +function phaser::buildSymmetricCrossoverOperator( + CrossoverChoice _choice, + optional _uniformCrossoverSwapChance +) +{ + switch (_choice) + { + case CrossoverChoice::SinglePoint: + return symmetricRandomPointCrossover(); + case CrossoverChoice::TwoPoint: + return symmetricRandomTwoPointCrossover(); + case CrossoverChoice::Uniform: + assert(_uniformCrossoverSwapChance.has_value()); + return symmetricUniformCrossover(_uniformCrossoverSwapChance.value()); + default: + assertThrow(false, solidity::util::Exception, "Invalid CrossoverChoice value."); + }; +} + Population RandomAlgorithm::runNextRound(Population _population) { RangeSelection elite(0.0, m_options.elitePoolSize); diff --git a/tools/yulPhaser/GeneticAlgorithms.h b/tools/yulPhaser/GeneticAlgorithms.h index 0d1f0375f..22ab62c04 100644 --- a/tools/yulPhaser/GeneticAlgorithms.h +++ b/tools/yulPhaser/GeneticAlgorithms.h @@ -20,11 +20,31 @@ #pragma once +#include #include +#include + namespace solidity::phaser { +enum class CrossoverChoice +{ + SinglePoint, + TwoPoint, + Uniform, +}; + +std::function buildCrossoverOperator( + CrossoverChoice _choice, + std::optional _uniformCrossoverSwapChance +); + +std::function buildSymmetricCrossoverOperator( + CrossoverChoice _choice, + std::optional _uniformCrossoverSwapChance +); + /** * Abstract base class for genetic algorithms. * The main feature is the @a runNextRound() method that executes one round of the algorithm, From d9e2735361f2fa85bbc8be4f3f5def2a34f4fa14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Wed, 11 Mar 2020 03:39:29 +0100 Subject: [PATCH 04/55] [yul-phaser] Add options for selecting crossover operator used by the algorithms --- test/yulPhaser/GeneticAlgorithms.cpp | 10 ++++++++++ test/yulPhaser/Phaser.cpp | 7 +++++++ tools/yulPhaser/GeneticAlgorithms.cpp | 12 ++++++++++-- tools/yulPhaser/GeneticAlgorithms.h | 8 +++++++- tools/yulPhaser/Phaser.cpp | 26 ++++++++++++++++++++++++++ tools/yulPhaser/Phaser.h | 8 ++++++++ 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/test/yulPhaser/GeneticAlgorithms.cpp b/test/yulPhaser/GeneticAlgorithms.cpp index 1b902d978..d7ef8db57 100644 --- a/test/yulPhaser/GeneticAlgorithms.cpp +++ b/test/yulPhaser/GeneticAlgorithms.cpp @@ -51,6 +51,8 @@ protected: /* mutationChance = */ 0.0, /* deletionChance = */ 0.0, /* additionChance = */ 0.0, + /* CrossoverChoice = */ CrossoverChoice::SinglePoint, + /* uniformCrossoverSwapChance= */ 0.5, }; }; @@ -113,6 +115,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_preserve_elite_and_regenerate_rest_o /* deletionVsAdditionChance = */ 1.0, /* percentGenesToRandomise = */ 0.0, /* percentGenesToAddOrDelete = */ 1.0, + /* CrossoverChoice = */ CrossoverChoice::SinglePoint, + /* uniformCrossoverSwapChance= */ 0.5, }; GenerationalElitistWithExclusivePools algorithm(options); @@ -133,6 +137,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_not_replace_elite_with_worse_individ /* deletionVsAdditionChance = */ 0.0, /* percentGenesToRandomise = */ 0.0, /* percentGenesToAddOrDelete = */ 1.0, + /* CrossoverChoice = */ CrossoverChoice::SinglePoint, + /* uniformCrossoverSwapChance= */ 0.5, }; GenerationalElitistWithExclusivePools algorithm(options); @@ -152,6 +158,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_generate_individuals_in_the_crossove /* deletionVsAdditionChance = */ 0.5, /* percentGenesToRandomise = */ 1.0, /* percentGenesToAddOrDelete = */ 1.0, + /* CrossoverChoice = */ CrossoverChoice::SinglePoint, + /* uniformCrossoverSwapChance= */ 0.5, }; GenerationalElitistWithExclusivePools algorithm(options); @@ -179,6 +187,8 @@ BOOST_FIXTURE_TEST_CASE(runNextRound_should_generate_individuals_in_the_crossove /* deletionVsAdditionChance = */ 0.0, /* percentGenesToRandomise = */ 0.0, /* percentGenesToAddOrDelete = */ 0.0, + /* CrossoverChoice = */ CrossoverChoice::SinglePoint, + /* uniformCrossoverSwapChance= */ 0.5, }; GenerationalElitistWithExclusivePools algorithm(options); diff --git a/test/yulPhaser/Phaser.cpp b/test/yulPhaser/Phaser.cpp index c365ce310..c381ba623 100644 --- a/test/yulPhaser/Phaser.cpp +++ b/test/yulPhaser/Phaser.cpp @@ -45,6 +45,8 @@ protected: /* algorithm = */ Algorithm::Random, /* minChromosomeLength = */ 50, /* maxChromosomeLength = */ 100, + /* CrossoverChoice = */ CrossoverChoice::Uniform, + /* uniformCrossoverSwapChance = */ 0.5, /* randomElitePoolSize = */ 0.5, /* gewepMutationPoolSize = */ 0.1, /* gewepCrossoverPoolSize = */ 0.1, @@ -121,6 +123,9 @@ BOOST_FIXTURE_TEST_CASE(build_should_select_the_right_algorithm_and_pass_the_opt auto gewepAlgorithm = dynamic_cast(algorithm2.get()); BOOST_REQUIRE(gewepAlgorithm != nullptr); + BOOST_TEST(gewepAlgorithm->options().crossover == m_options.crossover); + BOOST_TEST(gewepAlgorithm->options().uniformCrossoverSwapChance.has_value()); + BOOST_TEST(gewepAlgorithm->options().uniformCrossoverSwapChance.value() == m_options.uniformCrossoverSwapChance); BOOST_TEST(gewepAlgorithm->options().mutationPoolSize == m_options.gewepMutationPoolSize); BOOST_TEST(gewepAlgorithm->options().crossoverPoolSize == m_options.gewepCrossoverPoolSize); BOOST_TEST(gewepAlgorithm->options().randomisationChance == m_options.gewepRandomisationChance); @@ -134,6 +139,8 @@ BOOST_FIXTURE_TEST_CASE(build_should_select_the_right_algorithm_and_pass_the_opt auto classicAlgorithm = dynamic_cast(algorithm3.get()); BOOST_REQUIRE(classicAlgorithm != nullptr); + BOOST_TEST(classicAlgorithm->options().uniformCrossoverSwapChance.has_value()); + BOOST_TEST(classicAlgorithm->options().uniformCrossoverSwapChance.value() == m_options.uniformCrossoverSwapChance); BOOST_TEST(classicAlgorithm->options().elitePoolSize == m_options.classicElitePoolSize); BOOST_TEST(classicAlgorithm->options().crossoverChance == m_options.classicCrossoverChance); BOOST_TEST(classicAlgorithm->options().mutationChance == m_options.classicMutationChance); diff --git a/tools/yulPhaser/GeneticAlgorithms.cpp b/tools/yulPhaser/GeneticAlgorithms.cpp index 7dcc44ed4..1da31645f 100644 --- a/tools/yulPhaser/GeneticAlgorithms.cpp +++ b/tools/yulPhaser/GeneticAlgorithms.cpp @@ -96,7 +96,10 @@ Population GenerationalElitistWithExclusivePools::runNextRound(Population _popul geneAddition(m_options.percentGenesToAddOrDelete) ) ); - std::function crossoverOperator = randomPointCrossover(); + std::function crossoverOperator = buildCrossoverOperator( + m_options.crossover, + m_options.uniformCrossoverSwapChance + ); return _population.select(elitePool) + @@ -111,10 +114,15 @@ Population ClassicGeneticAlgorithm::runNextRound(Population _population) Population selectedPopulation = select(_population, rest.individuals().size()); + std::function crossoverOperator = buildSymmetricCrossoverOperator( + m_options.crossover, + m_options.uniformCrossoverSwapChance + ); + Population crossedPopulation = Population::combine( selectedPopulation.symmetricCrossoverWithRemainder( PairsFromRandomSubset(m_options.crossoverChance), - symmetricRandomPointCrossover() + crossoverOperator ) ); diff --git a/tools/yulPhaser/GeneticAlgorithms.h b/tools/yulPhaser/GeneticAlgorithms.h index 22ab62c04..96c027738 100644 --- a/tools/yulPhaser/GeneticAlgorithms.h +++ b/tools/yulPhaser/GeneticAlgorithms.h @@ -130,6 +130,8 @@ public: double deletionVsAdditionChance; ///< The chance of choosing @a geneDeletion as the mutation if randomisation was not chosen. double percentGenesToRandomise; ///< The chance of any given gene being mutated in gene randomisation. double percentGenesToAddOrDelete; ///< The chance of a gene being added (or deleted) in gene addition (or deletion). + CrossoverChoice crossover; ///< The crossover operator to use. + std::optional uniformCrossoverSwapChance; ///< Chance of a pair of genes being swapped in uniform crossover. bool isValid() const { @@ -140,6 +142,7 @@ public: 0 <= deletionVsAdditionChance && deletionVsAdditionChance <= 1.0 && 0 <= percentGenesToRandomise && percentGenesToRandomise <= 1.0 && 0 <= percentGenesToAddOrDelete && percentGenesToAddOrDelete <= 1.0 && + 0 <= uniformCrossoverSwapChance && uniformCrossoverSwapChance <= 1.0 && mutationPoolSize + crossoverPoolSize <= 1.0 ); } @@ -185,6 +188,8 @@ public: double mutationChance; ///< The chance of a particular gene being randomised in @a geneRandomisation mutation. double deletionChance; ///< The chance of a particular gene being deleted in @a geneDeletion mutation. double additionChance; ///< The chance of a particular gene being added in @a geneAddition mutation. + CrossoverChoice crossover; ///< The crossover operator to use + std::optional uniformCrossoverSwapChance; ///< Chance of a pair of genes being swapped in uniform crossover. bool isValid() const { @@ -193,7 +198,8 @@ public: 0 <= crossoverChance && crossoverChance <= 1.0 && 0 <= mutationChance && mutationChance <= 1.0 && 0 <= deletionChance && deletionChance <= 1.0 && - 0 <= additionChance && additionChance <= 1.0 + 0 <= additionChance && additionChance <= 1.0 && + 0 <= uniformCrossoverSwapChance && uniformCrossoverSwapChance <= 1.0 ); } }; diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 6f7be3257..ebfdbb897 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -78,6 +78,14 @@ map const MetricAggregatorChoiceToStringMap = }; map const StringToMetricAggregatorChoiceMap = invertMap(MetricAggregatorChoiceToStringMap); +map const CrossoverChoiceToStringMap = +{ + {CrossoverChoice::SinglePoint, "single-point"}, + {CrossoverChoice::TwoPoint, "two-point"}, + {CrossoverChoice::Uniform, "uniform"}, +}; +map const StringToCrossoverChoiceMap = invertMap(CrossoverChoiceToStringMap); + } istream& phaser::operator>>(istream& _inputStream, PhaserMode& _phaserMode) { return deserializeChoice(_inputStream, _phaserMode, StringToPhaserModeMap); } @@ -88,6 +96,8 @@ istream& phaser::operator>>(istream& _inputStream, MetricChoice& _metric) { retu ostream& phaser::operator<<(ostream& _outputStream, MetricChoice _metric) { return serializeChoice(_outputStream, _metric, MetricChoiceToStringMap); } istream& phaser::operator>>(istream& _inputStream, MetricAggregatorChoice& _aggregator) { return deserializeChoice(_inputStream, _aggregator, StringToMetricAggregatorChoiceMap); } ostream& phaser::operator<<(ostream& _outputStream, MetricAggregatorChoice _aggregator) { return serializeChoice(_outputStream, _aggregator, MetricAggregatorChoiceToStringMap); } +istream& phaser::operator>>(istream& _inputStream, CrossoverChoice& _crossover) { return deserializeChoice(_inputStream, _crossover, StringToCrossoverChoiceMap); } +ostream& phaser::operator<<(ostream& _outputStream, CrossoverChoice _crossover) { return serializeChoice(_outputStream, _crossover, CrossoverChoiceToStringMap); } GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLine(po::variables_map const& _arguments) { @@ -95,6 +105,8 @@ GeneticAlgorithmFactory::Options GeneticAlgorithmFactory::Options::fromCommandLi _arguments["algorithm"].as(), _arguments["min-chromosome-length"].as(), _arguments["max-chromosome-length"].as(), + _arguments["crossover"].as(), + _arguments["uniform-crossover-swap-chance"].as(), _arguments.count("random-elite-pool-size") > 0 ? _arguments["random-elite-pool-size"].as() : optional{}, @@ -155,6 +167,8 @@ unique_ptr GeneticAlgorithmFactory::build( /* deletionVsAdditionChance = */ _options.gewepDeletionVsAdditionChance, /* percentGenesToRandomise = */ percentGenesToRandomise, /* percentGenesToAddOrDelete = */ percentGenesToAddOrDelete, + /* crossover = */ _options.crossover, + /* uniformCrossoverSwapChance = */ _options.uniformCrossoverSwapChance, }); } case Algorithm::Classic: @@ -165,6 +179,8 @@ unique_ptr GeneticAlgorithmFactory::build( /* mutationChance = */ _options.classicMutationChance, /* deletionChance = */ _options.classicDeletionChance, /* additionChance = */ _options.classicAdditionChance, + /* crossover = */ _options.crossover, + /* uniformCrossoverSwapChance = */ _options.uniformCrossoverSwapChance, }); } default: @@ -451,6 +467,16 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() po::value()->value_name("")->default_value(30), "Maximum length of randomly generated chromosomes." ) + ( + "crossover", + po::value()->value_name("")->default_value(CrossoverChoice::SinglePoint), + "Type of the crossover operator to use." + ) + ( + "uniform-crossover-swap-chance", + po::value()->value_name("")->default_value(0.5), + "Chance of two genes being swapped between chromosomes in uniform crossover." + ) ; keywordDescription.add(algorithmDescription); diff --git a/tools/yulPhaser/Phaser.h b/tools/yulPhaser/Phaser.h index 2896dd090..9c1c5ef75 100644 --- a/tools/yulPhaser/Phaser.h +++ b/tools/yulPhaser/Phaser.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include @@ -83,6 +84,8 @@ std::istream& operator>>(std::istream& _inputStream, solidity::phaser::MetricCho std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::MetricChoice _metric); std::istream& operator>>(std::istream& _inputStream, solidity::phaser::MetricAggregatorChoice& _aggregator); std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::MetricAggregatorChoice _aggregator); +std::istream& operator>>(std::istream& _inputStream, solidity::phaser::CrossoverChoice& _crossover); +std::ostream& operator<<(std::ostream& _outputStream, solidity::phaser::CrossoverChoice _crossover); /** * Builds and validates instances of @a GeneticAlgorithm and its derived classes. @@ -95,13 +98,18 @@ public: Algorithm algorithm; size_t minChromosomeLength; size_t maxChromosomeLength; + CrossoverChoice crossover; + double uniformCrossoverSwapChance; + std::optional randomElitePoolSize; + double gewepMutationPoolSize; double gewepCrossoverPoolSize; double gewepRandomisationChance; double gewepDeletionVsAdditionChance; std::optional gewepGenesToRandomise; std::optional gewepGenesToAddOrDelete; + double classicElitePoolSize; double classicCrossoverChance; double classicMutationChance; From c2effd4e983a45c2274a2104c99b8527330fa1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 16 Apr 2020 11:36:05 +0200 Subject: [PATCH 05/55] [yul-phaser] Mutations: Minor style tweak, missing space in the ternary operator --- tools/yulPhaser/Mutations.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/yulPhaser/Mutations.cpp b/tools/yulPhaser/Mutations.cpp index 7313cd45b..2b988e622 100644 --- a/tools/yulPhaser/Mutations.cpp +++ b/tools/yulPhaser/Mutations.cpp @@ -145,7 +145,7 @@ function phaser::randomPointCrossover() size_t minLength = min(_chromosome1.length(), _chromosome2.length()); // Don't use position 0 (because this just swaps the values) unless it's the only choice. - size_t minPoint = (minLength > 0? 1 : 0); + size_t minPoint = (minLength > 0 ? 1 : 0); assert(minPoint <= minLength); size_t randomPoint = SimulationRNG::uniformInt(minPoint, minLength); @@ -160,7 +160,7 @@ function phaser::symmetricRandomPointCrossover() size_t minLength = min(_chromosome1.length(), _chromosome2.length()); // Don't use position 0 (because this just swaps the values) unless it's the only choice. - size_t minPoint = (minLength > 0? 1 : 0); + size_t minPoint = (minLength > 0 ? 1 : 0); assert(minPoint <= minLength); size_t randomPoint = SimulationRNG::uniformInt(minPoint, minLength); From fe383fbd7afbea44f99f069f56e4b40ad6952bc0 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 22 Apr 2020 12:03:10 +0100 Subject: [PATCH 06/55] Add missing blog URLs to bugs.json --- docs/bugs.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/bugs.json b/docs/bugs.json index 42a5277b1..56bae3aaf 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -11,6 +11,7 @@ "name": "MemoryArrayCreationOverflow", "summary": "The creation of very large memory arrays can result in overlapping memory regions and thus memory corruption.", "description": "No runtime overflow checks were performed for the length of memory arrays during creation. In cases for which the memory size of an array in bytes, i.e. the array length times 32, is larger than 2^256-1, the memory allocation will overflow, potentially resulting in overlapping memory areas. The length of the array is still stored correctly, so copying or iterating over such an array will result in out-of-gas.", + "link": "https://solidity.ethereum.org/2020/04/06/memory-creation-overflow-bug/", "introduced": "0.2.0", "fixed": "0.6.5", "severity": "low" @@ -73,6 +74,7 @@ "name": "SignedArrayStorageCopy", "summary": "Assigning an array of signed integers to a storage array of different type can lead to data corruption in that array.", "description": "In two's complement, negative integers have their higher order bits set. In order to fit into a shared storage slot, these have to be set to zero. When a conversion is done at the same time, the bits to set to zero were incorrectly determined from the source and not the target type. This means that such copy operations can lead to incorrect values being stored.", + "link": "https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs/", "introduced": "0.4.7", "fixed": "0.5.10", "severity": "low/medium" @@ -81,6 +83,7 @@ "name": "ABIEncoderV2StorageArrayWithMultiSlotElement", "summary": "Storage arrays containing structs or other statically-sized arrays are not read properly when directly encoded in external function calls or in abi.encode*.", "description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in external function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable or if the storage array only contains value types or dynamically-sized arrays.", + "link": "https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs/", "introduced": "0.4.16", "fixed": "0.5.10", "severity": "low", From 5329da93fb2af94b2dc763a39640a38a093ed2f6 Mon Sep 17 00:00:00 2001 From: Djordje Mijovic Date: Mon, 6 Apr 2020 17:26:59 +0200 Subject: [PATCH 07/55] [Sol2Yul] Adding support for constructors with parameters in case of inheritance --- libsolidity/codegen/YulUtilFunctions.cpp | 36 ++++ libsolidity/codegen/YulUtilFunctions.h | 7 + libsolidity/codegen/ir/IRGenerator.cpp | 171 +++++++++++++----- libsolidity/codegen/ir/IRGenerator.h | 19 +- .../codegen/ir/IRGeneratorForStatements.cpp | 8 + .../codegen/ir/IRGeneratorForStatements.h | 3 + .../standard_eWasm_requested/output.json | 85 +++++++-- .../output.json | 4 + .../standard_ir_requested/output.json | 12 +- .../yul_string_format_ascii/output.json | 12 +- .../output.json | 12 +- .../output.json | 12 +- .../yul_string_format_ascii_long/output.json | 12 +- .../yul_string_format_hex/output.json | 12 +- .../constructor_ihneritance_init_order_2.sol | 14 ++ .../constructor_inheritance_init_order.sol | 17 ++ ...ructor_with_params_diamond_inheritance.sol | 28 +++ .../constructor_with_params_inheritance.sol | 18 ++ .../constructor_with_params_inheritance_2.sol | 15 ++ .../state_variables_init_order.sol | 14 ++ .../state_variables_init_order_2.sol | 18 ++ .../state_variables_init_order_3.sol | 29 +++ 22 files changed, 476 insertions(+), 82 deletions(-) create mode 100644 test/libsolidity/semanticTests/constructor_ihneritance_init_order_2.sol create mode 100644 test/libsolidity/semanticTests/constructor_inheritance_init_order.sol create mode 100644 test/libsolidity/semanticTests/constructor_with_params_diamond_inheritance.sol create mode 100644 test/libsolidity/semanticTests/constructor_with_params_inheritance.sol create mode 100644 test/libsolidity/semanticTests/constructor_with_params_inheritance_2.sol create mode 100644 test/libsolidity/semanticTests/state_variables_init_order.sol create mode 100644 test/libsolidity/semanticTests/state_variables_init_order_2.sol create mode 100644 test/libsolidity/semanticTests/state_variables_init_order_3.sol diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index e520c586f..ab449a730 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -2334,3 +2334,39 @@ string YulUtilFunctions::extractReturndataFunction() }); } +string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction( + ContractDefinition const& _contract, + string const& _creationObjectName +) +{ + string functionName = "copy_arguments_for_constructor_" + + toString(_contract.constructor()->id()) + + "_object_" + + _contract.name() + + "_" + + toString(_contract.id()); + + return m_functionCollector.createFunction(functionName, [&]() { + string returnParams = suffixedVariableNameList("ret_param_",0, _contract.constructor()->parameters().size()); + ABIFunctions abiFunctions(m_evmVersion, m_revertStrings, m_functionCollector); + + return util::Whiskers(R"( + function () -> { + let programSize := datasize("") + let argSize := sub(codesize(), programSize) + + let memoryDataOffset := (argSize) + codecopy(memoryDataOffset, programSize, argSize) + + := (memoryDataOffset, add(memoryDataOffset, argSize)) + } + )") + ("functionName", functionName) + ("retParams", returnParams) + ("object", _creationObjectName) + ("allocate", allocationFunction()) + ("abiDecode", abiFunctions.tupleDecoder(FunctionType(*_contract.constructor()).parameterTypes(), true)) + .render(); + }); +} + diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 9550b9e89..b16eb0672 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -337,6 +337,13 @@ public: /// If returndatacopy() is not supported by the underlying target, a empty array will be returned instead. std::string extractReturndataFunction(); + /// @returns function name that returns constructor arguments copied to memory + /// signature: () -> arguments + std::string copyConstructorArgumentsToMemoryFunction( + ContractDefinition const& _contract, + std::string const& _creationObjectName + ); + private: /// Special case of conversionFunction - handles everything that does not /// use exactly one variable to hold the value. diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 7499d3909..92631ddc2 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -81,7 +81,9 @@ string IRGenerator::generate(ContractDefinition const& _contract) object "" { code { - + + let := () + () } @@ -99,8 +101,25 @@ string IRGenerator::generate(ContractDefinition const& _contract) t("CreationObject", creationObjectName(_contract)); t("memoryInit", memoryInit()); - t("constructor", constructorCode(_contract)); + + FunctionDefinition const* constructor = _contract.constructor(); + t("callValueCheck", !constructor || !constructor->isPayable() ? callValueCheck() : ""); + vector constructorParams; + if (constructor && !constructor->parameters().empty()) + { + for (size_t i = 0; i < constructor->parameters().size(); ++i) + constructorParams.emplace_back(m_context.newYulVariable()); + t( + "copyConstructorArguments", + m_utils.copyConstructorArgumentsToMemoryFunction(_contract, creationObjectName(_contract)) + ); + } + t("constructorParams", joinHumanReadable(constructorParams)); + t("constructorHasParams", !constructorParams.empty()); + t("implicitConstructor", implicitConstructorName(_contract)); + t("deploy", deployCode(_contract)); + generateImplicitConstructors(_contract); generateQueuedFunctions(); t("functions", m_context.functionCollector().requestedFunctions()); @@ -238,64 +257,115 @@ string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDec return generator.code(); } -string IRGenerator::constructorCode(ContractDefinition const& _contract) +pair> IRGenerator::evaluateConstructorArguments( + ContractDefinition const& _contract +) { - // Initialization of state variables in base-to-derived order. - solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); + map constructorParams; + vector>const *>> baseConstructorArguments; - using boost::adaptors::reverse; + for (ASTPointer const& base: _contract.baseContracts()) + if (FunctionDefinition const* baseConstructor = dynamic_cast( + base->name().annotation().referencedDeclaration + )->constructor(); baseConstructor && base->arguments()) + baseConstructorArguments.emplace_back( + dynamic_cast(baseConstructor->scope()), + base->arguments() + ); - ostringstream out; + if (FunctionDefinition const* constructor = _contract.constructor()) + for (auto const& modifier: constructor->modifiers()) + if (FunctionDefinition const* baseConstructor = dynamic_cast( + modifier->name()->annotation().referencedDeclaration + )->constructor(); baseConstructor && modifier->arguments()) + baseConstructorArguments.emplace_back( + dynamic_cast(baseConstructor->scope()), + modifier->arguments() + ); - FunctionDefinition const* constructor = _contract.constructor(); - if (constructor && !constructor->isPayable()) - out << callValueCheck(); - - for (ContractDefinition const* contract: reverse(_contract.annotation().linearizedBaseContracts)) + IRGeneratorForStatements generator{m_context, m_utils}; + for (auto&& [baseContract, arguments]: baseConstructorArguments) { - out << - "\n// Begin state variable initialization for contract \"" << - contract->name() << - "\" (" << - contract->stateVariables().size() << - " variables)\n"; - - IRGeneratorForStatements generator{m_context, m_utils}; - for (VariableDeclaration const* variable: contract->stateVariables()) - if (!variable->isConstant() && !variable->immutable()) - generator.initializeStateVar(*variable); - out << generator.code(); - - out << "// End state variable initialization for contract \"" << contract->name() << "\".\n"; + solAssert(baseContract && arguments, ""); + if (baseContract->constructor() && !arguments->empty()) + { + vector params; + for (size_t i = 0; i < arguments->size(); ++i) + params.emplace_back(generator.evaluateExpression( + *(arguments->at(i)), + *(baseContract->constructor()->parameters()[i]->type()) + ).commaSeparatedList()); + constructorParams[baseContract] = joinHumanReadable(params); + } } - if (constructor) + return {generator.code(), constructorParams}; +} + +string IRGenerator::initStateVariables(ContractDefinition const& _contract) +{ + IRGeneratorForStatements generator{m_context, m_utils}; + for (VariableDeclaration const* variable: _contract.stateVariables()) + if (!variable->isConstant() && !variable->immutable()) + generator.initializeStateVar(*variable); + + return generator.code(); +} + +void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contract) +{ + auto listAllParams = [&]( + map const& baseParams) -> string { - ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector()); - unsigned paramVars = make_shared(constructor->functionType(false)->parameterTypes())->sizeOnStack(); + vector params; + for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) + if (baseParams.count(contract)) + params.emplace_back(baseParams.at(contract)); + return joinHumanReadable(params); + }; - Whiskers t(R"X( - let programSize := datasize("") - let argSize := sub(codesize(), programSize) + map baseConstructorParams; + for (size_t i = 0; i < _contract.annotation().linearizedBaseContracts.size(); ++i) + { + ContractDefinition const* contract = _contract.annotation().linearizedBaseContracts[i]; + baseConstructorParams.erase(contract); - let memoryDataOffset := (argSize) - codecopy(memoryDataOffset, programSize, argSize) + m_context.functionCollector().createFunction(implicitConstructorName(*contract), [&]() { + Whiskers t(R"( + function () { + + () + + + } + )"); + string params; + if (contract->constructor()) + for (ASTPointer const& varDecl: contract->constructor()->parameters()) + params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList(); + t("params", params); + string baseParamsString = listAllParams(baseConstructorParams); + t("baseParams", baseParamsString); + t("comma", !params.empty() && !baseParamsString.empty() ? ", " : ""); + t("functionName", implicitConstructorName(*contract)); + pair> evaluatedArgs = evaluateConstructorArguments(*contract); + baseConstructorParams.insert(evaluatedArgs.second.begin(), evaluatedArgs.second.end()); + t("evalBaseArguments", evaluatedArgs.first); + if (i < _contract.annotation().linearizedBaseContracts.size() - 1) + { + t("hasNextConstructor", true); + ContractDefinition const* nextContract = _contract.annotation().linearizedBaseContracts[i + 1]; + t("nextConstructor", implicitConstructorName(*nextContract)); + t("nextParams", listAllParams(baseConstructorParams)); + } + else + t("hasNextConstructor", false); + t("initStateVariables", initStateVariables(*contract)); + t("userDefinedConstructorBody", contract->constructor() ? generate(contract->constructor()->body()) : ""); - (memoryDataOffset, add(memoryDataOffset, argSize)) - - () - )X"); - t("object", creationObjectName(_contract)); - t("allocate", m_utils.allocationFunction()); - t("assignToParams", paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := "); - t("params", suffixedVariableNameList("param_", 0, paramVars)); - t("abiDecode", abiFunctions.tupleDecoder(constructor->functionType(false)->parameterTypes(), true)); - t("constructorName", m_context.enqueueFunctionForCodeGeneration(*constructor)); - - out << t.render(); + return t.render(); + }); } - - return out.str(); } string IRGenerator::deployCode(ContractDefinition const& _contract) @@ -323,6 +393,11 @@ string IRGenerator::runtimeObjectName(ContractDefinition const& _contract) return _contract.name() + "_" + to_string(_contract.id()) + "_deployed"; } +string IRGenerator::implicitConstructorName(ContractDefinition const& _contract) +{ + return "constructor_" + _contract.name() + "_" + to_string(_contract.id()); +} + string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) { Whiskers t(R"X( diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index c4035141c..fde382cd7 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -67,12 +67,29 @@ private: /// Generates code that assigns the initial value of the respective type. std::string generateInitialAssignment(VariableDeclaration const& _varDecl); - std::string constructorCode(ContractDefinition const& _contract); + /// Generates implicit constructors for all contracts in the inheritance hierarchy of + /// @a _contract + /// If there are user defined constructors, their body will be included in implicit constructors body. + void generateImplicitConstructors(ContractDefinition const& _contract); + + /// Evaluates constructor's arguments for all base contracts (listed in inheritance specifiers) of + /// @a _contract + /// @returns Pair of expressions needed to evaluate params and list of parameters in a map contract -> params + std::pair> evaluateConstructorArguments( + ContractDefinition const& _contract + ); + + /// Initializes state variables of + /// @a _contract + /// @returns Source code to initialize state variables + std::string initStateVariables(ContractDefinition const& _contract); + std::string deployCode(ContractDefinition const& _contract); std::string callValueCheck(); std::string creationObjectName(ContractDefinition const& _contract); std::string runtimeObjectName(ContractDefinition const& _contract); + std::string implicitConstructorName(ContractDefinition const& _contract); std::string dispatchRoutine(ContractDefinition const& _contract); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index c1b4c8b21..71f87aa52 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -169,6 +169,14 @@ void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _va assign(m_context.localVariable(_varDecl), zero); } +IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType) +{ + _expression.accept(*this); + IRVariable variable{m_context.newYulVariable(), _targetType}; + define(variable, _expression); + return variable; +} + void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement) { if (Expression const* expression = _varDeclStatement.initialValue()) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index 810087a2d..4b245e9c8 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -51,6 +51,9 @@ public: /// Generates code to initialize the given local variable. void initializeLocalVar(VariableDeclaration const& _varDecl); + /// Calculates expression's value and returns variable where it was stored + IRVariable evaluateExpression(Expression const& _expression, Type const& _to); + void endVisit(VariableDeclarationStatement const& _variableDeclaration) override; bool visit(Conditional const& _conditional) override; bool visit(Assignment const& _assignment) override; diff --git a/test/cmdlineTests/standard_eWasm_requested/output.json b/test/cmdlineTests/standard_eWasm_requested/output.json index 0d796bce5..a67539f62 100644 --- a/test/cmdlineTests/standard_eWasm_requested/output.json +++ b/test/cmdlineTests/standard_eWasm_requested/output.json @@ -1,6 +1,8 @@ {"contracts":{"A":{"C":{"ewasm":{"wast":"(module ;; sub-module \"C_2_deployed\" will be encoded as custom section in binary here, but is skipped in text mode. (import \"ethereum\" \"codeCopy\" (func $eth.codeCopy (param i32 i32 i32))) + (import \"ethereum\" \"revert\" (func $eth.revert (param i32 i32))) + (import \"ethereum\" \"getCallValue\" (func $eth.getCallValue (param i32))) (import \"ethereum\" \"finish\" (func $eth.finish (param i32 i32))) (memory $memory (export \"memory\") 1) (export \"main\" (func $main)) @@ -9,27 +11,30 @@ (local $_1 i64) (local $p i64) (local $r i64) - (local $hi i64) - (local $hi_1 i64) - (local $y i64) - (local $hi_2 i64) (local $_2 i64) + (local $z1 i64) + (local $z2 i64) + (local $z3 i64) + (local $_3 i64) (local.set $_1 (i64.const 0)) (local.set $p (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 64))) (local.set $r (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $p)) (i32.wrap_i64 (i64.const 64))))) (if (i64.ne (i64.extend_i32_u (i32.lt_u (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (local.get $p)))) (i64.const 0)) (then (unreachable))) - (local.set $hi (i64.shl (call $endian_swap_16 (local.get $_1)) (i64.const 16))) - (local.set $hi_1 (i64.shl (i64.or (local.get $hi) (call $endian_swap_16 (i64.shr_u (local.get $_1) (i64.const 16)))) (i64.const 32))) - (local.set $y (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (local.get $_1) (i64.const 32))))) - (i64.store (i32.wrap_i64 (local.get $r)) (local.get $y)) - (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 8))))) (local.get $y)) - (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 16))))) (local.get $y)) - (local.set $hi_2 (i64.shl (call $endian_swap_32 (i64.const 128)) (i64.const 32))) - (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 24))))) (i64.or (local.get $hi_2) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32))))) - (local.set $_2 (datasize \"C_2_deployed\")) - (call $eth.codeCopy (i32.wrap_i64 (call $to_internal_i32ptr (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (dataoffset \"C_2_deployed\"))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_2)))) - (call $eth.finish (i32.wrap_i64 (call $to_internal_i32ptr (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_2)))) + (local.set $_2 (call $endian_swap (local.get $_1))) + (i64.store (i32.wrap_i64 (local.get $r)) (local.get $_2)) + (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 8))))) (local.get $_2)) + (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 16))))) (local.get $_2)) + (i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 24))))) (call $endian_swap (i64.const 128))) + (call $eth.getCallValue (i32.wrap_i64 (i64.const 0))) + (local.set $z1 (call $endian_swap (i64.load (i32.wrap_i64 (i64.const 0))))) + (local.set $z2 (call $endian_swap (i64.load (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (i64.const 0)) (i32.wrap_i64 (i64.const 8)))))))) + (local.set $z3 (call $endian_swap (i64.load (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (i64.const 0)) (i32.wrap_i64 (i64.const 16)))))))) + (if (i64.ne (i64.extend_i32_u (i32.eqz (i32.wrap_i64 (i64.extend_i32_u (i64.eqz (i64.or (i64.or (local.get $z1) (local.get $z2)) (i64.or (local.get $z3) (call $endian_swap (i64.load (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (i64.const 0)) (i32.wrap_i64 (i64.const 24)))))))))))))) (i64.const 0)) (then + (call $revert (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)))) + (local.set $_3 (datasize \"C_2_deployed\")) + (call $codecopy (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (dataoffset \"C_2_deployed\") (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_3)) + (call $return (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_3)) ) (func $u256_to_i32 @@ -62,6 +67,22 @@ (local.get $r) ) +(func $codecopy + (param $x1 i64) + (param $x2 i64) + (param $x3 i64) + (param $x4 i64) + (param $y1 i64) + (param $y2 i64) + (param $y3 i64) + (param $y4 i64) + (param $z1 i64) + (param $z2 i64) + (param $z3 i64) + (param $z4 i64) + (call $eth.codeCopy (i32.wrap_i64 (call $to_internal_i32ptr (local.get $x1) (local.get $x2) (local.get $x3) (local.get $x4))) (i32.wrap_i64 (call $u256_to_i32 (local.get $y1) (local.get $y2) (local.get $y3) (local.get $y4))) (i32.wrap_i64 (call $u256_to_i32 (local.get $z1) (local.get $z2) (local.get $z3) (local.get $z4)))) +) + (func $endian_swap_16 (param $x i64) (result i64) @@ -80,5 +101,39 @@ (local.get $y) ) +(func $endian_swap + (param $x i64) + (result i64) + (local $y i64) + (local $hi i64) + (local.set $hi (i64.shl (call $endian_swap_32 (local.get $x)) (i64.const 32))) + (local.set $y (i64.or (local.get $hi) (call $endian_swap_32 (i64.shr_u (local.get $x) (i64.const 32))))) + (local.get $y) +) + +(func $return + (param $x1 i64) + (param $x2 i64) + (param $x3 i64) + (param $x4 i64) + (param $y1 i64) + (param $y2 i64) + (param $y3 i64) + (param $y4 i64) + (call $eth.finish (i32.wrap_i64 (call $to_internal_i32ptr (local.get $x1) (local.get $x2) (local.get $x3) (local.get $x4))) (i32.wrap_i64 (call $u256_to_i32 (local.get $y1) (local.get $y2) (local.get $y3) (local.get $y4)))) +) + +(func $revert + (param $x1 i64) + (param $x2 i64) + (param $x3 i64) + (param $x4 i64) + (param $y1 i64) + (param $y2 i64) + (param $y3 i64) + (param $y4 i64) + (call $eth.revert (i32.wrap_i64 (call $to_internal_i32ptr (local.get $x1) (local.get $x2) (local.get $x3) (local.get $x4))) (i32.wrap_i64 (call $u256_to_i32 (local.get $y1) (local.get $y2) (local.get $y3) (local.get $y4)))) +) + ) "}}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_irOptimized_requested/output.json b/test/cmdlineTests/standard_irOptimized_requested/output.json index 78ebe59c3..ba3cb8b2a 100644 --- a/test/cmdlineTests/standard_irOptimized_requested/output.json +++ b/test/cmdlineTests/standard_irOptimized_requested/output.json @@ -8,8 +8,12 @@ object \"C_6\" { code { mstore(64, 128) + if callvalue() { revert(0, 0) } + constructor_C_6() codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\")) return(0, datasize(\"C_6_deployed\")) + function constructor_C_6() + { } } object \"C_6_deployed\" { code { diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json index 2ceee9da0..99fcc85b9 100644 --- a/test/cmdlineTests/standard_ir_requested/output.json +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -9,15 +9,21 @@ object \"C_6\" { code { mstore(64, 128) + if callvalue() { revert(0, 0) } - // Begin state variable initialization for contract \"C\" (0 variables) - // End state variable initialization for contract \"C\". - + constructor_C_6() codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\")) return(0, datasize(\"C_6_deployed\")) + function constructor_C_6() { + + + + + } + } object \"C_6_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_ascii/output.json b/test/cmdlineTests/yul_string_format_ascii/output.json index 0e88aa5aa..7c5c7a0be 100644 --- a/test/cmdlineTests/yul_string_format_ascii/output.json +++ b/test/cmdlineTests/yul_string_format_ascii/output.json @@ -9,15 +9,21 @@ object \"C_10\" { code { mstore(64, 128) + if callvalue() { revert(0, 0) } - // Begin state variable initialization for contract \"C\" (0 variables) - // End state variable initialization for contract \"C\". - + constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) + function constructor_C_10() { + + + + + } + } object \"C_10_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json index 256b1e4e1..763be945e 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json @@ -9,15 +9,21 @@ object \"C_10\" { code { mstore(64, 128) + if callvalue() { revert(0, 0) } - // Begin state variable initialization for contract \"C\" (0 variables) - // End state variable initialization for contract \"C\". - + constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) + function constructor_C_10() { + + + + + } + } object \"C_10_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json index abb929c19..55b35f138 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json @@ -9,15 +9,21 @@ object \"C_10\" { code { mstore(64, 128) + if callvalue() { revert(0, 0) } - // Begin state variable initialization for contract \"C\" (0 variables) - // End state variable initialization for contract \"C\". - + constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) + function constructor_C_10() { + + + + + } + } object \"C_10_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_ascii_long/output.json b/test/cmdlineTests/yul_string_format_ascii_long/output.json index 991f9d0fd..289846431 100644 --- a/test/cmdlineTests/yul_string_format_ascii_long/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_long/output.json @@ -9,15 +9,21 @@ object \"C_10\" { code { mstore(64, 128) + if callvalue() { revert(0, 0) } - // Begin state variable initialization for contract \"C\" (0 variables) - // End state variable initialization for contract \"C\". - + constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) + function constructor_C_10() { + + + + + } + } object \"C_10_deployed\" { code { diff --git a/test/cmdlineTests/yul_string_format_hex/output.json b/test/cmdlineTests/yul_string_format_hex/output.json index ff84b1c32..726c6ccf2 100644 --- a/test/cmdlineTests/yul_string_format_hex/output.json +++ b/test/cmdlineTests/yul_string_format_hex/output.json @@ -9,15 +9,21 @@ object \"C_10\" { code { mstore(64, 128) + if callvalue() { revert(0, 0) } - // Begin state variable initialization for contract \"C\" (0 variables) - // End state variable initialization for contract \"C\". - + constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) + function constructor_C_10() { + + + + + } + } object \"C_10_deployed\" { code { diff --git a/test/libsolidity/semanticTests/constructor_ihneritance_init_order_2.sol b/test/libsolidity/semanticTests/constructor_ihneritance_init_order_2.sol new file mode 100644 index 000000000..db5f45560 --- /dev/null +++ b/test/libsolidity/semanticTests/constructor_ihneritance_init_order_2.sol @@ -0,0 +1,14 @@ +contract A { + uint x = 42; + function f() public returns(uint256) { + return x; + } +} +contract B is A { + uint public y = f(); +} +// ==== +// compileViaYul: true +// ---- +// constructor() -> +// y() -> 42 diff --git a/test/libsolidity/semanticTests/constructor_inheritance_init_order.sol b/test/libsolidity/semanticTests/constructor_inheritance_init_order.sol new file mode 100644 index 000000000..898edf6ad --- /dev/null +++ b/test/libsolidity/semanticTests/constructor_inheritance_init_order.sol @@ -0,0 +1,17 @@ +contract A { + uint x; + constructor() public { + x = 42; + } + function f() public returns(uint256) { + return x; + } +} +contract B is A { + uint public y = f(); +} +// ==== +// compileViaYul: true +// ---- +// constructor() -> +// y() -> 42 diff --git a/test/libsolidity/semanticTests/constructor_with_params_diamond_inheritance.sol b/test/libsolidity/semanticTests/constructor_with_params_diamond_inheritance.sol new file mode 100644 index 000000000..0ec347963 --- /dev/null +++ b/test/libsolidity/semanticTests/constructor_with_params_diamond_inheritance.sol @@ -0,0 +1,28 @@ +contract A { + uint public i; + uint public k; + + constructor(uint newI, uint newK) public { + i = newI; + k = newK; + } +} +abstract contract B is A { + uint public j; + constructor(uint newJ) public { + j = newJ; + } +} +contract C is A { + constructor(uint newI, uint newK) A(newI, newK) public {} +} +contract D is B, C { + constructor(uint newI, uint newK) B(newI) C(newI, newK + 1) public {} +} +// ==== +// compileViaYul: also +// ---- +// constructor(): 2, 0 -> +// i() -> 2 +// j() -> 2 +// k() -> 1 diff --git a/test/libsolidity/semanticTests/constructor_with_params_inheritance.sol b/test/libsolidity/semanticTests/constructor_with_params_inheritance.sol new file mode 100644 index 000000000..3419a9c25 --- /dev/null +++ b/test/libsolidity/semanticTests/constructor_with_params_inheritance.sol @@ -0,0 +1,18 @@ +contract C { + uint public i; + uint public k; + + constructor(uint newI, uint newK) public { + i = newI; + k = newK; + } +} +contract D is C { + constructor(uint newI, uint newK) C(newI, newK + 1) public {} +} +// ==== +// compileViaYul: also +// ---- +// constructor(): 2, 0 -> +// i() -> 2 +// k() -> 1 diff --git a/test/libsolidity/semanticTests/constructor_with_params_inheritance_2.sol b/test/libsolidity/semanticTests/constructor_with_params_inheritance_2.sol new file mode 100644 index 000000000..f2c39e75e --- /dev/null +++ b/test/libsolidity/semanticTests/constructor_with_params_inheritance_2.sol @@ -0,0 +1,15 @@ +contract C { + uint public i; + uint public k; + + constructor(uint newI, uint newK) public { + i = newI; + k = newK; + } +} +contract D is C(2, 1) {} +// ==== +// compileViaYul: also +// ---- +// i() -> 2 +// k() -> 1 diff --git a/test/libsolidity/semanticTests/state_variables_init_order.sol b/test/libsolidity/semanticTests/state_variables_init_order.sol new file mode 100644 index 000000000..ef63a38f9 --- /dev/null +++ b/test/libsolidity/semanticTests/state_variables_init_order.sol @@ -0,0 +1,14 @@ +contract A { + uint public x = 0; + uint y = f(); + function f() public returns (uint256) { + ++x; + return 42; + } +} +contract B is A { +} +// ==== +// compileViaYul: also +// ---- +// x() -> 1 \ No newline at end of file diff --git a/test/libsolidity/semanticTests/state_variables_init_order_2.sol b/test/libsolidity/semanticTests/state_variables_init_order_2.sol new file mode 100644 index 000000000..9bdf9c70c --- /dev/null +++ b/test/libsolidity/semanticTests/state_variables_init_order_2.sol @@ -0,0 +1,18 @@ +contract A { + uint public x = 0; + uint y = f(); + function f() public returns (uint256) { + ++x; + return 42; + } +} +contract B is A { + uint public z; + constructor() public { + z = x; + } +} +// ==== +// compileViaYul: also +// ---- +// z() -> 1 \ No newline at end of file diff --git a/test/libsolidity/semanticTests/state_variables_init_order_3.sol b/test/libsolidity/semanticTests/state_variables_init_order_3.sol new file mode 100644 index 000000000..81a5a693d --- /dev/null +++ b/test/libsolidity/semanticTests/state_variables_init_order_3.sol @@ -0,0 +1,29 @@ +contract A { + uint public a = 42; + uint public b; + uint public c; + constructor(uint x) public { + b = a; + a = x; + } + function f(uint x) public returns (uint256) { c = x * 3; return 23; } +} +contract B is A { + uint public d = f(a); + uint public e = b; + uint public b_a; + uint public b_b; + uint public b_c; + constructor() public A(17) { b_a = a; b_b = b; b_c = c; } +} +// ==== +// compileViaYul: true +// ---- +// a() -> 17 +// b() -> 42 +// c() -> 51 +// b_a() -> 17 +// b_b() -> 42 +// b_c() -> 51 +// d() -> 23 +// e() -> 42 From 83c9e82099cda58806ec638864e25c8f90d92dce Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Wed, 22 Apr 2020 19:57:00 +0200 Subject: [PATCH 08/55] Fix ICE with fixed point --- Changelog.md | 1 + libsolidity/formal/BMC.cpp | 5 ++++- libsolidity/formal/SMTEncoder.cpp | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 07ab24363..50a3e4d1e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ Compiler Features: Bugfixes: + * SMTChecker: Fix internal error when fixed points are used. * Type Checker: Disallow ``virtual`` and ``override`` for constructors. * Type Checker: Fix several internal errors by performing size and recursiveness checks of types before the full type checking. * Type Checker: Perform recursiveness check on structs declared at the file level. diff --git a/libsolidity/formal/BMC.cpp b/libsolidity/formal/BMC.cpp index 1b4e8174a..13a19694b 100644 --- a/libsolidity/formal/BMC.cpp +++ b/libsolidity/formal/BMC.cpp @@ -304,7 +304,10 @@ void BMC::endVisit(UnaryOperation const& _op) { SMTEncoder::endVisit(_op); - if (_op.annotation().type->category() == Type::Category::RationalNumber) + if ( + _op.annotation().type->category() == Type::Category::RationalNumber || + _op.annotation().type->category() == Type::Category::FixedPoint + ) return; switch (_op.getOperator()) diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 820af022e..12c9971dc 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -457,6 +457,9 @@ void SMTEncoder::endVisit(UnaryOperation const& _op) createExpr(_op); + if (_op.annotation().type->category() == Type::Category::FixedPoint) + return; + switch (_op.getOperator()) { case Token::Not: // ! From 1cb68b1be7881274d521435a6ef7dc14e472b5d2 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Wed, 22 Apr 2020 20:15:01 +0200 Subject: [PATCH 09/55] Add internal function calls to CHC docs --- docs/security-considerations.rst | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index b47370493..cf706fb7b 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -491,7 +491,8 @@ Horn clauses, where the lifecycle of the contract is represented by a loop that can visit every public/external function non-deterministically. This way, the behavior of the entire contract over an unbounded number of transactions is taken into account when analyzing any function. Loops are fully supported -by this engine. Function calls are currently unsupported. +by this engine. Internal function calls are supported, but external function +calls are currently unsupported. The CHC engine is much more powerful than BMC in terms of what it can prove, and might require more computing resources. @@ -505,10 +506,16 @@ erasing knowledge or using a non-precise type). If it determines that a verification target is safe, it is indeed safe, that is, there are no false negatives (unless there is a bug in the SMTChecker). -Function calls to the same contract (or base contracts) are inlined when -possible, that is, when their implementation is available. -Calls to functions in other contracts are not inlined even if their code is +In the BMC engine, function calls to the same contract (or base contracts) are +inlined when possible, that is, when their implementation is available. Calls +to functions in other contracts are not inlined even if their code is available, since we cannot guarantee that the actual deployed code is the same. + +The CHC engine creates nonlinear Horn clauses that use summaries of the called +functions to support internal function calls. The same approach can and will be +used for external function calls, but the latter requires more work regarding +the entire state of the blockchain and is still unimplemented. + Complex pure functions are abstracted by an uninterpreted function (UF) over the arguments. @@ -519,11 +526,14 @@ the arguments. +-----------------------------------+--------------------------------------+ |``require`` |Assumption | +-----------------------------------+--------------------------------------+ -|internal |Inline function call | +|internal |BMC: Inline function call | +| |CHC: Function summaries | +-----------------------------------+--------------------------------------+ -|external |Inline function call | -| |Erase knowledge about state variables | -| |and local storage references | +|external |BMC: Inline function call or | +| |erase knowledge about state variables | +| |and local storage references. | +| |CHC: Function summaries and erase | +| |state knowledge. | +-----------------------------------+--------------------------------------+ |``gasleft``, ``blockhash``, |Abstracted with UF | |``keccak256``, ``ecrecover`` | | @@ -534,8 +544,8 @@ the arguments. |implementation (external or | | |complex) | | +-----------------------------------+--------------------------------------+ -|external functions without |Unsupported | -|implementation | | +|external functions without |BMC: Unsupported | +|implementation |CHC: Nondeterministic summary | +-----------------------------------+--------------------------------------+ |others |Currently unsupported | +-----------------------------------+--------------------------------------+ From b191139f2a299da4a036beb416d2637dd01195e6 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Wed, 22 Apr 2020 20:41:24 +0200 Subject: [PATCH 10/55] Fix undefined behavior with nullptr --- libsolidity/formal/Z3Interface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index f175e45d2..a84f39bb1 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -237,8 +237,8 @@ z3::sort Z3Interface::z3Sort(Sort const& _sort) z3::func_decl tupleConstructor = m_context.tuple_sort( tupleSort.name.c_str(), tupleSort.members.size(), - &cMembers[0], - &sorts[0], + cMembers.data(), + sorts.data(), projs ); return tupleConstructor.range(); From cfe368611655194b0bb1be5d7513b606135f4454 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Wed, 22 Apr 2020 18:54:38 +0200 Subject: [PATCH 11/55] Fix internal error when using array slices --- Changelog.md | 1 + libsolidity/formal/SymbolicTypes.cpp | 13 ++++++++++--- .../smtCheckerTests/operators/slices_1.sol | 13 +++++++++++++ .../smtCheckerTests/typecast/slice_to_bytes.sol | 13 +++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 test/libsolidity/smtCheckerTests/operators/slices_1.sol create mode 100644 test/libsolidity/smtCheckerTests/typecast/slice_to_bytes.sol diff --git a/Changelog.md b/Changelog.md index 50a3e4d1e..f37d9e096 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,7 @@ Compiler Features: Bugfixes: * SMTChecker: Fix internal error when fixed points are used. + * SMTChecker: Fix internal error when using array slices. * Type Checker: Disallow ``virtual`` and ``override`` for constructors. * Type Checker: Fix several internal errors by performing size and recursiveness checks of types before the full type checking. * Type Checker: Perform recursiveness check on structs declared at the file level. diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index 0364a0458..b8da5da0c 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -69,8 +69,14 @@ SortPointer smtSort(frontend::Type const& _type) } else { - solAssert(isArray(_type.category()), ""); - auto arrayType = dynamic_cast(&_type); + frontend::ArrayType const* arrayType = nullptr; + if (auto const* arr = dynamic_cast(&_type)) + arrayType = arr; + else if (auto const* slice = dynamic_cast(&_type)) + arrayType = &slice->arrayType(); + else + solAssert(false, ""); + solAssert(arrayType, ""); return make_shared(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType())); } @@ -297,7 +303,8 @@ bool isMapping(frontend::Type::Category _category) bool isArray(frontend::Type::Category _category) { return _category == frontend::Type::Category::Array || - _category == frontend::Type::Category::StringLiteral; + _category == frontend::Type::Category::StringLiteral || + _category == frontend::Type::Category::ArraySlice; } bool isTuple(frontend::Type::Category _category) diff --git a/test/libsolidity/smtCheckerTests/operators/slices_1.sol b/test/libsolidity/smtCheckerTests/operators/slices_1.sol new file mode 100644 index 000000000..c723472a8 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/operators/slices_1.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C { + function f(bytes calldata x) external pure { + x[:18726387213]; + x[18726387213:]; + x[18726387213:111111111111111111]; + } +} +// ---- +// Warning: (94-109): Assertion checker does not yet implement this expression. +// Warning: (113-128): Assertion checker does not yet implement this expression. +// Warning: (132-165): Assertion checker does not yet implement this expression. diff --git a/test/libsolidity/smtCheckerTests/typecast/slice_to_bytes.sol b/test/libsolidity/smtCheckerTests/typecast/slice_to_bytes.sol new file mode 100644 index 000000000..1fb74bac9 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/slice_to_bytes.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C { + function f(bytes calldata x) external pure { + bytes(x[:18726387213]); + bytes(x[18726387213:]); + bytes(x[18726387213:111111111111111111]); + } +} +// ---- +// Warning: (100-115): Assertion checker does not yet implement this expression. +// Warning: (126-141): Assertion checker does not yet implement this expression. +// Warning: (152-185): Assertion checker does not yet implement this expression. From 615668bfb4dd5fbe7df0619b537f17e680e2b6fb Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Apr 2020 01:35:21 +0200 Subject: [PATCH 12/55] Explain nonpayable Fixes https://github.com/ethereum/solidity/issues/8736 --- docs/abi-spec.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 59636acb6..ec31654dd 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -507,13 +507,17 @@ A function description is a JSON object with the fields: - ``outputs``: an array of objects similar to ``inputs``. - ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state `), ``view`` (:ref:`specified to not modify the blockchain - state `), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether). + state `), ``nonpayable`` (function does not accept Ether - the default) and ``payable`` (function accepts Ether). Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either. .. note:: Sending non-zero Ether to non-payable function will revert the transaction. +.. note:: + The state mutability ``nonpayable`` is reflected in Solidity by not specifying + a state mutability modifier at all. + An event description is a JSON object with fairly similar fields: - ``type``: always ``"event"`` From 9538024c81368d866788a360ab81e527c57a38bd Mon Sep 17 00:00:00 2001 From: a3d4 Date: Thu, 23 Apr 2020 05:48:02 +0200 Subject: [PATCH 13/55] Fix #8711, #8277 --- Changelog.md | 1 + libsolidity/analysis/TypeChecker.cpp | 2 +- .../tupleAssignments/empty_tuples_lhs.sol | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol diff --git a/Changelog.md b/Changelog.md index 50a3e4d1e..30f0e3368 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,7 @@ Bugfixes: * SMTChecker: Fix internal error when fixed points are used. * Type Checker: Disallow ``virtual`` and ``override`` for constructors. * Type Checker: Fix several internal errors by performing size and recursiveness checks of types before the full type checking. + * Type Checker: Fix internal error when assigning to empty tuples. * Type Checker: Perform recursiveness check on structs declared at the file level. Build System: diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 17add7a66..db88f20bf 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1289,7 +1289,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const& if (auto const* tupleExpression = dynamic_cast(&_expression)) { auto const* tupleType = dynamic_cast(&_type); - auto const& types = tupleType && tupleExpression->components().size() > 1 ? tupleType->components() : vector { &_type }; + auto const& types = tupleType && tupleExpression->components().size() != 1 ? tupleType->components() : vector { &_type }; solAssert( tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(), diff --git a/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol b/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol new file mode 100644 index 000000000..390a3636a --- /dev/null +++ b/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol @@ -0,0 +1,24 @@ +contract C { + function f0() public { (()) = 2; } + + function f1() public pure { (()) = (); } + + //#8711 + function f2() internal pure returns (uint, uint) { return () = f2(); } + + //#8277 + function f3()public{return()=();} + + //#8277 + function f4 ( bytes32 hash , uint8 v , bytes32 r , bytes32 s , uint blockExpired , bytes32 salt ) public returns ( address ) { + require ( ( ( ) ) |= keccak256 ( abi . encodePacked ( blockExpired , salt ) ) ) ; + return ecrecover ( hash , v , r , s ) ; + } +} +// ---- +// TypeError: (47-48): Type int_const 2 is not implicitly convertible to expected type tuple(). +// TypeError: (178-182): Type tuple(uint256,uint256) is not implicitly convertible to expected type tuple(). +// TypeError: (166-182): Different number of arguments in return statement than in returns declaration. +// TypeError: (399-466): Compound assignment is not allowed for tuple types. +// TypeError: (410-466): Type bytes32 is not implicitly convertible to expected type tuple(). +// TypeError: (389-396): No matching declaration found after argument-dependent lookup. From c4bc77874bf029f60daecbba959e196708ff4124 Mon Sep 17 00:00:00 2001 From: a3d4 Date: Thu, 23 Apr 2020 07:02:04 +0200 Subject: [PATCH 14/55] Disallow empty tuples on the left hand side --- libsolidity/analysis/TypeChecker.cpp | 3 +++ .../syntaxTests/tupleAssignments/empty_tuples_lhs.sol | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index db88f20bf..f0248538e 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1288,6 +1288,9 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const& { if (auto const* tupleExpression = dynamic_cast(&_expression)) { + if (tupleExpression->components().empty()) + m_errorReporter.typeError(_expression.location(), "Empty tuple on the left hand side."); + auto const* tupleType = dynamic_cast(&_type); auto const& types = tupleType && tupleExpression->components().size() != 1 ? tupleType->components() : vector { &_type }; diff --git a/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol b/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol index 390a3636a..a771f630a 100644 --- a/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol +++ b/test/libsolidity/syntaxTests/tupleAssignments/empty_tuples_lhs.sol @@ -16,9 +16,14 @@ contract C { } } // ---- +// TypeError: (41-43): Empty tuple on the left hand side. // TypeError: (47-48): Type int_const 2 is not implicitly convertible to expected type tuple(). +// TypeError: (86-88): Empty tuple on the left hand side. +// TypeError: (173-175): Empty tuple on the left hand side. // TypeError: (178-182): Type tuple(uint256,uint256) is not implicitly convertible to expected type tuple(). // TypeError: (166-182): Different number of arguments in return statement than in returns declaration. +// TypeError: (229-231): Empty tuple on the left hand side. +// TypeError: (401-404): Empty tuple on the left hand side. // TypeError: (399-466): Compound assignment is not allowed for tuple types. // TypeError: (410-466): Type bytes32 is not implicitly convertible to expected type tuple(). // TypeError: (389-396): No matching declaration found after argument-dependent lookup. From f78414b3337af41d00aabc2784b8d5b072be6d6d Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Wed, 22 Apr 2020 15:06:42 +0200 Subject: [PATCH 15/55] Disallow statements containing empty blocks e.g., empty if, for, function definition --- test/tools/ossfuzz/protoToYul.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index 5414405c6..5589043de 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -1302,19 +1302,23 @@ void ProtoConverter::visit(Statement const& _x) visit(_x.assignment()); break; case Statement::kIfstmt: - visit(_x.ifstmt()); + if (_x.ifstmt().if_body().statements_size() > 0) + visit(_x.ifstmt()); break; case Statement::kStorageFunc: visit(_x.storage_func()); break; case Statement::kBlockstmt: - visit(_x.blockstmt()); + if (_x.blockstmt().statements_size() > 0) + visit(_x.blockstmt()); break; case Statement::kForstmt: - visit(_x.forstmt()); + if (_x.forstmt().for_body().statements_size() > 0) + visit(_x.forstmt()); break; case Statement::kBoundedforstmt: - visit(_x.boundedforstmt()); + if (_x.boundedforstmt().for_body().statements_size() > 0) + visit(_x.boundedforstmt()); break; case Statement::kSwitchstmt: visit(_x.switchstmt()); @@ -1346,8 +1350,9 @@ void ProtoConverter::visit(Statement const& _x) visit(_x.functioncall()); break; case Statement::kFuncdef: - if (!m_inForInitScope) - visit(_x.funcdef()); + if (_x.funcdef().block().statements_size() > 0) + if (!m_inForInitScope) + visit(_x.funcdef()); break; case Statement::kPop: visit(_x.pop()); @@ -1524,7 +1529,7 @@ void ProtoConverter::visit(Block const& _x) // scope belongs to for-init (in which function declarations // are forbidden). for (auto const& statement: _x.statements()) - if (statement.has_funcdef() && !m_inForInitScope) + if (statement.has_funcdef() && statement.funcdef().block().statements_size() > 0 && !m_inForInitScope) registerFunction(&statement.funcdef()); if (_x.statements_size() > 0) From 92059fa84848ce7f78d93a8af720bef034b74fde Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Thu, 23 Apr 2020 10:40:57 +0200 Subject: [PATCH 16/55] Use Spacer option to improve performance of constant arrays --- libsolidity/formal/Z3CHCInterface.cpp | 2 ++ .../functions/functions_recursive_indirect.sol | 1 - ...for_loop_array_assignment_storage_memory.sol | 4 +--- ...or_loop_array_assignment_storage_storage.sol | 1 - ...le_loop_array_assignment_storage_storage.sol | 6 ++---- .../operators/delete_array_index_2d.sol | 1 + .../smtCheckerTests/types/unused_mapping.sol | 17 +++++++++++++++++ 7 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 test/libsolidity/smtCheckerTests/types/unused_mapping.sol diff --git a/libsolidity/formal/Z3CHCInterface.cpp b/libsolidity/formal/Z3CHCInterface.cpp index c64da2edc..dcfd19c86 100644 --- a/libsolidity/formal/Z3CHCInterface.cpp +++ b/libsolidity/formal/Z3CHCInterface.cpp @@ -43,6 +43,8 @@ Z3CHCInterface::Z3CHCInterface(): p.set("fp.spacer.mbqi", false); // Ground pobs by using values from a model. p.set("fp.spacer.ground_pobs", false); + // Limits array reasoning, good for constant arrays. + p.set("fp.spacer.weak_abs", false); m_solver.set(p); } diff --git a/test/libsolidity/smtCheckerTests/functions/functions_recursive_indirect.sol b/test/libsolidity/smtCheckerTests/functions/functions_recursive_indirect.sol index 5d3292992..7cb7b22b1 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_recursive_indirect.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_recursive_indirect.sol @@ -22,4 +22,3 @@ contract C } } // ---- -// Warning: (130-144): Error trying to invoke SMT solver. diff --git a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol index 0b301505b..d7d88424a 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol @@ -12,12 +12,10 @@ contract LoopFor2 { b[i] = i + 1; c[i] = b[i]; } - // This is safe but too hard to solve currently. assert(b[0] == c[0]); assert(a[0] == 900); assert(b[0] == 900); } } // ---- -// Warning: (316-336): Assertion violation happens here -// Warning: (363-382): Assertion violation happens here +// Warning: (312-331): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol index 333273781..a90053024 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol @@ -19,6 +19,5 @@ contract LoopFor2 { } } // ---- -// Warning: (317-337): Assertion violation happens here // Warning: (341-360): Assertion violation happens here // Warning: (364-383): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol index 92d1ded3e..7670ddb6d 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol @@ -14,13 +14,11 @@ contract LoopFor2 { c[i] = b[i]; ++i; } - // Fails as false positive. assert(b[0] == c[0]); assert(a[0] == 900); assert(b[0] == 900); } } // ---- -// Warning: (296-316): Assertion violation happens here -// Warning: (320-339): Assertion violation happens here -// Warning: (343-362): Assertion violation happens here +// Warning: (290-309): Assertion violation happens here +// Warning: (313-332): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol b/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol index 8a1ba5e6e..cb5a0799f 100644 --- a/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol +++ b/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol @@ -16,4 +16,5 @@ contract C // ==== // SMTSolvers: z3 // ---- +// Warning: (174-194): Error trying to invoke SMT solver. // Warning: (174-194): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/unused_mapping.sol b/test/libsolidity/smtCheckerTests/types/unused_mapping.sol new file mode 100644 index 000000000..f12cd41de --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/unused_mapping.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract C { + uint x; + uint y; + mapping (address => bool) public never_used; + + function inc() public { + require(x < 10); + require(y < 10); + + if(x == 0) x = 0; // noop state var read + x++; + y++; + assert(y == x); + } +} From b864fe1c43b9bb85fb7ab9cc41f7251d2fa5ef66 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Apr 2020 12:13:26 +0200 Subject: [PATCH 17/55] Remove unnecessary move. --- libsolidity/codegen/ir/IRGeneratorForStatements.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index e375739d0..711f0f1f6 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1069,7 +1069,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm) solAssert(holds_alternative(modified), ""); // Do not provide dialect so that we get the full type information. - m_code << yul::AsmPrinter()(std::get(std::move(modified))) << "\n"; + m_code << yul::AsmPrinter()(std::get(modified)) << "\n"; return false; } From 35eae96a7f93a16d939f348e22d33ad112007e7d Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Apr 2020 13:16:10 +0200 Subject: [PATCH 18/55] Move helper up and avoid trailing spaces. --- test/TestCase.cpp | 13 +++++++++++++ test/TestCase.h | 2 ++ test/libsolidity/ABIJsonTest.cpp | 15 --------------- test/libsolidity/ABIJsonTest.h | 2 -- test/libyul/EwasmTranslationTest.cpp | 8 -------- test/libyul/EwasmTranslationTest.h | 1 - test/libyul/YulInterpreterTest.cpp | 8 -------- test/libyul/YulInterpreterTest.h | 1 - test/libyul/YulOptimizerTest.cpp | 8 -------- test/libyul/YulOptimizerTest.h | 1 - 10 files changed, 15 insertions(+), 44 deletions(-) diff --git a/test/TestCase.cpp b/test/TestCase.cpp index f952e40bd..e0897ae47 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -60,6 +61,18 @@ void TestCase::expect(string::iterator& _it, string::iterator _end, string::valu ++_it; } +void TestCase::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const +{ + stringstream output(_output); + string line; + while (getline(output, line)) + if (line.empty()) + // Avoid trailing spaces. + _stream << boost::trim_right_copy(_linePrefix) << endl; + else + _stream << _linePrefix << line << endl; +} + EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(string const& _filename): TestCase(_filename) { diff --git a/test/TestCase.h b/test/TestCase.h index 0add6947b..8ec3086fe 100644 --- a/test/TestCase.h +++ b/test/TestCase.h @@ -93,6 +93,8 @@ protected: ++_it; } + void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; + TestCaseReader m_reader; bool m_shouldRun = true; }; diff --git a/test/libsolidity/ABIJsonTest.cpp b/test/libsolidity/ABIJsonTest.cpp index e7a0cce6d..d144e2ee7 100644 --- a/test/libsolidity/ABIJsonTest.cpp +++ b/test/libsolidity/ABIJsonTest.cpp @@ -26,8 +26,6 @@ #include #include -#include - #include using namespace std; @@ -85,16 +83,3 @@ void ABIJsonTest::printUpdatedExpectations(ostream& _stream, string const& _line { printIndented(_stream, m_obtainedResult, _linePrefix); } - -void ABIJsonTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const -{ - stringstream output(_output); - string line; - while (getline(output, line)) - if (line.empty()) - // Avoid trailing spaces. - _stream << boost::trim_right_copy(_linePrefix) << endl; - else - _stream << _linePrefix << line << endl; -} - diff --git a/test/libsolidity/ABIJsonTest.h b/test/libsolidity/ABIJsonTest.h index 9193dc750..3a7cd397c 100644 --- a/test/libsolidity/ABIJsonTest.h +++ b/test/libsolidity/ABIJsonTest.h @@ -41,8 +41,6 @@ public: void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; private: - void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; - std::string m_source; std::string m_expectation; std::string m_obtainedResult; diff --git a/test/libyul/EwasmTranslationTest.cpp b/test/libyul/EwasmTranslationTest.cpp index a545b6124..46ede4221 100644 --- a/test/libyul/EwasmTranslationTest.cpp +++ b/test/libyul/EwasmTranslationTest.cpp @@ -94,14 +94,6 @@ void EwasmTranslationTest::printUpdatedExpectations(ostream& _stream, string con printIndented(_stream, m_obtainedResult, _linePrefix); } -void EwasmTranslationTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const -{ - stringstream output(_output); - string line; - while (getline(output, line)) - _stream << _linePrefix << line << endl; -} - bool EwasmTranslationTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) { AssemblyStack stack( diff --git a/test/libyul/EwasmTranslationTest.h b/test/libyul/EwasmTranslationTest.h index 68b640032..f9c6e1d3c 100644 --- a/test/libyul/EwasmTranslationTest.h +++ b/test/libyul/EwasmTranslationTest.h @@ -46,7 +46,6 @@ public: void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; private: - void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; bool parse(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted); std::string interpret(); diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index 937ffb57e..69e2c8d10 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -82,14 +82,6 @@ void YulInterpreterTest::printUpdatedExpectations(ostream& _stream, string const printIndented(_stream, m_obtainedResult, _linePrefix); } -void YulInterpreterTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const -{ - stringstream output(_output); - string line; - while (getline(output, line)) - _stream << _linePrefix << line << endl; -} - bool YulInterpreterTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) { AssemblyStack stack( diff --git a/test/libyul/YulInterpreterTest.h b/test/libyul/YulInterpreterTest.h index 5fb6d02b3..ec60dbaf4 100644 --- a/test/libyul/YulInterpreterTest.h +++ b/test/libyul/YulInterpreterTest.h @@ -51,7 +51,6 @@ public: void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; private: - void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; bool parse(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted); std::string interpret(); diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 9458174ed..2a5209b25 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -375,14 +375,6 @@ void YulOptimizerTest::printUpdatedExpectations(ostream& _stream, string const& printIndented(_stream, m_obtainedResult, _linePrefix); } -void YulOptimizerTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const -{ - stringstream output(_output); - string line; - while (getline(output, line)) - _stream << _linePrefix << line << endl; -} - bool YulOptimizerTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) { ErrorList errors; diff --git a/test/libyul/YulOptimizerTest.h b/test/libyul/YulOptimizerTest.h index 99e239e3b..374ea9b19 100644 --- a/test/libyul/YulOptimizerTest.h +++ b/test/libyul/YulOptimizerTest.h @@ -60,7 +60,6 @@ public: void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; private: - void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; bool parse(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted); void disambiguate(); void updateContext(); From f82f16729053d00d3457234e5b50f2d0dd8d7c17 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 22 Apr 2020 10:49:44 +0200 Subject: [PATCH 19/55] Yul formatting: Reduce multiple consecutive empty lines to a single one. --- libyul/Utilities.cpp | 7 +++++++ test/cmdlineTests/standard_ir_requested/output.json | 6 ------ test/cmdlineTests/yul_string_format_ascii/output.json | 8 -------- .../yul_string_format_ascii_bytes32/output.json | 5 ----- .../output.json | 5 ----- .../cmdlineTests/yul_string_format_ascii_long/output.json | 8 -------- test/cmdlineTests/yul_string_format_hex/output.json | 5 ----- 7 files changed, 7 insertions(+), 37 deletions(-) diff --git a/libyul/Utilities.cpp b/libyul/Utilities.cpp index dfe72b05e..8789381b7 100644 --- a/libyul/Utilities.cpp +++ b/libyul/Utilities.cpp @@ -59,6 +59,13 @@ string solidity::yul::reindent(string const& _code) for (string& line: lines) boost::trim(line); + // Reduce multiple consecutive empty lines. + lines = fold(lines, vector{}, [](auto&& _lines, auto&& _line) { + if (!(_line.empty() && !_lines.empty() && _lines.back().empty())) + _lines.emplace_back(std::move(_line)); + return std::move(_lines); + }); + stringstream out; int depth = 0; diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json index 99fcc85b9..2e7aef192 100644 --- a/test/cmdlineTests/standard_ir_requested/output.json +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -16,12 +16,8 @@ object \"C_6\" { codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\")) return(0, datasize(\"C_6_deployed\")) - function constructor_C_6() { - - - } } @@ -50,7 +46,6 @@ object \"C_6\" { if iszero(calldatasize()) { } revert(0, 0) - function abi_decode_tuple_(headStart, dataEnd) { if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } @@ -71,7 +66,6 @@ object \"C_6\" { function fun_f_5() { - } function shift_right_224_unsigned(value) -> newValue { diff --git a/test/cmdlineTests/yul_string_format_ascii/output.json b/test/cmdlineTests/yul_string_format_ascii/output.json index 7c5c7a0be..0ca1c28dc 100644 --- a/test/cmdlineTests/yul_string_format_ascii/output.json +++ b/test/cmdlineTests/yul_string_format_ascii/output.json @@ -16,12 +16,8 @@ object \"C_10\" { codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) - function constructor_C_10() { - - - } } @@ -50,7 +46,6 @@ object \"C_10\" { if iszero(calldatasize()) { } revert(0, 0) - function abi_decode_tuple_(headStart, dataEnd) { if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } @@ -81,11 +76,8 @@ object \"C_10\" { function array_length_t_string_memory_ptr(value) -> length { - length := mload(value) - - } function array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) -> updated_pos { diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json index 763be945e..062a56a54 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json @@ -16,12 +16,8 @@ object \"C_10\" { codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) - function constructor_C_10() { - - - } } @@ -50,7 +46,6 @@ object \"C_10\" { if iszero(calldatasize()) { } revert(0, 0) - function abi_decode_tuple_(headStart, dataEnd) { if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json index 55b35f138..6779e68c0 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json @@ -16,12 +16,8 @@ object \"C_10\" { codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) - function constructor_C_10() { - - - } } @@ -50,7 +46,6 @@ object \"C_10\" { if iszero(calldatasize()) { } revert(0, 0) - function abi_decode_tuple_(headStart, dataEnd) { if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } diff --git a/test/cmdlineTests/yul_string_format_ascii_long/output.json b/test/cmdlineTests/yul_string_format_ascii_long/output.json index 289846431..4b4b11347 100644 --- a/test/cmdlineTests/yul_string_format_ascii_long/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_long/output.json @@ -16,12 +16,8 @@ object \"C_10\" { codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) - function constructor_C_10() { - - - } } @@ -50,7 +46,6 @@ object \"C_10\" { if iszero(calldatasize()) { } revert(0, 0) - function abi_decode_tuple_(headStart, dataEnd) { if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } @@ -81,11 +76,8 @@ object \"C_10\" { function array_length_t_string_memory_ptr(value) -> length { - length := mload(value) - - } function array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) -> updated_pos { diff --git a/test/cmdlineTests/yul_string_format_hex/output.json b/test/cmdlineTests/yul_string_format_hex/output.json index 726c6ccf2..d011fdade 100644 --- a/test/cmdlineTests/yul_string_format_hex/output.json +++ b/test/cmdlineTests/yul_string_format_hex/output.json @@ -16,12 +16,8 @@ object \"C_10\" { codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\")) - function constructor_C_10() { - - - } } @@ -50,7 +46,6 @@ object \"C_10\" { if iszero(calldatasize()) { } revert(0, 0) - function abi_decode_tuple_(headStart, dataEnd) { if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } From e2e32d372ffb378fc2115cbf5b67ec80f77192c5 Mon Sep 17 00:00:00 2001 From: hrkrshnn Date: Wed, 15 Apr 2020 16:12:15 +0530 Subject: [PATCH 20/55] virtual modifiers (in Abstract contracts) allow empty bodies --- libsolidity/analysis/ContractLevelChecker.cpp | 65 ++++++++----------- libsolidity/analysis/ContractLevelChecker.h | 3 +- libsolidity/analysis/ImmutableValidator.cpp | 3 +- libsolidity/analysis/OverrideChecker.cpp | 12 +++- libsolidity/analysis/OverrideChecker.h | 2 + libsolidity/analysis/SyntaxChecker.cpp | 2 +- libsolidity/analysis/TypeChecker.cpp | 6 ++ libsolidity/analysis/TypeChecker.h | 1 + libsolidity/ast/AST.h | 9 +-- libsolidity/ast/ASTAnnotations.h | 4 +- libsolidity/ast/ASTJsonConverter.cpp | 4 +- libsolidity/ast/ASTJsonImporter.cpp | 2 +- libsolidity/ast/AST_accept.h | 6 +- libsolidity/ast/Types.h | 1 - libsolidity/formal/SMTEncoder.cpp | 3 +- libsolidity/parsing/Parser.cpp | 17 +++-- .../SolidityNameAndTypeResolution.cpp | 12 ++-- tools/solidityUpgrade/Upgrade060.cpp | 2 +- 18 files changed, 87 insertions(+), 67 deletions(-) diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index 27323c544..18051f99b 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -55,7 +55,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract) checkDuplicateEvents(_contract); m_overrideChecker.check(_contract); checkBaseConstructorArguments(_contract); - checkAbstractFunctions(_contract); + checkAbstractDefinitions(_contract); checkExternalTypeClashes(_contract); checkHashCollisions(_contract); checkLibraryRequirements(_contract); @@ -156,55 +156,43 @@ void ContractLevelChecker::findDuplicateDefinitions(map> const } } -void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _contract) +void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _contract) { - // Mapping from name to function definition (exactly one per argument type equality class) and - // flag to indicate whether it is fully implemented. - using FunTypeAndFlag = std::pair; - map> functions; + // Collects functions, static variable getters and modifiers. If they + // override (unimplemented) base class ones, they are replaced. + set proxies; - auto registerFunction = [&](Declaration const& _declaration, FunctionTypePointer const& _type, bool _implemented) + auto registerProxy = [&proxies](OverrideProxy const& _overrideProxy) { - auto& overloads = functions[_declaration.name()]; - auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) - { - return _type->hasEqualParameterTypes(*_funAndFlag.first); - }); - if (it == overloads.end()) - overloads.emplace_back(_type, _implemented); - else if (_implemented) - it->second = true; + // Overwrite an existing proxy, if it exists. + if (!_overrideProxy.unimplemented()) + proxies.erase(_overrideProxy); + + proxies.insert(_overrideProxy); }; - // Search from base to derived, collect all functions and update - // the 'implemented' flag. + // Search from base to derived, collect all functions and modifiers and + // update proxies. for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) { for (VariableDeclaration const* v: contract->stateVariables()) if (v->isPartOfExternalInterface()) - registerFunction(*v, TypeProvider::function(*v), true); + registerProxy(OverrideProxy(v)); for (FunctionDefinition const* function: contract->definedFunctions()) if (!function->isConstructor()) - registerFunction( - *function, - TypeProvider::function(*function)->asCallableFunction(false), - function->isImplemented() - ); + registerProxy(OverrideProxy(function)); + + for (ModifierDefinition const* modifier: contract->functionModifiers()) + registerProxy(OverrideProxy(modifier)); } // Set to not fully implemented if at least one flag is false. - // Note that `_contract.annotation().unimplementedFunctions` has already been + // Note that `_contract.annotation().unimplementedDeclarations` has already been // pre-filled by `checkBaseConstructorArguments`. - for (auto const& it: functions) - for (auto const& funAndFlag: it.second) - if (!funAndFlag.second) - { - FunctionDefinition const* function = dynamic_cast(&funAndFlag.first->declaration()); - solAssert(function, ""); - _contract.annotation().unimplementedFunctions.push_back(function); - break; - } + for (auto const& proxy: proxies) + if (proxy.unimplemented()) + _contract.annotation().unimplementedDeclarations.push_back(proxy.declaration()); if (_contract.abstract()) { @@ -221,15 +209,16 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con if ( _contract.contractKind() == ContractKind::Contract && !_contract.abstract() && - !_contract.annotation().unimplementedFunctions.empty() + !_contract.annotation().unimplementedDeclarations.empty() ) { SecondarySourceLocation ssl; - for (auto function: _contract.annotation().unimplementedFunctions) - ssl.append("Missing implementation:", function->location()); + for (auto declaration: _contract.annotation().unimplementedDeclarations) + ssl.append("Missing implementation: ", declaration->location()); m_errorReporter.typeError(_contract.location(), ssl, "Contract \"" + _contract.annotation().canonicalName + "\" should be marked as abstract."); + } } @@ -277,7 +266,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons if (FunctionDefinition const* constructor = contract->constructor()) if (contract != &_contract && !constructor->parameters().empty()) if (!_contract.annotation().baseConstructorArguments.count(constructor)) - _contract.annotation().unimplementedFunctions.push_back(constructor); + _contract.annotation().unimplementedDeclarations.push_back(constructor); } void ContractLevelChecker::annotateBaseConstructorArguments( diff --git a/libsolidity/analysis/ContractLevelChecker.h b/libsolidity/analysis/ContractLevelChecker.h index 919c35c07..736b3d3d7 100644 --- a/libsolidity/analysis/ContractLevelChecker.h +++ b/libsolidity/analysis/ContractLevelChecker.h @@ -61,7 +61,8 @@ private: void checkDuplicateEvents(ContractDefinition const& _contract); template void findDuplicateDefinitions(std::map> const& _definitions, std::string _message); - void checkAbstractFunctions(ContractDefinition const& _contract); + /// Checks for unimplemented functions and modifiers. + void checkAbstractDefinitions(ContractDefinition const& _contract); /// Checks that the base constructor arguments are properly provided. /// Fills the list of unimplemented functions in _contract's annotations. void checkBaseConstructorArguments(ContractDefinition const& _contract); diff --git a/libsolidity/analysis/ImmutableValidator.cpp b/libsolidity/analysis/ImmutableValidator.cpp index b03d314eb..598f43e50 100644 --- a/libsolidity/analysis/ImmutableValidator.cpp +++ b/libsolidity/analysis/ImmutableValidator.cpp @@ -148,7 +148,8 @@ bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDec funcDef->body().accept(*this); } else if (ModifierDefinition const* modDef = dynamic_cast(&_callableDeclaration)) - modDef->body().accept(*this); + if (modDef->isImplemented()) + modDef->body().accept(*this); m_currentConstructor = prevConstructor; diff --git a/libsolidity/analysis/OverrideChecker.cpp b/libsolidity/analysis/OverrideChecker.cpp index 8367f9f58..91b45d33c 100644 --- a/libsolidity/analysis/OverrideChecker.cpp +++ b/libsolidity/analysis/OverrideChecker.cpp @@ -327,6 +327,16 @@ ModifierType const* OverrideProxy::modifierType() const }, m_item); } + +Declaration const* OverrideProxy::declaration() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const* _function) -> Declaration const* { return _function; }, + [&](VariableDeclaration const* _variable) -> Declaration const* { return _variable; }, + [&](ModifierDefinition const* _modifier) -> Declaration const* { return _modifier; } + }, m_item); +} + SourceLocation const& OverrideProxy::location() const { return std::visit(GenericVisitor{ @@ -365,7 +375,7 @@ bool OverrideProxy::unimplemented() const { return std::visit(GenericVisitor{ [&](FunctionDefinition const* _item) { return !_item->isImplemented(); }, - [&](ModifierDefinition const*) { return false; }, + [&](ModifierDefinition const* _item) { return !_item->isImplemented(); }, [&](VariableDeclaration const*) { return false; } }, m_item); } diff --git a/libsolidity/analysis/OverrideChecker.h b/libsolidity/analysis/OverrideChecker.h index d4b96663c..9d7776028 100644 --- a/libsolidity/analysis/OverrideChecker.h +++ b/libsolidity/analysis/OverrideChecker.h @@ -85,6 +85,8 @@ public: FunctionType const* functionType() const; ModifierType const* modifierType() const; + Declaration const* declaration() const; + langutil::SourceLocation const& location() const; std::string astNodeName() const; diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index ba973bffa..c9ad29d33 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -141,7 +141,7 @@ bool SyntaxChecker::visit(ModifierDefinition const&) void SyntaxChecker::endVisit(ModifierDefinition const& _modifier) { - if (!m_placeholderFound) + if (_modifier.isImplemented() && !m_placeholderFound) m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'."); m_placeholderFound = false; } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 250607fe0..a0155c9ce 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -289,6 +289,12 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected."); } +void TypeChecker::endVisit(ModifierDefinition const& _modifier) +{ + if (!_modifier.isImplemented() && !_modifier.virtualSemantics()) + m_errorReporter.typeError(_modifier.location(), "Modifiers without implementation must be marked virtual."); +} + bool TypeChecker::visit(FunctionDefinition const& _function) { bool isLibraryFunction = _function.inContractKind() == ContractKind::Library; diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 585f7d95f..5c327bf2e 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -112,6 +112,7 @@ private: void endVisit(InheritanceSpecifier const& _inheritance) override; void endVisit(UsingForDirective const& _usingFor) override; + void endVisit(ModifierDefinition const& _modifier) override; bool visit(FunctionDefinition const& _function) override; bool visit(VariableDeclaration const& _variable) override; /// We need to do this manually because we want to pass the bases of the current contract in diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 926d4e74a..3c79912c2 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -969,7 +969,7 @@ private: /** * Definition of a function modifier. */ -class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented +class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional { public: ModifierDefinition( @@ -980,18 +980,19 @@ public: ASTPointer const& _parameters, bool _isVirtual, ASTPointer const& _overrides, - ASTPointer _body + ASTPointer const& _body ): CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides), StructurallyDocumented(_documentation), - m_body(std::move(_body)) + ImplementationOptional(_body != nullptr), + m_body(_body) { } void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; - Block const& body() const { return *m_body; } + Block const& body() const { solAssert(m_body, ""); return *m_body; } TypePointer type() const override; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 2589103cb..0ed54897a 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -140,8 +140,8 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation { - /// List of functions without a body. Can also contain functions from base classes. - std::vector unimplementedFunctions; + /// List of functions and modifiers without a body. Can also contain functions from base classes. + std::vector unimplementedDeclarations; /// List of all (direct and indirect) base contracts in order from derived to /// base, including the contract itself. std::vector linearizedBaseContracts; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index c9d3e1389..9f61184be 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -272,7 +272,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), make_pair("contractKind", contractKind(_node.contractKind())), make_pair("abstract", _node.abstract()), - make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()), + make_pair("fullyImplemented", _node.annotation().unimplementedDeclarations.empty()), make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)), make_pair("baseContracts", toJson(_node.baseContracts())), make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)), @@ -407,7 +407,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node) make_pair("parameters", toJson(_node.parameterList())), make_pair("virtual", _node.markedVirtual()), make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), - make_pair("body", toJson(_node.body())) + make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue) }; if (!_node.annotation().baseFunctions.empty()) attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true))); diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp index 7d388877b..e4538059d 100644 --- a/libsolidity/ast/ASTJsonImporter.cpp +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -453,7 +453,7 @@ ASTPointer ASTJsonImporter::createModifierDefinition(Json::V createParameterList(member(_node, "parameters")), memberAsBool(_node, "virtual"), _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), - createBlock(member(_node, "body")) + _node["body"].isNull() ? nullptr: createBlock(member(_node, "body")) ); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index bc0123d51..e597ce46b 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -288,7 +288,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor) m_parameters->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); - m_body->accept(_visitor); + if (m_body) + m_body->accept(_visitor); } _visitor.endVisit(*this); } @@ -302,7 +303,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const m_parameters->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); - m_body->accept(_visitor); + if (m_body) + m_body->accept(_visitor); } _visitor.endVisit(*this); } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index c1f66df4d..974bae4a3 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -1390,7 +1390,6 @@ public: std::string richIdentifier() const override; bool operator==(Type const& _other) const override; std::string toString(bool _short) const override; - protected: std::vector> makeStackItems() const override { return {}; } private: diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 12c9971dc..9003d26ce 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -194,7 +194,8 @@ void SMTEncoder::inlineModifierInvocation(ModifierInvocation const* _invocation, pushCallStack({_definition, _invocation}); if (auto modifier = dynamic_cast(_definition)) { - modifier->body().accept(*this); + if (modifier->isImplemented()) + modifier->body().accept(*this); popCallStack(); } else if (auto function = dynamic_cast(_definition)) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index befd3c696..c6da9a55b 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -595,13 +595,13 @@ ASTPointer Parser::parseFunctionDefinition() ASTPointer block; nodeFactory.markEndPosition(); - if (m_scanner->currentToken() != Token::Semicolon) + if (m_scanner->currentToken() == Token::Semicolon) + m_scanner->next(); + else { block = parseBlock(); nodeFactory.setEndPositionFromNode(block); } - else - m_scanner->next(); // just consume the ';' return nodeFactory.createNode( name, header.visibility, @@ -851,9 +851,16 @@ ASTPointer Parser::parseModifierDefinition() break; } + ASTPointer block; + nodeFactory.markEndPosition(); + if (m_scanner->currentToken() != Token::Semicolon) + { + block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + } + else + m_scanner->next(); // just consume the ';' - ASTPointer block = parseBlock(); - nodeFactory.setEndPositionFromNode(block); return nodeFactory.createNode(name, documentation, parameters, isVirtual, overrides, block); } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index b66b5aad6..26d9ca989 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -52,7 +52,7 @@ BOOST_AUTO_TEST_CASE(function_no_implementation) std::vector> nodes = sourceUnit->nodes(); ContractDefinition* contract = dynamic_cast(nodes[1].get()); BOOST_REQUIRE(contract); - BOOST_CHECK(!contract->annotation().unimplementedFunctions.empty()); + BOOST_CHECK(!contract->annotation().unimplementedDeclarations.empty()); BOOST_CHECK(!contract->definedFunctions()[0]->isImplemented()); } @@ -68,10 +68,10 @@ BOOST_AUTO_TEST_CASE(abstract_contract) ContractDefinition* base = dynamic_cast(nodes[1].get()); ContractDefinition* derived = dynamic_cast(nodes[2].get()); BOOST_REQUIRE(base); - BOOST_CHECK(!base->annotation().unimplementedFunctions.empty()); + BOOST_CHECK(!base->annotation().unimplementedDeclarations.empty()); BOOST_CHECK(!base->definedFunctions()[0]->isImplemented()); BOOST_REQUIRE(derived); - BOOST_CHECK(derived->annotation().unimplementedFunctions.empty()); + BOOST_CHECK(derived->annotation().unimplementedDeclarations.empty()); BOOST_CHECK(derived->definedFunctions()[0]->isImplemented()); } @@ -87,9 +87,9 @@ BOOST_AUTO_TEST_CASE(abstract_contract_with_overload) ContractDefinition* base = dynamic_cast(nodes[1].get()); ContractDefinition* derived = dynamic_cast(nodes[2].get()); BOOST_REQUIRE(base); - BOOST_CHECK(!base->annotation().unimplementedFunctions.empty()); + BOOST_CHECK(!base->annotation().unimplementedDeclarations.empty()); BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().unimplementedFunctions.empty()); + BOOST_CHECK(!derived->annotation().unimplementedDeclarations.empty()); } BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor) @@ -104,7 +104,7 @@ BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor) BOOST_CHECK_EQUAL(nodes.size(), 3); ContractDefinition* derived = dynamic_cast(nodes[2].get()); BOOST_REQUIRE(derived); - BOOST_CHECK(!derived->annotation().unimplementedFunctions.empty()); + BOOST_CHECK(!derived->annotation().unimplementedDeclarations.empty()); } BOOST_AUTO_TEST_CASE(function_canonical_signature) diff --git a/tools/solidityUpgrade/Upgrade060.cpp b/tools/solidityUpgrade/Upgrade060.cpp index 9ed7cbc74..f5fc4bdf5 100644 --- a/tools/solidityUpgrade/Upgrade060.cpp +++ b/tools/solidityUpgrade/Upgrade060.cpp @@ -99,7 +99,7 @@ inline string appendVirtual(FunctionDefinition const& _function) void AbstractContract::endVisit(ContractDefinition const& _contract) { - bool isFullyImplemented = _contract.annotation().unimplementedFunctions.empty(); + bool isFullyImplemented = _contract.annotation().unimplementedDeclarations.empty(); if ( !isFullyImplemented && From 48ff9fd4d68461e9a130d50b79183b889b4d8608 Mon Sep 17 00:00:00 2001 From: hrkrshnn Date: Wed, 15 Apr 2020 15:29:47 +0530 Subject: [PATCH 21/55] Tests, Changelog and updated grammar --- Changelog.md | 1 + docs/Solidity.g4 | 2 +- .../modifiers/function_modifier_empty.sol | 17 ++++++++++ .../modifiers/empty_modifier_body.sol | 14 +++++++++ .../modifiers/empty_modifier_err.sol | 10 ++++++ .../unimplemented_function_and_modifier.sol | 31 +++++++++++++++++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 test/libsolidity/semanticTests/modifiers/function_modifier_empty.sol create mode 100644 test/libsolidity/syntaxTests/modifiers/empty_modifier_body.sol create mode 100644 test/libsolidity/syntaxTests/modifiers/empty_modifier_err.sol create mode 100644 test/libsolidity/syntaxTests/modifiers/unimplemented_function_and_modifier.sol diff --git a/Changelog.md b/Changelog.md index 55f063755..4baf84ebb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Language Features: * Add support for EIP 165 interface identifiers with `type(I).interfaceId`. + * Allow virtual modifiers inside abstract contracts to have empty body. Compiler Features: diff --git a/docs/Solidity.g4 b/docs/Solidity.g4 index 8721f47a4..a415c19f9 100644 --- a/docs/Solidity.g4 +++ b/docs/Solidity.g4 @@ -68,7 +68,7 @@ structDefinition '{' ( variableDeclaration ';' (variableDeclaration ';')* )? '}' ; modifierDefinition - : 'modifier' identifier parameterList? ( VirtualKeyword | overrideSpecifier )* block ; + : 'modifier' identifier parameterList? ( VirtualKeyword | overrideSpecifier )* ( ';' | block ) ; functionDefinition : functionDescriptor parameterList modifierList returnParameters? ( ';' | block ) ; diff --git a/test/libsolidity/semanticTests/modifiers/function_modifier_empty.sol b/test/libsolidity/semanticTests/modifiers/function_modifier_empty.sol new file mode 100644 index 000000000..94a352587 --- /dev/null +++ b/test/libsolidity/semanticTests/modifiers/function_modifier_empty.sol @@ -0,0 +1,17 @@ +abstract contract A { + function f() public mod returns (bool r) { + return true; + } + + modifier mod virtual; +} + + +contract C is A { + modifier mod override { + if (false) _; + } +} + +// ---- +// f() -> false diff --git a/test/libsolidity/syntaxTests/modifiers/empty_modifier_body.sol b/test/libsolidity/syntaxTests/modifiers/empty_modifier_body.sol new file mode 100644 index 000000000..3ca8c8933 --- /dev/null +++ b/test/libsolidity/syntaxTests/modifiers/empty_modifier_body.sol @@ -0,0 +1,14 @@ +abstract contract A { modifier mod(uint a) virtual;} +contract B is A { modifier mod(uint a) override { _; } } + +abstract contract C { + modifier m virtual; + function f() m public { + + } +} +contract D is C { + modifier m override { + _; + } +} diff --git a/test/libsolidity/syntaxTests/modifiers/empty_modifier_err.sol b/test/libsolidity/syntaxTests/modifiers/empty_modifier_err.sol new file mode 100644 index 000000000..77a29903f --- /dev/null +++ b/test/libsolidity/syntaxTests/modifiers/empty_modifier_err.sol @@ -0,0 +1,10 @@ +contract A {modifier m virtual;} + +abstract contract B {modifier m virtual;} +contract C is B { } + +abstract contract D {modifier m;} +// ---- +// TypeError: (0-32): Contract "A" should be marked as abstract. +// TypeError: (76-95): Contract "C" should be marked as abstract. +// TypeError: (118-129): Modifiers without implementation must be marked virtual. diff --git a/test/libsolidity/syntaxTests/modifiers/unimplemented_function_and_modifier.sol b/test/libsolidity/syntaxTests/modifiers/unimplemented_function_and_modifier.sol new file mode 100644 index 000000000..aff392e8b --- /dev/null +++ b/test/libsolidity/syntaxTests/modifiers/unimplemented_function_and_modifier.sol @@ -0,0 +1,31 @@ +abstract contract A { + function foo() public virtual; + function foo(uint x) virtual public returns(uint); + modifier mod() virtual; +} + +contract B is A { + function foo(uint x) override public returns(uint) {return x;} + modifier mod() override { _; } +} + +contract C is A { + function foo() public override {} + modifier mod() override { _; } +} + +contract D is A { + function foo() public override {} + function foo(uint x) override public returns(uint) {return x;} +} + +/* No errors */ +contract E is A { + function foo() public override {} + function foo(uint x) override public returns(uint) {return x;} + modifier mod() override { _;} +} +// ---- +// TypeError: (137-254): Contract "B" should be marked as abstract. +// TypeError: (256-344): Contract "C" should be marked as abstract. +// TypeError: (346-466): Contract "D" should be marked as abstract. From d136e7dc95eb91644242308a4d58d0e79a82823a Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 20 Apr 2020 15:47:58 +0200 Subject: [PATCH 22/55] Rules for optimizing idempotency for bitwise operations. --- Changelog.md | 1 + libevmasm/RuleList.h | 23 +++++++++++++++++++ .../expressionSimplifier/idempotency.yul | 15 ++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 test/libyul/yulOptimizerTests/expressionSimplifier/idempotency.yul diff --git a/Changelog.md b/Changelog.md index 4baf84ebb..6a7042e6f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ Language Features: Compiler Features: + * Optimizer: Simplify repeated AND and OR operations. Bugfixes: * SMTChecker: Fix internal error when fixed points are used. diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 812c67002..cd6eda81d 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -232,6 +232,28 @@ std::vector> simplificationRuleListPart4( }; } +template +std::vector> simplificationRuleListPart4_5( + Pattern, + Pattern, + Pattern, + Pattern X, + Pattern Y +) +{ + using Builtins = typename Pattern::Builtins; + return std::vector>{ + // idempotent operations + {Builtins::AND(Builtins::AND(X, Y), Y), [=]{ return Builtins::AND(X, Y); }, true}, + {Builtins::AND(Y, Builtins::AND(X, Y)), [=]{ return Builtins::AND(X, Y); }, true}, + {Builtins::AND(Builtins::AND(Y, X), Y), [=]{ return Builtins::AND(Y, X); }, true}, + {Builtins::AND(Y, Builtins::AND(Y, X)), [=]{ return Builtins::AND(Y, X); }, true}, + {Builtins::OR(Builtins::OR(X, Y), Y), [=]{ return Builtins::OR(X, Y); }, true}, + {Builtins::OR(Y, Builtins::OR(X, Y)), [=]{ return Builtins::OR(X, Y); }, true}, + {Builtins::OR(Builtins::OR(Y, X), Y), [=]{ return Builtins::OR(Y, X); }, true}, + {Builtins::OR(Y, Builtins::OR(Y, X)), [=]{ return Builtins::OR(Y, X); }, true}, + }; +} template std::vector> simplificationRuleListPart5( @@ -663,6 +685,7 @@ std::vector> simplificationRuleList( rules += simplificationRuleListPart2(A, B, C, W, X); rules += simplificationRuleListPart3(A, B, C, W, X); rules += simplificationRuleListPart4(A, B, C, W, X); + rules += simplificationRuleListPart4_5(A, B, C, W, X); rules += simplificationRuleListPart5(A, B, C, W, X); rules += simplificationRuleListPart6(A, B, C, W, X); rules += simplificationRuleListPart7(A, B, C, W, X); diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/idempotency.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/idempotency.yul new file mode 100644 index 000000000..78d51db73 --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/idempotency.yul @@ -0,0 +1,15 @@ +{ + let x := calldataload(0) + let z := calldataload(1) + let t := and(and(x, z), x) + let w := or(or(x, z), x) +} +// ---- +// step: expressionSimplifier +// +// { +// let x := calldataload(0) +// let z := calldataload(1) +// let t := and(x, z) +// let w := or(x, z) +// } From d429d20b0b9f55d07b60c82f557e353dabd6c677 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 20 Apr 2020 21:43:24 +0200 Subject: [PATCH 23/55] Restructure documentation and improve TOC. --- docs/cheatsheet.rst | 190 ++++++ docs/grammar.rst | 6 + docs/index.rst | 40 +- docs/internals/layout_in_calldata.rst | 13 + docs/internals/layout_in_memory.rst | 39 ++ docs/internals/layout_in_storage.rst | 359 ++++++++++++ docs/internals/optimiser.rst | 71 +++ docs/internals/source_mappings.rst | 62 ++ docs/internals/variable_cleanup.rst | 47 ++ docs/miscellaneous.rst | 814 -------------------------- docs/solidity-in-depth.rst | 22 - 11 files changed, 823 insertions(+), 840 deletions(-) create mode 100644 docs/cheatsheet.rst create mode 100644 docs/grammar.rst create mode 100644 docs/internals/layout_in_calldata.rst create mode 100644 docs/internals/layout_in_memory.rst create mode 100644 docs/internals/layout_in_storage.rst create mode 100644 docs/internals/optimiser.rst create mode 100644 docs/internals/source_mappings.rst create mode 100644 docs/internals/variable_cleanup.rst delete mode 100644 docs/miscellaneous.rst delete mode 100644 docs/solidity-in-depth.rst diff --git a/docs/cheatsheet.rst b/docs/cheatsheet.rst new file mode 100644 index 000000000..26d7480b9 --- /dev/null +++ b/docs/cheatsheet.rst @@ -0,0 +1,190 @@ +********** +Cheatsheet +********** + +.. index:: precedence + +.. _order: + +Order of Precedence of Operators +================================ + +The following is the order of precedence for operators, listed in order of evaluation. + ++------------+-------------------------------------+--------------------------------------------+ +| Precedence | Description | Operator | ++============+=====================================+============================================+ +| *1* | Postfix increment and decrement | ``++``, ``--`` | ++ +-------------------------------------+--------------------------------------------+ +| | New expression | ``new `` | ++ +-------------------------------------+--------------------------------------------+ +| | Array subscripting | ``[]`` | ++ +-------------------------------------+--------------------------------------------+ +| | Member access | ``.`` | ++ +-------------------------------------+--------------------------------------------+ +| | Function-like call | ``()`` | ++ +-------------------------------------+--------------------------------------------+ +| | Parentheses | ``()`` | ++------------+-------------------------------------+--------------------------------------------+ +| *2* | Prefix increment and decrement | ``++``, ``--`` | ++ +-------------------------------------+--------------------------------------------+ +| | Unary minus | ``-`` | ++ +-------------------------------------+--------------------------------------------+ +| | Unary operations | ``delete`` | ++ +-------------------------------------+--------------------------------------------+ +| | Logical NOT | ``!`` | ++ +-------------------------------------+--------------------------------------------+ +| | Bitwise NOT | ``~`` | ++------------+-------------------------------------+--------------------------------------------+ +| *3* | Exponentiation | ``**`` | ++------------+-------------------------------------+--------------------------------------------+ +| *4* | Multiplication, division and modulo | ``*``, ``/``, ``%`` | ++------------+-------------------------------------+--------------------------------------------+ +| *5* | Addition and subtraction | ``+``, ``-`` | ++------------+-------------------------------------+--------------------------------------------+ +| *6* | Bitwise shift operators | ``<<``, ``>>`` | ++------------+-------------------------------------+--------------------------------------------+ +| *7* | Bitwise AND | ``&`` | ++------------+-------------------------------------+--------------------------------------------+ +| *8* | Bitwise XOR | ``^`` | ++------------+-------------------------------------+--------------------------------------------+ +| *9* | Bitwise OR | ``|`` | ++------------+-------------------------------------+--------------------------------------------+ +| *10* | Inequality operators | ``<``, ``>``, ``<=``, ``>=`` | ++------------+-------------------------------------+--------------------------------------------+ +| *11* | Equality operators | ``==``, ``!=`` | ++------------+-------------------------------------+--------------------------------------------+ +| *12* | Logical AND | ``&&`` | ++------------+-------------------------------------+--------------------------------------------+ +| *13* | Logical OR | ``||`` | ++------------+-------------------------------------+--------------------------------------------+ +| *14* | Ternary operator | `` ? : `` | ++ +-------------------------------------+--------------------------------------------+ +| | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, | +| | | ``>>=``, ``+=``, ``-=``, ``*=``, ``/=``, | +| | | ``%=`` | ++------------+-------------------------------------+--------------------------------------------+ +| *15* | Comma operator | ``,`` | ++------------+-------------------------------------+--------------------------------------------+ + +.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send + +Global Variables +================ + +- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI `-decodes + the provided data. The types are given in parentheses as second argument. + Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))`` +- ``abi.encode(...) returns (bytes memory)``: :ref:`ABI `-encodes the given arguments +- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding ` of + the given arguments. Note that this encoding can be ambiguous! +- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI `-encodes + the given arguments starting from the second and prepends the given four-byte selector +- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent + to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)``` +- ``block.coinbase`` (``address payable``): current block miner's address +- ``block.difficulty`` (``uint``): current block difficulty +- ``block.gaslimit`` (``uint``): current block gaslimit +- ``block.number`` (``uint``): current block number +- ``block.timestamp`` (``uint``): current block timestamp +- ``gasleft() returns (uint256)``: remaining gas +- ``msg.data`` (``bytes``): complete calldata +- ``msg.sender`` (``address payable``): sender of the message (current call) +- ``msg.value`` (``uint``): number of wei sent with the message +- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``) +- ``tx.gasprice`` (``uint``): gas price of the transaction +- ``tx.origin`` (``address payable``): sender of the transaction (full call chain) +- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error) +- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use + for malformed input or error in external component) +- ``require(bool condition, string memory message)``: abort execution and revert state changes if + condition is ``false`` (use for malformed input or error in external component). Also provide error message. +- ``revert()``: abort execution and revert state changes +- ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string +- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks +- ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input +- ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input +- ``ripemd160(bytes memory) returns (bytes20)``: compute the RIPEMD-160 hash of the input +- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with + the public key from elliptic curve signature, return zero on error +- ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with + arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. +- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed + with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. +- ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` or ``address payable`` +- ``super``: the contract one level higher in the inheritance hierarchy +- ``selfdestruct(address payable recipient)``: destroy the current contract, sending its funds to the given address +- ``
.balance`` (``uint256``): balance of the :ref:`address` in Wei +- ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, + returns ``false`` on failure +- ``
.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure +- ``type(C).name`` (``string``): the name of the contract +- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information`. +- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information`. + +.. note:: + Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness, + unless you know what you are doing. + + Both the timestamp and the block hash can be influenced by miners to some degree. + Bad actors in the mining community can for example run a casino payout function on a chosen hash + and just retry a different hash if they did not receive any money. + + The current block timestamp must be strictly larger than the timestamp of the last block, + but the only guarantee is that it will be somewhere between the timestamps of two + consecutive blocks in the canonical chain. + +.. note:: + The block hashes are not available for all blocks for scalability reasons. + You can only access the hashes of the most recent 256 blocks, all other + values will be zero. + +.. note:: + In version 0.5.0, the following aliases were removed: ``suicide`` as alias for ``selfdestruct``, + ``msg.gas`` as alias for ``gasleft``, ``block.blockhash`` as alias for ``blockhash`` and + ``sha3`` as alias for ``keccak256``. + +.. index:: visibility, public, private, external, internal + +Function Visibility Specifiers +============================== + +:: + + function myFunction() returns (bool) { + return true; + } + +- ``public``: visible externally and internally (creates a :ref:`getter function` for storage/state variables) +- ``private``: only visible in the current contract +- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``) +- ``internal``: only visible internally + + +.. index:: modifiers, pure, view, payable, constant, anonymous, indexed + +Modifiers +========= + +- ``pure`` for functions: Disallows modification or access of state. +- ``view`` for functions: Disallows modification of state. +- ``payable`` for functions: Allows them to receive Ether together with a call. +- ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot. +- ``immutable`` for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code. +- ``anonymous`` for events: Does not store event signature as topic. +- ``indexed`` for event parameters: Stores the parameter as topic. +- ``virtual`` for functions and modifiers: Allows the function's or modifier's + behaviour to be changed in derived contracts. +- ``override``: States that this function, modifier or public state variable changes + the behaviour of a function or modifier in a base contract. + +Reserved Keywords +================= + +These keywords are reserved in Solidity. They might become part of the syntax in the future: + +``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``, +``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``, +``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``, +``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``, +``unchecked``. diff --git a/docs/grammar.rst b/docs/grammar.rst new file mode 100644 index 000000000..c7a22dd7e --- /dev/null +++ b/docs/grammar.rst @@ -0,0 +1,6 @@ +**************** +Language Grammar +**************** + +.. literalinclude:: Solidity.g4 + :language: antlr diff --git a/docs/index.rst b/docs/index.rst index 6610b5217..89976a8d4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,7 +36,7 @@ If you are new to the concept of smart contracts we recommend you start with :ref:`an example smart contract ` written in Solidity. When you are ready for more detail, we recommend you read the :doc:`"Solidity by Example" ` and -:doc:`"Solidity in Depth" ` sections to learn the core concepts of the language. +"Language Description" sections to learn the core concepts of the language. For further reading, try :ref:`the basics of blockchains ` and details of the :ref:`Ethereum Virtual Machine `. @@ -88,17 +88,49 @@ Contents .. toctree:: :maxdepth: 2 + :caption: Basics introduction-to-smart-contracts.rst installing-solidity.rst solidity-by-example.rst - solidity-in-depth.rst + +.. toctree:: + :maxdepth: 2 + :caption: Language Description + + layout-of-source-files.rst + structure-of-a-contract.rst + types.rst + units-and-global-variables.rst + control-structures.rst + contracts.rst + assembly.rst + cheatsheet.rst + grammar.rst + +.. toctree:: + :maxdepth: 2 + :caption: Internals + + internals/layout_in_storage.rst + internals/layout_in_memory.rst + internals/layout_in_calldata.rst + internals/variable_cleanup.rst + internals/source_mappings.rst + internals/optimiser.rst + metadata.rst + abi-spec.rst + +.. toctree:: + :maxdepth: 2 + :caption: Additional Material + + 050-breaking-changes.rst + 060-breaking-changes.rst natspec-format.rst security-considerations.rst resources.rst using-the-compiler.rst - metadata.rst - abi-spec.rst yul.rst style-guide.rst common-patterns.rst diff --git a/docs/internals/layout_in_calldata.rst b/docs/internals/layout_in_calldata.rst new file mode 100644 index 000000000..cfabaf99f --- /dev/null +++ b/docs/internals/layout_in_calldata.rst @@ -0,0 +1,13 @@ +******************* +Layout of Call Data +******************* + +The input data for a function call is assumed to be in the format defined by the :ref:`ABI +specification `. Among others, the ABI specification requires arguments to be padded to multiples of 32 +bytes. The internal function calls use a different convention. + +Arguments for the constructor of a contract are directly appended at the end of the +contract's code, also in ABI encoding. The constructor will access them through a hard-coded offset, and +not by using the ``codesize`` opcode, since this of course changes when appending +data to the code. + diff --git a/docs/internals/layout_in_memory.rst b/docs/internals/layout_in_memory.rst new file mode 100644 index 000000000..34c3035eb --- /dev/null +++ b/docs/internals/layout_in_memory.rst @@ -0,0 +1,39 @@ + +.. index: memory layout + +**************** +Layout in Memory +**************** + +Solidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows: + +- ``0x00`` - ``0x3f`` (64 bytes): scratch space for hashing methods +- ``0x40`` - ``0x5f`` (32 bytes): currently allocated memory size (aka. free memory pointer) +- ``0x60`` - ``0x7f`` (32 bytes): zero slot + +Scratch space can be used between statements (i.e. within inline assembly). The zero slot +is used as initial value for dynamic memory arrays and should never be written to +(the free memory pointer points to ``0x80`` initially). + +Solidity always places new objects at the free memory pointer and +memory is never freed (this might change in the future). + +Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this +is even true for ``byte[]``, but not for ``bytes`` and ``string``). +Multi-dimensional memory arrays are pointers to memory arrays. The length of a +dynamic array is stored at the first slot of the array and followed by the array +elements. + +.. warning:: + There are some operations in Solidity that need a temporary memory area + larger than 64 bytes and therefore will not fit into the scratch space. + They will be placed where the free memory points to, but given their + short lifetime, the pointer is not updated. The memory may or may not + be zeroed out. Because of this, one should not expect the free memory + to point to zeroed out memory. + + While it may seem like a good idea to use ``msize`` to arrive at a + definitely zeroed out memory area, using such a pointer non-temporarily + without updating the free memory pointer can have unexpected results. + +.. index: calldata layout \ No newline at end of file diff --git a/docs/internals/layout_in_storage.rst b/docs/internals/layout_in_storage.rst new file mode 100644 index 000000000..0101556ef --- /dev/null +++ b/docs/internals/layout_in_storage.rst @@ -0,0 +1,359 @@ +.. index:: storage, state variable, mapping + +************************************ +Layout of State Variables in Storage +************************************ + +.. _storage-inplace-encoding: + +Statically-sized variables (everything except mapping and dynamically-sized +array types) are laid out contiguously in storage starting from position ``0``. +Multiple, contiguous items that need less than 32 bytes are packed into a single +storage slot if possible, according to the following rules: + +- The first item in a storage slot is stored lower-order aligned. +- Elementary types use only as many bytes as are necessary to store them. +- If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot. +- Structs and array data always start a new slot and occupy whole slots + (but items inside a struct or array are packed tightly according to these rules). + +For contracts that use inheritance, the ordering of state variables is determined by the +C3-linearized order of contracts starting with the most base-ward contract. If allowed +by the above rules, state variables from different contracts do share the same storage slot. + +The elements of structs and arrays are stored after each other, just as if they were given explicitly. + +.. warning:: + When using elements that are smaller than 32 bytes, your contract's gas usage may be higher. + This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller + than that, the EVM must use more operations in order to reduce the size of the element from 32 + bytes to the desired size. + + It is only beneficial to use reduced-size arguments if you are dealing with storage values + because the compiler will pack multiple elements into one storage slot, and thus, combine + multiple reads or writes into a single operation. When dealing with function arguments or memory + values, there is no inherent benefit because the compiler does not pack these values. + + Finally, in order to allow the EVM to optimize for this, ensure that you try to order your + storage variables and ``struct`` members such that they can be packed tightly. For example, + declaring your storage variables in the order of ``uint128, uint128, uint256`` instead of + ``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the + latter will take up three. + +.. note:: + The layout of state variables in storage is considered to be part of the external interface + of Solidity due to the fact that storage pointers can be passed to libraries. This means that + any change to the rules outlined in this section is considered a breaking change + of the language and due to its critical nature should be considered very carefully before + being executed. + + +Mappings and Dynamic Arrays +=========================== + +.. _storage-hashed-encoding: + +Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash +computation to find the starting position of the value or the array data. +These starting positions are always full stack slots. + +The mapping or the dynamic array itself occupies a slot in storage at some position ``p`` +according to the above rule (or by recursively applying this rule for +mappings of mappings or arrays of arrays). For dynamic arrays, +this slot stores the number of elements in the array (byte arrays and +strings are an exception, see :ref:`below `). +For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different +hash distribution). Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key +``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. If the value is again a +non-elementary type, the positions are found by adding an offset of ``keccak256(k . p)``. + +So for the following contract snippet +the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``:: + + + pragma solidity >=0.4.0 <0.7.0; + + + contract C { + struct S { uint a; uint b; } + uint x; + mapping(uint => mapping(uint => S)) data; + } + +.. _bytes-and-string: + +``bytes`` and ``string`` +------------------------ + +``bytes`` and ``string`` are encoded identically. For short byte arrays, they store their data in the same +slot where the length is also stored. In particular: if the data is at most ``31`` bytes long, it is stored +in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``. +For byte arrays that store data which is ``32`` or more bytes long, the main slot stores ``length * 2 + 1`` and the data is +stored as usual in ``keccak256(slot)``. This means that you can distinguish a short array from a long array +by checking if the lowest bit is set: short (not set) and long (set). + +.. note:: + Handling invalidly encoded slots is currently not supported but may be added in the future. + +JSON Output +=========== + +.. _storage-layout-top-level: + +The storage layout of a contract can be requested via +the :ref:`standard JSON interface `. The output is a JSON object containing two keys, +``storage`` and ``types``. The ``storage`` object is an array where each +element has the following form: + + +.. code:: + + + { + "astId": 2, + "contract": "fileA:A", + "label": "x", + "offset": 0, + "slot": "0", + "type": "t_uint256" + } + +The example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA`` +and + +- ``astId`` is the id of the AST node of the state variable's declaration +- ``contract`` is the name of the contract including its path as prefix +- ``label`` is the name of the state variable +- ``offset`` is the offset in bytes within the storage slot according to the encoding +- ``slot`` is the storage slot where the state variable resides or starts. This + number may be very large and therefore its JSON value is represented as a + string. +- ``type`` is an identifier used as key to the variable's type information (described in the following) + +The given ``type``, in this case ``t_uint256`` represents an element in +``types``, which has the form: + + +.. code:: + + { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32", + } + +where + +- ``encoding`` how the data is encoded in storage, where the possible values are: + + - ``inplace``: data is laid out contiguously in storage (see :ref:`above `). + - ``mapping``: Keccak-256 hash-based method (see :ref:`above `). + - ``dynamic_array``: Keccak-256 hash-based method (see :ref:`above `). + - ``bytes``: single slot or Keccak-256 hash-based depending on the data size (see :ref:`above `). + +- ``label`` is the canonical type name. +- ``numberOfBytes`` is the number of used bytes (as a decimal string). + Note that if ``numberOfBytes > 32`` this means that more than one slot is used. + +Some types have extra information besides the four above. Mappings contain +its ``key`` and ``value`` types (again referencing an entry in this mapping +of types), arrays have its ``base`` type, and structs list their ``members`` in +the same format as the top-level ``storage`` (see :ref:`above +`). + +.. note :: + The JSON output format of a contract's storage layout is still considered experimental + and is subject to change in non-breaking releases of Solidity. + +The following example shows a contract and its storage layout, containing +value and reference types, types that are encoded packed, and nested types. + + +.. code:: + + pragma solidity >=0.4.0 <0.7.0; + contract A { + struct S { + uint128 a; + uint128 b; + uint[2] staticArray; + uint[] dynArray; + } + + uint x; + uint y; + S s; + address addr; + mapping (uint => mapping (address => bool)) map; + uint[] array; + string s1; + bytes b1; + } + +.. code:: + + "storageLayout": { + "storage": [ + { + "astId": 14, + "contract": "fileA:A", + "label": "x", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 16, + "contract": "fileA:A", + "label": "y", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 18, + "contract": "fileA:A", + "label": "s", + "offset": 0, + "slot": "2", + "type": "t_struct(S)12_storage" + }, + { + "astId": 20, + "contract": "fileA:A", + "label": "addr", + "offset": 0, + "slot": "6", + "type": "t_address" + }, + { + "astId": 26, + "contract": "fileA:A", + "label": "map", + "offset": 0, + "slot": "7", + "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))" + }, + { + "astId": 29, + "contract": "fileA:A", + "label": "array", + "offset": 0, + "slot": "8", + "type": "t_array(t_uint256)dyn_storage" + }, + { + "astId": 31, + "contract": "fileA:A", + "label": "s1", + "offset": 0, + "slot": "9", + "type": "t_string_storage" + }, + { + "astId": 33, + "contract": "fileA:A", + "label": "b1", + "offset": 0, + "slot": "10", + "type": "t_bytes_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)2_storage": { + "base": "t_uint256", + "encoding": "inplace", + "label": "uint256[2]", + "numberOfBytes": "64" + }, + "t_array(t_uint256)dyn_storage": { + "base": "t_uint256", + "encoding": "dynamic_array", + "label": "uint256[]", + "numberOfBytes": "32" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes_storage": { + "encoding": "bytes", + "label": "bytes", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_uint256,t_mapping(t_address,t_bool))": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => mapping(address => bool))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_bool)" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_struct(S)12_storage": { + "encoding": "inplace", + "label": "struct A.S", + "members": [ + { + "astId": 2, + "contract": "fileA:A", + "label": "a", + "offset": 0, + "slot": "0", + "type": "t_uint128" + }, + { + "astId": 4, + "contract": "fileA:A", + "label": "b", + "offset": 16, + "slot": "0", + "type": "t_uint128" + }, + { + "astId": 8, + "contract": "fileA:A", + "label": "staticArray", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)2_storage" + }, + { + "astId": 11, + "contract": "fileA:A", + "label": "dynArray", + "offset": 0, + "slot": "3", + "type": "t_array(t_uint256)dyn_storage" + } + ], + "numberOfBytes": "128" + }, + "t_uint128": { + "encoding": "inplace", + "label": "uint128", + "numberOfBytes": "16" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } diff --git a/docs/internals/optimiser.rst b/docs/internals/optimiser.rst new file mode 100644 index 000000000..a66291f01 --- /dev/null +++ b/docs/internals/optimiser.rst @@ -0,0 +1,71 @@ +.. index:: optimizer, common subexpression elimination, constant propagation + +************* +The Optimiser +************* + +This section discusses the optimiser that was first added to Solidity, +which operates on opcode streams. For information on the new Yul-based optimiser, +please see the `readme on github `_. + +The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks +at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser +analyses the instructions and records every modification to the stack, +memory, or storage as an expression which consists of an instruction and +a list of arguments which are pointers to other expressions. The optimiser +uses a component called "CommonSubexpressionEliminator" that amongst other +tasks, finds expressions that are always equal (on every input) and combines +them into an expression class. The optimiser first tries to find each new +expression in a list of already known expressions. If this does not work, +it simplifies the expression according to rules like +``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is +a recursive process, we can also apply the latter rule if the second factor +is a more complex expression where we know that it always evaluates to one. +Modifications to storage and memory locations have to erase knowledge about +storage and memory locations which are not known to be different. If we first +write to location x and then to location y and both are input variables, the +second could overwrite the first, so we do not know what is stored at x after +we wrote to y. If simplification of the expression x - y evaluates to a +non-zero constant, we know that we can keep our knowledge about what is stored at x. + +After this process, we know which expressions have to be on the stack at +the end, and have a list of modifications to memory and storage. This information +is stored together with the basic blocks and is used to link them. Furthermore, +knowledge about the stack, storage and memory configuration is forwarded to +the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions, +we can build a complete control flow graph of the program. If there is only +one target we do not know (this can happen as in principle, jump targets can +be computed from inputs), we have to erase all knowledge about the input state +of a block as it can be the target of the unknown ``JUMP``. If the optimiser +finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it +to an unconditional jump. + +As the last step, the code in each block is re-generated. The optimiser creates +a dependency graph from the expressions on the stack at the end of the block, +and it drops every operation that is not part of this graph. It generates code +that applies the modifications to memory and storage in the order they were +made in the original code (dropping modifications which were found not to be +needed). Finally, it generates all values that are required to be on the +stack in the correct place. + +These steps are applied to each basic block and the newly generated code +is used as replacement if it is smaller. If a basic block is split at a +``JUMPI`` and during the analysis, the condition evaluates to a constant, +the ``JUMPI`` is replaced depending on the value of the constant. Thus code like + +:: + + uint x = 7; + data[7] = 9; + if (data[x] != x + 2) + return 2; + else + return 1; + +still simplifies to code which you can compile even though the instructions contained +a jump in the beginning of the process: + +:: + + data[7] = 9; + return 1; diff --git a/docs/internals/source_mappings.rst b/docs/internals/source_mappings.rst new file mode 100644 index 000000000..d650031c7 --- /dev/null +++ b/docs/internals/source_mappings.rst @@ -0,0 +1,62 @@ +.. index:: source mappings + +*************** +Source Mappings +*************** + +As part of the AST output, the compiler provides the range of the source +code that is represented by the respective node in the AST. This can be +used for various purposes ranging from static analysis tools that report +errors based on the AST and debugging tools that highlight local variables +and their uses. + +Furthermore, the compiler can also generate a mapping from the bytecode +to the range in the source code that generated the instruction. This is again +important for static analysis tools that operate on bytecode level and +for displaying the current position in the source code inside a debugger +or for breakpoint handling. This mapping also contains other information, +like the jump type and the modifier depth (see below). + +Both kinds of source mappings use integer identifiers to refer to source files. +The identifier of a source file is stored in +``output['sources'][sourceName]['id']`` where ``output`` is the output of the +standard-json compiler interface parsed as JSON. + +.. note :: + In the case of instructions that are not associated with any particular source file, + the source mapping assigns an integer identifier of ``-1``. This may happen for + bytecode sections stemming from compiler-generated inline assembly statements. + +The source mappings inside the AST use the following +notation: + +``s:l:f`` + +Where ``s`` is the byte-offset to the start of the range in the source file, +``l`` is the length of the source range in bytes and ``f`` is the source +index mentioned above. + +The encoding in the source mapping for the bytecode is more complicated: +It is a list of ``s:l:f:j:m`` separated by ``;``. Each of these +elements corresponds to an instruction, i.e. you cannot use the byte offset +but have to use the instruction offset (push instructions are longer than a single byte). +The fields ``s``, ``l`` and ``f`` are as above. ``j`` can be either +``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a +function, returns from a function or is a regular jump as part of e.g. a loop. +The last field, ``m``, is an integer that denotes the "modifier depth". This depth +is increased whenever the placeholder statement (``_``) is entered in a modifier +and decreased when it is left again. This allows debuggers to track tricky cases +like the same modifier being used twice or multiple placeholder statements being +used in a single modifier. + +In order to compress these source mappings especially for bytecode, the +following rules are used: + + - If a field is empty, the value of the preceding element is used. + - If a ``:`` is missing, all following fields are considered empty. + +This means the following source mappings represent the same information: + +``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2`` + +``1:2:1;:9;2:1:2;;`` diff --git a/docs/internals/variable_cleanup.rst b/docs/internals/variable_cleanup.rst new file mode 100644 index 000000000..1718fc66b --- /dev/null +++ b/docs/internals/variable_cleanup.rst @@ -0,0 +1,47 @@ +.. index: variable cleanup + +********************* +Cleaning Up Variables +********************* + +When a value is shorter than 256 bit, in some cases the remaining bits +must be cleaned. +The Solidity compiler is designed to clean such remaining bits before any operations +that might be adversely affected by the potential garbage in the remaining bits. +For example, before writing a value to memory, the remaining bits need +to be cleared because the memory contents can be used for computing +hashes or sent as the data of a message call. Similarly, before +storing a value in the storage, the remaining bits need to be cleaned +because otherwise the garbled value can be observed. + +On the other hand, we do not clean the bits if the immediately +following operation is not affected. For instance, since any non-zero +value is considered ``true`` by ``JUMPI`` instruction, we do not clean +the boolean values before they are used as the condition for +``JUMPI``. + +In addition to the design principle above, the Solidity compiler +cleans input data when it is loaded onto the stack. + +Different types have different rules for cleaning up invalid values: + ++---------------+---------------+-------------------+ +|Type |Valid Values |Invalid Values Mean| ++===============+===============+===================+ +|enum of n |0 until n - 1 |exception | +|members | | | ++---------------+---------------+-------------------+ +|bool |0 or 1 |1 | ++---------------+---------------+-------------------+ +|signed integers|sign-extended |currently silently | +| |word |wraps; in the | +| | |future exceptions | +| | |will be thrown | +| | | | +| | | | ++---------------+---------------+-------------------+ +|unsigned |higher bits |currently silently | +|integers |zeroed |wraps; in the | +| | |future exceptions | +| | |will be thrown | ++---------------+---------------+-------------------+ diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst deleted file mode 100644 index 3c30fec86..000000000 --- a/docs/miscellaneous.rst +++ /dev/null @@ -1,814 +0,0 @@ -############# -Miscellaneous -############# - -.. index:: storage, state variable, mapping - -************************************ -Layout of State Variables in Storage -************************************ - -.. _storage-inplace-encoding: - -Statically-sized variables (everything except mapping and dynamically-sized -array types) are laid out contiguously in storage starting from position ``0``. -Multiple, contiguous items that need less than 32 bytes are packed into a single -storage slot if possible, according to the following rules: - -- The first item in a storage slot is stored lower-order aligned. -- Elementary types use only as many bytes as are necessary to store them. -- If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot. -- Structs and array data always start a new slot and occupy whole slots - (but items inside a struct or array are packed tightly according to these rules). - -For contracts that use inheritance, the ordering of state variables is determined by the -C3-linearized order of contracts starting with the most base-ward contract. If allowed -by the above rules, state variables from different contracts do share the same storage slot. - -The elements of structs and arrays are stored after each other, just as if they were given explicitly. - -.. warning:: - When using elements that are smaller than 32 bytes, your contract's gas usage may be higher. - This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller - than that, the EVM must use more operations in order to reduce the size of the element from 32 - bytes to the desired size. - - It is only beneficial to use reduced-size arguments if you are dealing with storage values - because the compiler will pack multiple elements into one storage slot, and thus, combine - multiple reads or writes into a single operation. When dealing with function arguments or memory - values, there is no inherent benefit because the compiler does not pack these values. - - Finally, in order to allow the EVM to optimize for this, ensure that you try to order your - storage variables and ``struct`` members such that they can be packed tightly. For example, - declaring your storage variables in the order of ``uint128, uint128, uint256`` instead of - ``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the - latter will take up three. - -.. note:: - The layout of state variables in storage is considered to be part of the external interface - of Solidity due to the fact that storage pointers can be passed to libraries. This means that - any change to the rules outlined in this section is considered a breaking change - of the language and due to its critical nature should be considered very carefully before - being executed. - - -Mappings and Dynamic Arrays -=========================== - -.. _storage-hashed-encoding: - -Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash -computation to find the starting position of the value or the array data. -These starting positions are always full stack slots. - -The mapping or the dynamic array itself occupies a slot in storage at some position ``p`` -according to the above rule (or by recursively applying this rule for -mappings of mappings or arrays of arrays). For dynamic arrays, -this slot stores the number of elements in the array (byte arrays and -strings are an exception, see :ref:`below `). -For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different -hash distribution). Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key -``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. If the value is again a -non-elementary type, the positions are found by adding an offset of ``keccak256(k . p)``. - -So for the following contract snippet -the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``:: - - - pragma solidity >=0.4.0 <0.7.0; - - - contract C { - struct S { uint a; uint b; } - uint x; - mapping(uint => mapping(uint => S)) data; - } - -.. _bytes-and-string: - -``bytes`` and ``string`` ------------------------- - -``bytes`` and ``string`` are encoded identically. For short byte arrays, they store their data in the same -slot where the length is also stored. In particular: if the data is at most ``31`` bytes long, it is stored -in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``. -For byte arrays that store data which is ``32`` or more bytes long, the main slot stores ``length * 2 + 1`` and the data is -stored as usual in ``keccak256(slot)``. This means that you can distinguish a short array from a long array -by checking if the lowest bit is set: short (not set) and long (set). - -.. note:: - Handling invalidly encoded slots is currently not supported but may be added in the future. - -JSON Output -=========== - -.. _storage-layout-top-level: - -The storage layout of a contract can be requested via -the :ref:`standard JSON interface `. The output is a JSON object containing two keys, -``storage`` and ``types``. The ``storage`` object is an array where each -element has the following form: - - -.. code:: - - - { - "astId": 2, - "contract": "fileA:A", - "label": "x", - "offset": 0, - "slot": "0", - "type": "t_uint256" - } - -The example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA`` -and - -- ``astId`` is the id of the AST node of the state variable's declaration -- ``contract`` is the name of the contract including its path as prefix -- ``label`` is the name of the state variable -- ``offset`` is the offset in bytes within the storage slot according to the encoding -- ``slot`` is the storage slot where the state variable resides or starts. This - number may be very large and therefore its JSON value is represented as a - string. -- ``type`` is an identifier used as key to the variable's type information (described in the following) - -The given ``type``, in this case ``t_uint256`` represents an element in -``types``, which has the form: - - -.. code:: - - { - "encoding": "inplace", - "label": "uint256", - "numberOfBytes": "32", - } - -where - -- ``encoding`` how the data is encoded in storage, where the possible values are: - - - ``inplace``: data is laid out contiguously in storage (see :ref:`above `). - - ``mapping``: Keccak-256 hash-based method (see :ref:`above `). - - ``dynamic_array``: Keccak-256 hash-based method (see :ref:`above `). - - ``bytes``: single slot or Keccak-256 hash-based depending on the data size (see :ref:`above `). - -- ``label`` is the canonical type name. -- ``numberOfBytes`` is the number of used bytes (as a decimal string). - Note that if ``numberOfBytes > 32`` this means that more than one slot is used. - -Some types have extra information besides the four above. Mappings contain -its ``key`` and ``value`` types (again referencing an entry in this mapping -of types), arrays have its ``base`` type, and structs list their ``members`` in -the same format as the top-level ``storage`` (see :ref:`above -`). - -.. note :: - The JSON output format of a contract's storage layout is still considered experimental - and is subject to change in non-breaking releases of Solidity. - -The following example shows a contract and its storage layout, containing -value and reference types, types that are encoded packed, and nested types. - - -.. code:: - - pragma solidity >=0.4.0 <0.7.0; - contract A { - struct S { - uint128 a; - uint128 b; - uint[2] staticArray; - uint[] dynArray; - } - - uint x; - uint y; - S s; - address addr; - mapping (uint => mapping (address => bool)) map; - uint[] array; - string s1; - bytes b1; - } - -.. code:: - - "storageLayout": { - "storage": [ - { - "astId": 14, - "contract": "fileA:A", - "label": "x", - "offset": 0, - "slot": "0", - "type": "t_uint256" - }, - { - "astId": 16, - "contract": "fileA:A", - "label": "y", - "offset": 0, - "slot": "1", - "type": "t_uint256" - }, - { - "astId": 18, - "contract": "fileA:A", - "label": "s", - "offset": 0, - "slot": "2", - "type": "t_struct(S)12_storage" - }, - { - "astId": 20, - "contract": "fileA:A", - "label": "addr", - "offset": 0, - "slot": "6", - "type": "t_address" - }, - { - "astId": 26, - "contract": "fileA:A", - "label": "map", - "offset": 0, - "slot": "7", - "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))" - }, - { - "astId": 29, - "contract": "fileA:A", - "label": "array", - "offset": 0, - "slot": "8", - "type": "t_array(t_uint256)dyn_storage" - }, - { - "astId": 31, - "contract": "fileA:A", - "label": "s1", - "offset": 0, - "slot": "9", - "type": "t_string_storage" - }, - { - "astId": 33, - "contract": "fileA:A", - "label": "b1", - "offset": 0, - "slot": "10", - "type": "t_bytes_storage" - } - ], - "types": { - "t_address": { - "encoding": "inplace", - "label": "address", - "numberOfBytes": "20" - }, - "t_array(t_uint256)2_storage": { - "base": "t_uint256", - "encoding": "inplace", - "label": "uint256[2]", - "numberOfBytes": "64" - }, - "t_array(t_uint256)dyn_storage": { - "base": "t_uint256", - "encoding": "dynamic_array", - "label": "uint256[]", - "numberOfBytes": "32" - }, - "t_bool": { - "encoding": "inplace", - "label": "bool", - "numberOfBytes": "1" - }, - "t_bytes_storage": { - "encoding": "bytes", - "label": "bytes", - "numberOfBytes": "32" - }, - "t_mapping(t_address,t_bool)": { - "encoding": "mapping", - "key": "t_address", - "label": "mapping(address => bool)", - "numberOfBytes": "32", - "value": "t_bool" - }, - "t_mapping(t_uint256,t_mapping(t_address,t_bool))": { - "encoding": "mapping", - "key": "t_uint256", - "label": "mapping(uint256 => mapping(address => bool))", - "numberOfBytes": "32", - "value": "t_mapping(t_address,t_bool)" - }, - "t_string_storage": { - "encoding": "bytes", - "label": "string", - "numberOfBytes": "32" - }, - "t_struct(S)12_storage": { - "encoding": "inplace", - "label": "struct A.S", - "members": [ - { - "astId": 2, - "contract": "fileA:A", - "label": "a", - "offset": 0, - "slot": "0", - "type": "t_uint128" - }, - { - "astId": 4, - "contract": "fileA:A", - "label": "b", - "offset": 16, - "slot": "0", - "type": "t_uint128" - }, - { - "astId": 8, - "contract": "fileA:A", - "label": "staticArray", - "offset": 0, - "slot": "1", - "type": "t_array(t_uint256)2_storage" - }, - { - "astId": 11, - "contract": "fileA:A", - "label": "dynArray", - "offset": 0, - "slot": "3", - "type": "t_array(t_uint256)dyn_storage" - } - ], - "numberOfBytes": "128" - }, - "t_uint128": { - "encoding": "inplace", - "label": "uint128", - "numberOfBytes": "16" - }, - "t_uint256": { - "encoding": "inplace", - "label": "uint256", - "numberOfBytes": "32" - } - } - } - -.. index: memory layout - -**************** -Layout in Memory -**************** - -Solidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows: - -- ``0x00`` - ``0x3f`` (64 bytes): scratch space for hashing methods -- ``0x40`` - ``0x5f`` (32 bytes): currently allocated memory size (aka. free memory pointer) -- ``0x60`` - ``0x7f`` (32 bytes): zero slot - -Scratch space can be used between statements (i.e. within inline assembly). The zero slot -is used as initial value for dynamic memory arrays and should never be written to -(the free memory pointer points to ``0x80`` initially). - -Solidity always places new objects at the free memory pointer and -memory is never freed (this might change in the future). - -Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this -is even true for ``byte[]``, but not for ``bytes`` and ``string``). -Multi-dimensional memory arrays are pointers to memory arrays. The length of a -dynamic array is stored at the first slot of the array and followed by the array -elements. - -.. warning:: - There are some operations in Solidity that need a temporary memory area - larger than 64 bytes and therefore will not fit into the scratch space. - They will be placed where the free memory points to, but given their - short lifetime, the pointer is not updated. The memory may or may not - be zeroed out. Because of this, one should not expect the free memory - to point to zeroed out memory. - - While it may seem like a good idea to use ``msize`` to arrive at a - definitely zeroed out memory area, using such a pointer non-temporarily - without updating the free memory pointer can have unexpected results. - -.. index: calldata layout - -******************* -Layout of Call Data -******************* - -The input data for a function call is assumed to be in the format defined by the :ref:`ABI -specification `. Among others, the ABI specification requires arguments to be padded to multiples of 32 -bytes. The internal function calls use a different convention. - -Arguments for the constructor of a contract are directly appended at the end of the -contract's code, also in ABI encoding. The constructor will access them through a hard-coded offset, and -not by using the ``codesize`` opcode, since this of course changes when appending -data to the code. - - -.. index: variable cleanup - -********************************* -Internals - Cleaning Up Variables -********************************* - -When a value is shorter than 256 bit, in some cases the remaining bits -must be cleaned. -The Solidity compiler is designed to clean such remaining bits before any operations -that might be adversely affected by the potential garbage in the remaining bits. -For example, before writing a value to memory, the remaining bits need -to be cleared because the memory contents can be used for computing -hashes or sent as the data of a message call. Similarly, before -storing a value in the storage, the remaining bits need to be cleaned -because otherwise the garbled value can be observed. - -On the other hand, we do not clean the bits if the immediately -following operation is not affected. For instance, since any non-zero -value is considered ``true`` by ``JUMPI`` instruction, we do not clean -the boolean values before they are used as the condition for -``JUMPI``. - -In addition to the design principle above, the Solidity compiler -cleans input data when it is loaded onto the stack. - -Different types have different rules for cleaning up invalid values: - -+---------------+---------------+-------------------+ -|Type |Valid Values |Invalid Values Mean| -+===============+===============+===================+ -|enum of n |0 until n - 1 |exception | -|members | | | -+---------------+---------------+-------------------+ -|bool |0 or 1 |1 | -+---------------+---------------+-------------------+ -|signed integers|sign-extended |currently silently | -| |word |wraps; in the | -| | |future exceptions | -| | |will be thrown | -| | | | -| | | | -+---------------+---------------+-------------------+ -|unsigned |higher bits |currently silently | -|integers |zeroed |wraps; in the | -| | |future exceptions | -| | |will be thrown | -+---------------+---------------+-------------------+ - -.. index:: optimizer, common subexpression elimination, constant propagation - -************************* -Internals - The Optimiser -************************* - -This section discusses the optimiser that was first added to Solidity, -which operates on opcode streams. For information on the new Yul-based optimiser, -please see the `readme on github `_. - -The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks -at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser -analyses the instructions and records every modification to the stack, -memory, or storage as an expression which consists of an instruction and -a list of arguments which are pointers to other expressions. The optimiser -uses a component called "CommonSubexpressionEliminator" that amongst other -tasks, finds expressions that are always equal (on every input) and combines -them into an expression class. The optimiser first tries to find each new -expression in a list of already known expressions. If this does not work, -it simplifies the expression according to rules like -``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is -a recursive process, we can also apply the latter rule if the second factor -is a more complex expression where we know that it always evaluates to one. -Modifications to storage and memory locations have to erase knowledge about -storage and memory locations which are not known to be different. If we first -write to location x and then to location y and both are input variables, the -second could overwrite the first, so we do not know what is stored at x after -we wrote to y. If simplification of the expression x - y evaluates to a -non-zero constant, we know that we can keep our knowledge about what is stored at x. - -After this process, we know which expressions have to be on the stack at -the end, and have a list of modifications to memory and storage. This information -is stored together with the basic blocks and is used to link them. Furthermore, -knowledge about the stack, storage and memory configuration is forwarded to -the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions, -we can build a complete control flow graph of the program. If there is only -one target we do not know (this can happen as in principle, jump targets can -be computed from inputs), we have to erase all knowledge about the input state -of a block as it can be the target of the unknown ``JUMP``. If the optimiser -finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it -to an unconditional jump. - -As the last step, the code in each block is re-generated. The optimiser creates -a dependency graph from the expressions on the stack at the end of the block, -and it drops every operation that is not part of this graph. It generates code -that applies the modifications to memory and storage in the order they were -made in the original code (dropping modifications which were found not to be -needed). Finally, it generates all values that are required to be on the -stack in the correct place. - -These steps are applied to each basic block and the newly generated code -is used as replacement if it is smaller. If a basic block is split at a -``JUMPI`` and during the analysis, the condition evaluates to a constant, -the ``JUMPI`` is replaced depending on the value of the constant. Thus code like - -:: - - uint x = 7; - data[7] = 9; - if (data[x] != x + 2) - return 2; - else - return 1; - -still simplifies to code which you can compile even though the instructions contained -a jump in the beginning of the process: - -:: - - data[7] = 9; - return 1; - -.. index:: source mappings - -*************** -Source Mappings -*************** - -As part of the AST output, the compiler provides the range of the source -code that is represented by the respective node in the AST. This can be -used for various purposes ranging from static analysis tools that report -errors based on the AST and debugging tools that highlight local variables -and their uses. - -Furthermore, the compiler can also generate a mapping from the bytecode -to the range in the source code that generated the instruction. This is again -important for static analysis tools that operate on bytecode level and -for displaying the current position in the source code inside a debugger -or for breakpoint handling. This mapping also contains other information, -like the jump type and the modifier depth (see below). - -Both kinds of source mappings use integer identifiers to refer to source files. -The identifier of a source file is stored in -``output['sources'][sourceName]['id']`` where ``output`` is the output of the -standard-json compiler interface parsed as JSON. - -.. note :: - In the case of instructions that are not associated with any particular source file, - the source mapping assigns an integer identifier of ``-1``. This may happen for - bytecode sections stemming from compiler-generated inline assembly statements. - -The source mappings inside the AST use the following -notation: - -``s:l:f`` - -Where ``s`` is the byte-offset to the start of the range in the source file, -``l`` is the length of the source range in bytes and ``f`` is the source -index mentioned above. - -The encoding in the source mapping for the bytecode is more complicated: -It is a list of ``s:l:f:j:m`` separated by ``;``. Each of these -elements corresponds to an instruction, i.e. you cannot use the byte offset -but have to use the instruction offset (push instructions are longer than a single byte). -The fields ``s``, ``l`` and ``f`` are as above. ``j`` can be either -``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a -function, returns from a function or is a regular jump as part of e.g. a loop. -The last field, ``m``, is an integer that denotes the "modifier depth". This depth -is increased whenever the placeholder statement (``_``) is entered in a modifier -and decreased when it is left again. This allows debuggers to track tricky cases -like the same modifier being used twice or multiple placeholder statements being -used in a single modifier. - -In order to compress these source mappings especially for bytecode, the -following rules are used: - - - If a field is empty, the value of the preceding element is used. - - If a ``:`` is missing, all following fields are considered empty. - -This means the following source mappings represent the same information: - -``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2`` - -``1:2:1;:9;2:1:2;;`` - -*************** -Tips and Tricks -*************** - -* Use ``delete`` on arrays to delete all its elements. -* Use shorter types for struct elements and sort them such that short types are - grouped together. This can lower the gas costs as multiple ``SSTORE`` operations - might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is - what you want to optimise). Use the gas price estimator (with optimiser enabled) to check! -* Make your state variables public - the compiler creates :ref:`getters ` for you automatically. -* If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`. -* Initialize storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});`` - -.. note:: - If the storage struct has tightly packed properties, initialize it with separate - assignments: ``x.a = 1; x.b = 2;``. In this way it will be easier for the - optimizer to update storage in one go, thus making assignment cheaper. - -********** -Cheatsheet -********** - -.. index:: precedence - -.. _order: - -Order of Precedence of Operators -================================ - -The following is the order of precedence for operators, listed in order of evaluation. - -+------------+-------------------------------------+--------------------------------------------+ -| Precedence | Description | Operator | -+============+=====================================+============================================+ -| *1* | Postfix increment and decrement | ``++``, ``--`` | -+ +-------------------------------------+--------------------------------------------+ -| | New expression | ``new `` | -+ +-------------------------------------+--------------------------------------------+ -| | Array subscripting | ``[]`` | -+ +-------------------------------------+--------------------------------------------+ -| | Member access | ``.`` | -+ +-------------------------------------+--------------------------------------------+ -| | Function-like call | ``()`` | -+ +-------------------------------------+--------------------------------------------+ -| | Parentheses | ``()`` | -+------------+-------------------------------------+--------------------------------------------+ -| *2* | Prefix increment and decrement | ``++``, ``--`` | -+ +-------------------------------------+--------------------------------------------+ -| | Unary minus | ``-`` | -+ +-------------------------------------+--------------------------------------------+ -| | Unary operations | ``delete`` | -+ +-------------------------------------+--------------------------------------------+ -| | Logical NOT | ``!`` | -+ +-------------------------------------+--------------------------------------------+ -| | Bitwise NOT | ``~`` | -+------------+-------------------------------------+--------------------------------------------+ -| *3* | Exponentiation | ``**`` | -+------------+-------------------------------------+--------------------------------------------+ -| *4* | Multiplication, division and modulo | ``*``, ``/``, ``%`` | -+------------+-------------------------------------+--------------------------------------------+ -| *5* | Addition and subtraction | ``+``, ``-`` | -+------------+-------------------------------------+--------------------------------------------+ -| *6* | Bitwise shift operators | ``<<``, ``>>`` | -+------------+-------------------------------------+--------------------------------------------+ -| *7* | Bitwise AND | ``&`` | -+------------+-------------------------------------+--------------------------------------------+ -| *8* | Bitwise XOR | ``^`` | -+------------+-------------------------------------+--------------------------------------------+ -| *9* | Bitwise OR | ``|`` | -+------------+-------------------------------------+--------------------------------------------+ -| *10* | Inequality operators | ``<``, ``>``, ``<=``, ``>=`` | -+------------+-------------------------------------+--------------------------------------------+ -| *11* | Equality operators | ``==``, ``!=`` | -+------------+-------------------------------------+--------------------------------------------+ -| *12* | Logical AND | ``&&`` | -+------------+-------------------------------------+--------------------------------------------+ -| *13* | Logical OR | ``||`` | -+------------+-------------------------------------+--------------------------------------------+ -| *14* | Ternary operator | `` ? : `` | -+ +-------------------------------------+--------------------------------------------+ -| | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, | -| | | ``>>=``, ``+=``, ``-=``, ``*=``, ``/=``, | -| | | ``%=`` | -+------------+-------------------------------------+--------------------------------------------+ -| *15* | Comma operator | ``,`` | -+------------+-------------------------------------+--------------------------------------------+ - -.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send - -Global Variables -================ - -- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI `-decodes - the provided data. The types are given in parentheses as second argument. - Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))`` -- ``abi.encode(...) returns (bytes memory)``: :ref:`ABI `-encodes the given arguments -- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding ` of - the given arguments. Note that this encoding can be ambiguous! -- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI `-encodes - the given arguments starting from the second and prepends the given four-byte selector -- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent - to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)``` -- ``block.coinbase`` (``address payable``): current block miner's address -- ``block.difficulty`` (``uint``): current block difficulty -- ``block.gaslimit`` (``uint``): current block gaslimit -- ``block.number`` (``uint``): current block number -- ``block.timestamp`` (``uint``): current block timestamp -- ``gasleft() returns (uint256)``: remaining gas -- ``msg.data`` (``bytes``): complete calldata -- ``msg.sender`` (``address payable``): sender of the message (current call) -- ``msg.value`` (``uint``): number of wei sent with the message -- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``) -- ``tx.gasprice`` (``uint``): gas price of the transaction -- ``tx.origin`` (``address payable``): sender of the transaction (full call chain) -- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error) -- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use - for malformed input or error in external component) -- ``require(bool condition, string memory message)``: abort execution and revert state changes if - condition is ``false`` (use for malformed input or error in external component). Also provide error message. -- ``revert()``: abort execution and revert state changes -- ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string -- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks -- ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input -- ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input -- ``ripemd160(bytes memory) returns (bytes20)``: compute the RIPEMD-160 hash of the input -- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with - the public key from elliptic curve signature, return zero on error -- ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with - arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. -- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed - with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. -- ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` or ``address payable`` -- ``super``: the contract one level higher in the inheritance hierarchy -- ``selfdestruct(address payable recipient)``: destroy the current contract, sending its funds to the given address -- ``
.balance`` (``uint256``): balance of the :ref:`address` in Wei -- ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, - returns ``false`` on failure -- ``
.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure -- ``type(C).name`` (``string``): the name of the contract -- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information`. -- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information`. - -.. note:: - Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness, - unless you know what you are doing. - - Both the timestamp and the block hash can be influenced by miners to some degree. - Bad actors in the mining community can for example run a casino payout function on a chosen hash - and just retry a different hash if they did not receive any money. - - The current block timestamp must be strictly larger than the timestamp of the last block, - but the only guarantee is that it will be somewhere between the timestamps of two - consecutive blocks in the canonical chain. - -.. note:: - The block hashes are not available for all blocks for scalability reasons. - You can only access the hashes of the most recent 256 blocks, all other - values will be zero. - -.. note:: - In version 0.5.0, the following aliases were removed: ``suicide`` as alias for ``selfdestruct``, - ``msg.gas`` as alias for ``gasleft``, ``block.blockhash`` as alias for ``blockhash`` and - ``sha3`` as alias for ``keccak256``. - -.. index:: visibility, public, private, external, internal - -Function Visibility Specifiers -============================== - -:: - - function myFunction() returns (bool) { - return true; - } - -- ``public``: visible externally and internally (creates a :ref:`getter function` for storage/state variables) -- ``private``: only visible in the current contract -- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``) -- ``internal``: only visible internally - - -.. index:: modifiers, pure, view, payable, constant, anonymous, indexed - -Modifiers -========= - -- ``pure`` for functions: Disallows modification or access of state. -- ``view`` for functions: Disallows modification of state. -- ``payable`` for functions: Allows them to receive Ether together with a call. -- ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot. -- ``immutable`` for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code. -- ``anonymous`` for events: Does not store event signature as topic. -- ``indexed`` for event parameters: Stores the parameter as topic. -- ``virtual`` for functions and modifiers: Allows the function's or modifier's - behaviour to be changed in derived contracts. -- ``override``: States that this function, modifier or public state variable changes - the behaviour of a function or modifier in a base contract. - -Reserved Keywords -================= - -These keywords are reserved in Solidity. They might become part of the syntax in the future: - -``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``, -``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``, -``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``, -``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``, -``unchecked``. - -Language Grammar -================ - -.. literalinclude:: Solidity.g4 - :language: antlr diff --git a/docs/solidity-in-depth.rst b/docs/solidity-in-depth.rst deleted file mode 100644 index 111a100d7..000000000 --- a/docs/solidity-in-depth.rst +++ /dev/null @@ -1,22 +0,0 @@ -################# -Solidity in Depth -################# - -This section should provide you with all you need to know about Solidity. -If something is missing here, please contact us on -`Gitter `_ or create a pull request on -`Github `_. - -.. toctree:: - :maxdepth: 2 - - layout-of-source-files.rst - structure-of-a-contract.rst - types.rst - units-and-global-variables.rst - control-structures.rst - contracts.rst - assembly.rst - miscellaneous.rst - 050-breaking-changes.rst - 060-breaking-changes.rst From e65a5a562e69607756e12e8796b3705afebff074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 23 Apr 2020 21:16:41 +0200 Subject: [PATCH 24/55] IRGenerationContext::internalDispatch(): Fix code generated when the function called via pointer does not return anything --- .../codegen/ir/IRGenerationContext.cpp | 3 ++- ...function_returning_nothing_via_pointer.sol | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 test/libsolidity/semanticTests/functionCall/call_function_returning_nothing_via_pointer.sol diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index c3b896ee1..0410d8835 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -122,7 +122,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out) <#cases> case { - := () + () } default { invalid() } @@ -133,6 +133,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out) YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions); templ("in", suffixedVariableNameList("in_", 0, _in)); templ("arrow", _out > 0 ? "->" : ""); + templ("assignment_op", _out > 0 ? ":=" : ""); templ("out", suffixedVariableNameList("out_", 0, _out)); vector> functions; for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts) diff --git a/test/libsolidity/semanticTests/functionCall/call_function_returning_nothing_via_pointer.sol b/test/libsolidity/semanticTests/functionCall/call_function_returning_nothing_via_pointer.sol new file mode 100644 index 000000000..569d185bd --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/call_function_returning_nothing_via_pointer.sol @@ -0,0 +1,19 @@ +contract test { + bool public flag = false; + + function f0() public { + flag = true; + } + + function f() public returns (bool) { + function() internal x = f0; + x(); + return flag; + } +} + +// ==== +// compileViaYul: also +// ---- +// f() -> true +// flag() -> true From aafa4583df7a9989be7d8350ddb92bfa81f8426c Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Thu, 23 Apr 2020 17:14:03 -0500 Subject: [PATCH 25/55] [Sol - Yul] Add support for built-in gasleft(). --- libsolidity/codegen/ir/IRGeneratorForStatements.cpp | 5 +++++ test/libsolidity/semanticTests/various/gasleft_decrease.sol | 2 ++ 2 files changed, 7 insertions(+) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index c572d5d8a..908e1d48c 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -771,6 +771,11 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) { break; } + case FunctionType::Kind::GasLeft: + { + define(_functionCall) << "gas()\n"; + break; + } default: solUnimplemented("FunctionKind " + toString(static_cast(functionType->kind())) + " not yet implemented"); } diff --git a/test/libsolidity/semanticTests/various/gasleft_decrease.sol b/test/libsolidity/semanticTests/various/gasleft_decrease.sol index 8de56296e..b6251205e 100644 --- a/test/libsolidity/semanticTests/various/gasleft_decrease.sol +++ b/test/libsolidity/semanticTests/various/gasleft_decrease.sol @@ -15,6 +15,8 @@ contract C { } } +// ==== +// compileViaYul: also // ---- // f() -> true // g() -> true From 8717c073a67d1a7f44cb61a78df15e6be5de1927 Mon Sep 17 00:00:00 2001 From: a3d4 Date: Thu, 23 Apr 2020 08:23:01 +0200 Subject: [PATCH 26/55] Fix leftpad in SourceReferenceFormatterHuman --- liblangutil/SourceReferenceFormatterHuman.cpp | 25 +++-- test/cmdlineTests/message_format/err | 50 +++++++++ test/cmdlineTests/message_format/input.sol | 103 ++++++++++++++++++ test/cmdlineTests/too_long_line/err | 2 +- .../too_long_line_both_sides_short/err | 2 +- test/cmdlineTests/too_long_line_edge_in/err | 2 +- test/cmdlineTests/too_long_line_edge_out/err | 2 +- .../cmdlineTests/too_long_line_left_short/err | 2 +- test/cmdlineTests/too_long_line_multiline/err | 2 +- .../too_long_line_right_short/err | 2 +- 10 files changed, 173 insertions(+), 19 deletions(-) create mode 100644 test/cmdlineTests/message_format/err create mode 100644 test/cmdlineTests/message_format/input.sol diff --git a/liblangutil/SourceReferenceFormatterHuman.cpp b/liblangutil/SourceReferenceFormatterHuman.cpp index 669fa2007..430883996 100644 --- a/liblangutil/SourceReferenceFormatterHuman.cpp +++ b/liblangutil/SourceReferenceFormatterHuman.cpp @@ -70,35 +70,36 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ if (_ref.sourceName.empty()) return; // Nothing we can print here - int const leftpad = static_cast(log10(max(_ref.position.line, 1))) + 1; - - // line 0: source name - frameColored() << string(leftpad, ' ') << "--> "; - if (_ref.position.line < 0) { + frameColored() << "--> "; m_stream << _ref.sourceName << "\n"; return; // No line available, nothing else to print } - m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n'; + string line = std::to_string(_ref.position.line + 1); // one-based line number as string + string leftpad = string(line.size(), ' '); + + // line 0: source name + frameColored() << leftpad << "--> "; + m_stream << _ref.sourceName << ":" << line << ":" << (_ref.position.column + 1) << ":" << '\n'; if (!_ref.multiline) { int const locationLength = _ref.endColumn - _ref.startColumn; // line 1: - m_stream << string(leftpad, ' '); + m_stream << leftpad; frameColored() << " |" << '\n'; // line 2: - frameColored() << (_ref.position.line + 1) << " | "; + frameColored() << line << " | "; m_stream << _ref.text.substr(0, _ref.startColumn); highlightColored() << _ref.text.substr(_ref.startColumn, locationLength); m_stream << _ref.text.substr(_ref.endColumn) << '\n'; // line 3: - m_stream << string(leftpad, ' '); + m_stream << leftpad; frameColored() << " | "; for_each( _ref.text.cbegin(), @@ -110,16 +111,16 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ else { // line 1: - m_stream << string(leftpad, ' '); + m_stream << leftpad; frameColored() << " |" << '\n'; // line 2: - frameColored() << (_ref.position.line + 1) << " | "; + frameColored() << line << " | "; m_stream << _ref.text.substr(0, _ref.startColumn); highlightColored() << _ref.text.substr(_ref.startColumn) << '\n'; // line 3: - frameColored() << string(leftpad, ' ') << " | "; + frameColored() << leftpad << " | "; m_stream << string(_ref.startColumn, ' '); diagColored() << "^ (Relevant source part starts here and spans across multiple lines).\n"; } diff --git a/test/cmdlineTests/message_format/err b/test/cmdlineTests/message_format/err new file mode 100644 index 000000000..eee8267e2 --- /dev/null +++ b/test/cmdlineTests/message_format/err @@ -0,0 +1,50 @@ +Warning: Source file does not specify required compiler version! +--> message_format/input.sol + +Warning: Unused local variable. + --> message_format/input.sol:9:27: + | +9 | function f() public { int x; } + | ^^^^^ + +Warning: Unused local variable. + --> message_format/input.sol:10:27: + | +10 | function g() public { int x; } + | ^^^^^ + +Warning: Unused local variable. + --> message_format/input.sol:99:14: + | +99 | /**/ int a; /**/ + | ^^^^^ + +Warning: Unused local variable. + --> message_format/input.sol:100:14: + | +100 | /**/ int b; /**/ + | ^^^^^ + +Warning: Unused local variable. + --> message_format/input.sol:101:14: + | +101 | /**/ int c; /**/ + | ^^^^^ + +Warning: Function state mutability can be restricted to pure + --> message_format/input.sol:9:5: + | +9 | function f() public { int x; } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Warning: Function state mutability can be restricted to pure + --> message_format/input.sol:10:5: + | +10 | function g() public { int x; } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Warning: Function state mutability can be restricted to pure + --> message_format/input.sol:11:5: + | +11 | function h() public { + | ^ (Relevant source part starts here and spans across multiple lines). diff --git a/test/cmdlineTests/message_format/input.sol b/test/cmdlineTests/message_format/input.sol new file mode 100644 index 000000000..6f854f96a --- /dev/null +++ b/test/cmdlineTests/message_format/input.sol @@ -0,0 +1,103 @@ +// checks that error messages around power-or-10 lines are formatted correctly + + + + + + +contract C { + function f() public { int x; } + function g() public { int x; } + function h() public { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /**/ int a; /**/ + /**/ int b; /**/ + /**/ int c; /**/ + } +} diff --git a/test/cmdlineTests/too_long_line/err b/test/cmdlineTests/too_long_line/err index 87612fe59..6e5119037 100644 --- a/test/cmdlineTests/too_long_line/err +++ b/test/cmdlineTests/too_long_line/err @@ -1,5 +1,5 @@ Warning: Source file does not specify required compiler version! - --> too_long_line/input.sol +--> too_long_line/input.sol Error: Identifier not found or not unique. --> too_long_line/input.sol:2:164: diff --git a/test/cmdlineTests/too_long_line_both_sides_short/err b/test/cmdlineTests/too_long_line_both_sides_short/err index 81f44930f..34bd25d28 100644 --- a/test/cmdlineTests/too_long_line_both_sides_short/err +++ b/test/cmdlineTests/too_long_line_both_sides_short/err @@ -1,5 +1,5 @@ Warning: Source file does not specify required compiler version! - --> too_long_line_both_sides_short/input.sol +--> too_long_line_both_sides_short/input.sol Error: Identifier not found or not unique. --> too_long_line_both_sides_short/input.sol:2:15: diff --git a/test/cmdlineTests/too_long_line_edge_in/err b/test/cmdlineTests/too_long_line_edge_in/err index b00bec7a1..2988bc44e 100644 --- a/test/cmdlineTests/too_long_line_edge_in/err +++ b/test/cmdlineTests/too_long_line_edge_in/err @@ -1,5 +1,5 @@ Warning: Source file does not specify required compiler version! - --> too_long_line_edge_in/input.sol +--> too_long_line_edge_in/input.sol Error: Identifier not found or not unique. --> too_long_line_edge_in/input.sol:2:36: diff --git a/test/cmdlineTests/too_long_line_edge_out/err b/test/cmdlineTests/too_long_line_edge_out/err index 3de913b7d..a85faa44f 100644 --- a/test/cmdlineTests/too_long_line_edge_out/err +++ b/test/cmdlineTests/too_long_line_edge_out/err @@ -1,5 +1,5 @@ Warning: Source file does not specify required compiler version! - --> too_long_line_edge_out/input.sol +--> too_long_line_edge_out/input.sol Error: Identifier not found or not unique. --> too_long_line_edge_out/input.sol:2:37: diff --git a/test/cmdlineTests/too_long_line_left_short/err b/test/cmdlineTests/too_long_line_left_short/err index a6ddfca81..aa36b1068 100644 --- a/test/cmdlineTests/too_long_line_left_short/err +++ b/test/cmdlineTests/too_long_line_left_short/err @@ -1,5 +1,5 @@ Warning: Source file does not specify required compiler version! - --> too_long_line_left_short/input.sol +--> too_long_line_left_short/input.sol Error: Identifier not found or not unique. --> too_long_line_left_short/input.sol:2:15: diff --git a/test/cmdlineTests/too_long_line_multiline/err b/test/cmdlineTests/too_long_line_multiline/err index 5a2655174..2aa801623 100644 --- a/test/cmdlineTests/too_long_line_multiline/err +++ b/test/cmdlineTests/too_long_line_multiline/err @@ -5,4 +5,4 @@ Error: No visibility specified. Did you intend to add "public"? | ^ (Relevant source part starts here and spans across multiple lines). Warning: Source file does not specify required compiler version! - --> too_long_line_multiline/input.sol +--> too_long_line_multiline/input.sol diff --git a/test/cmdlineTests/too_long_line_right_short/err b/test/cmdlineTests/too_long_line_right_short/err index f5801e616..d2d8f3980 100644 --- a/test/cmdlineTests/too_long_line_right_short/err +++ b/test/cmdlineTests/too_long_line_right_short/err @@ -1,5 +1,5 @@ Warning: Source file does not specify required compiler version! - --> too_long_line_right_short/input.sol +--> too_long_line_right_short/input.sol Error: Identifier not found or not unique. --> too_long_line_right_short/input.sol:2:164: From 523460da077585430094654a09f760aca6fa0aa0 Mon Sep 17 00:00:00 2001 From: a3d4 Date: Fri, 24 Apr 2020 00:51:15 +0200 Subject: [PATCH 27/55] Prevent coloring irrelevant whitespaces --- liblangutil/SourceReferenceFormatterHuman.cpp | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/liblangutil/SourceReferenceFormatterHuman.cpp b/liblangutil/SourceReferenceFormatterHuman.cpp index 430883996..ca947314e 100644 --- a/liblangutil/SourceReferenceFormatterHuman.cpp +++ b/liblangutil/SourceReferenceFormatterHuman.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include using namespace std; @@ -72,8 +71,8 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ if (_ref.position.line < 0) { - frameColored() << "--> "; - m_stream << _ref.sourceName << "\n"; + frameColored() << "-->"; + m_stream << ' ' << _ref.sourceName << '\n'; return; // No line available, nothing else to print } @@ -81,48 +80,55 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ string leftpad = string(line.size(), ' '); // line 0: source name - frameColored() << leftpad << "--> "; - m_stream << _ref.sourceName << ":" << line << ":" << (_ref.position.column + 1) << ":" << '\n'; + m_stream << leftpad; + frameColored() << "-->"; + m_stream << ' ' << _ref.sourceName << ':' << line << ':' << (_ref.position.column + 1) << ":\n"; if (!_ref.multiline) { int const locationLength = _ref.endColumn - _ref.startColumn; // line 1: - m_stream << leftpad; - frameColored() << " |" << '\n'; + m_stream << leftpad << ' '; + frameColored() << '|'; + m_stream << '\n'; // line 2: - frameColored() << line << " | "; - m_stream << _ref.text.substr(0, _ref.startColumn); + frameColored() << line << " |"; + m_stream << ' ' << _ref.text.substr(0, _ref.startColumn); highlightColored() << _ref.text.substr(_ref.startColumn, locationLength); m_stream << _ref.text.substr(_ref.endColumn) << '\n'; // line 3: - m_stream << leftpad; - frameColored() << " | "; + m_stream << leftpad << ' '; + frameColored() << '|'; + m_stream << ' '; for_each( _ref.text.cbegin(), _ref.text.cbegin() + _ref.startColumn, [this](char ch) { m_stream << (ch == '\t' ? '\t' : ' '); } ); - diagColored() << string(locationLength, '^') << '\n'; + diagColored() << string(locationLength, '^'); + m_stream << '\n'; } else { // line 1: - m_stream << leftpad; - frameColored() << " |" << '\n'; + m_stream << leftpad << ' '; + frameColored() << '|'; + m_stream << '\n'; // line 2: - frameColored() << line << " | "; - m_stream << _ref.text.substr(0, _ref.startColumn); + frameColored() << line << " |"; + m_stream << ' ' << _ref.text.substr(0, _ref.startColumn); highlightColored() << _ref.text.substr(_ref.startColumn) << '\n'; // line 3: - frameColored() << leftpad << " | "; - m_stream << string(_ref.startColumn, ' '); - diagColored() << "^ (Relevant source part starts here and spans across multiple lines).\n"; + m_stream << leftpad << ' '; + frameColored() << '|'; + m_stream << ' ' << string(_ref.startColumn, ' '); + diagColored() << "^ (Relevant source part starts here and spans across multiple lines)."; + m_stream << '\n'; } } From 059d0bdebbe45edffb676b19ff49a112325e5b99 Mon Sep 17 00:00:00 2001 From: Leonardo Alt Date: Fri, 24 Apr 2020 11:55:58 +0200 Subject: [PATCH 28/55] Revert "Use Spacer option to improve performance of constant arrays" This reverts commit 92059fa84848ce7f78d93a8af720bef034b74fde. --- libsolidity/formal/Z3CHCInterface.cpp | 2 -- .../functions/functions_recursive_indirect.sol | 1 + ...for_loop_array_assignment_storage_memory.sol | 4 +++- ...or_loop_array_assignment_storage_storage.sol | 1 + ...le_loop_array_assignment_storage_storage.sol | 6 ++++-- .../operators/delete_array_index_2d.sol | 1 - .../smtCheckerTests/types/unused_mapping.sol | 17 ----------------- 7 files changed, 9 insertions(+), 23 deletions(-) delete mode 100644 test/libsolidity/smtCheckerTests/types/unused_mapping.sol diff --git a/libsolidity/formal/Z3CHCInterface.cpp b/libsolidity/formal/Z3CHCInterface.cpp index dcfd19c86..c64da2edc 100644 --- a/libsolidity/formal/Z3CHCInterface.cpp +++ b/libsolidity/formal/Z3CHCInterface.cpp @@ -43,8 +43,6 @@ Z3CHCInterface::Z3CHCInterface(): p.set("fp.spacer.mbqi", false); // Ground pobs by using values from a model. p.set("fp.spacer.ground_pobs", false); - // Limits array reasoning, good for constant arrays. - p.set("fp.spacer.weak_abs", false); m_solver.set(p); } diff --git a/test/libsolidity/smtCheckerTests/functions/functions_recursive_indirect.sol b/test/libsolidity/smtCheckerTests/functions/functions_recursive_indirect.sol index 7cb7b22b1..5d3292992 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_recursive_indirect.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_recursive_indirect.sol @@ -22,3 +22,4 @@ contract C } } // ---- +// Warning: (130-144): Error trying to invoke SMT solver. diff --git a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol index d7d88424a..0b301505b 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol @@ -12,10 +12,12 @@ contract LoopFor2 { b[i] = i + 1; c[i] = b[i]; } + // This is safe but too hard to solve currently. assert(b[0] == c[0]); assert(a[0] == 900); assert(b[0] == 900); } } // ---- -// Warning: (312-331): Assertion violation happens here +// Warning: (316-336): Assertion violation happens here +// Warning: (363-382): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol index a90053024..333273781 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol @@ -19,5 +19,6 @@ contract LoopFor2 { } } // ---- +// Warning: (317-337): Assertion violation happens here // Warning: (341-360): Assertion violation happens here // Warning: (364-383): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol index 7670ddb6d..92d1ded3e 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol @@ -14,11 +14,13 @@ contract LoopFor2 { c[i] = b[i]; ++i; } + // Fails as false positive. assert(b[0] == c[0]); assert(a[0] == 900); assert(b[0] == 900); } } // ---- -// Warning: (290-309): Assertion violation happens here -// Warning: (313-332): Assertion violation happens here +// Warning: (296-316): Assertion violation happens here +// Warning: (320-339): Assertion violation happens here +// Warning: (343-362): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol b/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol index cb5a0799f..8a1ba5e6e 100644 --- a/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol +++ b/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol @@ -16,5 +16,4 @@ contract C // ==== // SMTSolvers: z3 // ---- -// Warning: (174-194): Error trying to invoke SMT solver. // Warning: (174-194): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/unused_mapping.sol b/test/libsolidity/smtCheckerTests/types/unused_mapping.sol deleted file mode 100644 index f12cd41de..000000000 --- a/test/libsolidity/smtCheckerTests/types/unused_mapping.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma experimental SMTChecker; - -contract C { - uint x; - uint y; - mapping (address => bool) public never_used; - - function inc() public { - require(x < 10); - require(y < 10); - - if(x == 0) x = 0; // noop state var read - x++; - y++; - assert(y == x); - } -} From 397ea18b788b0b1277c305a13a14a0b3b3bdce36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 20 Apr 2020 16:21:57 +0200 Subject: [PATCH 29/55] IRVariable: Fix improperly wrapped docstring --- libsolidity/codegen/ir/IRVariable.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libsolidity/codegen/ir/IRVariable.h b/libsolidity/codegen/ir/IRVariable.h index 8967f55fa..ba4b1b93a 100644 --- a/libsolidity/codegen/ir/IRVariable.h +++ b/libsolidity/codegen/ir/IRVariable.h @@ -29,8 +29,7 @@ class Expression; /** * An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression - * of a specific S - * olidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable. + * of a specific Solidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable. * Otherwise the set of yul variables it refers to is (recursively) determined by @see ``Type::stackItems()``. * For example, an IRVariable referring to a dynamically sized calldata array will consist of two parts named * ``offset`` and ``length``, whereas an IRVariable referring to a statically sized calldata type, a storage reference From 56a85d6cb32df4e0d137d74a178c2bcc0bf6319c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 20 Apr 2020 23:21:09 +0200 Subject: [PATCH 30/55] IRGeneratorForStatements: Handle internal calls to functions from specific base contracts as static calls rather than calls via pointers --- .../codegen/ir/IRGeneratorForStatements.cpp | 68 +++++++++++++------ .../functionCall/base_base_overload.sol | 2 + .../functionCall/base_overload.sol | 2 + 3 files changed, 52 insertions(+), 20 deletions(-) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 908e1d48c..c7a873e60 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -579,32 +579,60 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) else args.emplace_back(convert(*arguments[i], *parameterTypes[i]).commaSeparatedList()); - if (auto identifier = dynamic_cast(&_functionCall.expression())) + optional functionDef; + if (auto memberAccess = dynamic_cast(&_functionCall.expression())) { solAssert(!functionType->bound(), ""); - if (auto functionDef = dynamic_cast(identifier->annotation().referencedDeclaration)) + + functionDef = dynamic_cast(memberAccess->annotation().referencedDeclaration); + if (functionDef.value() != nullptr) + solAssert(functionType->declaration() == *memberAccess->annotation().referencedDeclaration, ""); + else { - define(_functionCall) << - m_context.enqueueFunctionForCodeGeneration( - functionDef->resolveVirtual(m_context.mostDerivedContract()) - ) << - "(" << - joinHumanReadable(args) << - ")\n"; - return; + solAssert(dynamic_cast(memberAccess->annotation().referencedDeclaration), ""); + solAssert(!functionType->hasDeclaration(), ""); } } + else if (auto identifier = dynamic_cast(&_functionCall.expression())) + { + solAssert(!functionType->bound(), ""); - define(_functionCall) << - // NOTE: internalDispatch() takes care of adding the function to function generation queue - m_context.internalDispatch( - TupleType(functionType->parameterTypes()).sizeOnStack(), - TupleType(functionType->returnParameterTypes()).sizeOnStack() - ) << - "(" << - IRVariable(_functionCall.expression()).part("functionIdentifier").name() << - joinHumanReadablePrefixed(args) << - ")\n"; + if (auto unresolvedFunctionDef = dynamic_cast(identifier->annotation().referencedDeclaration)) + { + functionDef = &unresolvedFunctionDef->resolveVirtual(m_context.mostDerivedContract()); + solAssert(functionType->declaration() == *identifier->annotation().referencedDeclaration, ""); + } + else + { + functionDef = nullptr; + solAssert(dynamic_cast(identifier->annotation().referencedDeclaration), ""); + solAssert(!functionType->hasDeclaration(), ""); + } + } + else + // Not a simple expression like x or A.x + functionDef = nullptr; + + solAssert(functionDef.has_value(), ""); + solAssert(functionDef.value() == nullptr || functionDef.value()->isImplemented(), ""); + + if (functionDef.value() != nullptr) + define(_functionCall) << + m_context.enqueueFunctionForCodeGeneration(*functionDef.value()) << + "(" << + joinHumanReadable(args) << + ")\n"; + else + define(_functionCall) << + // NOTE: internalDispatch() takes care of adding the function to function generation queue + m_context.internalDispatch( + TupleType(functionType->parameterTypes()).sizeOnStack(), + TupleType(functionType->returnParameterTypes()).sizeOnStack() + ) << + "(" << + IRVariable(_functionCall.expression()).part("functionIdentifier").name() << + joinHumanReadablePrefixed(args) << + ")\n"; break; } case FunctionType::Kind::External: diff --git a/test/libsolidity/semanticTests/functionCall/base_base_overload.sol b/test/libsolidity/semanticTests/functionCall/base_base_overload.sol index adf1200ee..c1ba1c1ff 100644 --- a/test/libsolidity/semanticTests/functionCall/base_base_overload.sol +++ b/test/libsolidity/semanticTests/functionCall/base_base_overload.sol @@ -34,6 +34,8 @@ contract Child is Base { BaseBase.init(c, d); } } +// ==== +// compileViaYul: also // ---- // x() -> 0 // y() -> 0 diff --git a/test/libsolidity/semanticTests/functionCall/base_overload.sol b/test/libsolidity/semanticTests/functionCall/base_overload.sol index 924431ec5..be4a73809 100644 --- a/test/libsolidity/semanticTests/functionCall/base_overload.sol +++ b/test/libsolidity/semanticTests/functionCall/base_overload.sol @@ -18,6 +18,8 @@ contract Child is Base { Base.init(c, d); } } +// ==== +// compileViaYul: also // ---- // x() -> 0 // y() -> 0 From 64bce597a164c917261c618be355ad47976617ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Mon, 20 Apr 2020 16:22:39 +0200 Subject: [PATCH 31/55] IRGenerator: Enable code generation for libraries --- libsolidity/codegen/ir/IRGenerator.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 92631ddc2..68a6f2a9d 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -75,15 +75,15 @@ pair IRGenerator::run(ContractDefinition const& _contract) string IRGenerator::generate(ContractDefinition const& _contract) { - solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented."); - Whiskers t(R"( object "" { code { + let := () () + } @@ -101,6 +101,7 @@ string IRGenerator::generate(ContractDefinition const& _contract) t("CreationObject", creationObjectName(_contract)); t("memoryInit", memoryInit()); + t("notLibrary", !_contract.isLibrary()); FunctionDefinition const* constructor = _contract.constructor(); t("callValueCheck", !constructor || !constructor->isPayable() ? callValueCheck() : ""); From d3da8782002f596c49c95eec98872f92515bc636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 23 Apr 2020 15:59:42 +0200 Subject: [PATCH 32/55] Enable internal library calls --- libsolidity/codegen/ir/IRGenerationContext.cpp | 5 +++++ .../codegen/ir/IRGeneratorForStatements.cpp | 14 +++++++++++--- .../inherited_function_from_a_library.sol | 2 ++ .../libraries/internal_library_function.sol | 2 ++ .../internal_library_function_calling_private.sol | 2 ++ .../semanticTests/libraries/stub_internal.sol | 2 ++ 6 files changed, 24 insertions(+), 3 deletions(-) diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index 0410d8835..aa6ec7619 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -135,6 +135,11 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out) templ("arrow", _out > 0 ? "->" : ""); templ("assignment_op", _out > 0 ? ":=" : ""); templ("out", suffixedVariableNameList("out_", 0, _out)); + + // UNIMPLEMENTED: Internal library calls via pointers are not implemented yet. + // We're not generating code for internal library functions here even though it's possible + // to call them via pointers. Right now such calls end up triggering the `default` case in + // the switch above. vector> functions; for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts) for (FunctionDefinition const* function: contract->definedFunctions()) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index c7a873e60..08a5fa900 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -567,6 +567,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]); } + if (auto memberAccess = dynamic_cast(&_functionCall.expression())) + if (auto expressionType = dynamic_cast(memberAccess->expression().annotation().type)) + if (auto contractType = dynamic_cast(expressionType->actualType())) + solUnimplementedAssert( + !contractType->contractDefinition().isLibrary() || functionType->kind() == FunctionType::Kind::Internal, + "Only internal function calls implemented for libraries" + ); + solUnimplementedAssert(!functionType->bound(), ""); switch (functionType->kind()) { @@ -582,7 +590,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) optional functionDef; if (auto memberAccess = dynamic_cast(&_functionCall.expression())) { - solAssert(!functionType->bound(), ""); + solUnimplementedAssert(!functionType->bound(), "Internal calls to bound functions are not yet implemented for libraries and not allowed for contracts"); functionDef = dynamic_cast(memberAccess->annotation().referencedDeclaration); if (functionDef.value() != nullptr) @@ -1325,9 +1333,9 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier) define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n"; else if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) handleVariableReference(*varDecl, _identifier); - else if (auto contract = dynamic_cast(declaration)) + else if (dynamic_cast(declaration)) { - solUnimplementedAssert(!contract->isLibrary(), "Libraries not yet supported."); + // no-op } else if (dynamic_cast(declaration)) { diff --git a/test/libsolidity/semanticTests/intheritance/inherited_function_from_a_library.sol b/test/libsolidity/semanticTests/intheritance/inherited_function_from_a_library.sol index 42d4f711c..3756a4e97 100644 --- a/test/libsolidity/semanticTests/intheritance/inherited_function_from_a_library.sol +++ b/test/libsolidity/semanticTests/intheritance/inherited_function_from_a_library.sol @@ -15,5 +15,7 @@ contract B { } } +// ==== +// compileViaYul: also // ---- // g() -> 1 diff --git a/test/libsolidity/semanticTests/libraries/internal_library_function.sol b/test/libsolidity/semanticTests/libraries/internal_library_function.sol index a3c8a8700..8e8d561e9 100644 --- a/test/libsolidity/semanticTests/libraries/internal_library_function.sol +++ b/test/libsolidity/semanticTests/libraries/internal_library_function.sol @@ -17,5 +17,7 @@ contract C { } } +// ==== +// compileViaYul: also // ---- // f() -> 2 diff --git a/test/libsolidity/semanticTests/libraries/internal_library_function_calling_private.sol b/test/libsolidity/semanticTests/libraries/internal_library_function_calling_private.sol index 2283c30ff..e9806aa4f 100644 --- a/test/libsolidity/semanticTests/libraries/internal_library_function_calling_private.sol +++ b/test/libsolidity/semanticTests/libraries/internal_library_function_calling_private.sol @@ -22,5 +22,7 @@ contract C { } } +// ==== +// compileViaYul: also // ---- // f() -> 2 diff --git a/test/libsolidity/semanticTests/libraries/stub_internal.sol b/test/libsolidity/semanticTests/libraries/stub_internal.sol index 075b8dbb2..4634d4e7a 100644 --- a/test/libsolidity/semanticTests/libraries/stub_internal.sol +++ b/test/libsolidity/semanticTests/libraries/stub_internal.sol @@ -6,6 +6,8 @@ contract C { return L.f(v); } } +// ==== +// compileViaYul: also // ---- // g(uint256): 1 -> 1 // g(uint256): 2 -> 4 From 172b6c245ff6a2be2bcb0a46d48acbd62a439c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 24 Apr 2020 10:30:19 +0200 Subject: [PATCH 33/55] cmdlineTests.sh: Fix the script not removing all temporary files it creates in /tmp - The script was leaving hundreds of loose `tmp.XXXXXX` and `tmp.XXXXXX.bak` files in `/tmp` after each run - There's a trap handler that removes them but it's being registered multiple times in a loop and only the last one actually runs when the script exits. It's still useful because it removes the remaining files from the most recent iteration but on its own it's not enough to clean up everything. --- test/cmdlineTests.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index e99969808..077aa5c19 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -126,7 +126,7 @@ function test_solc_behaviour() # Remove trailing empty lines. Needs a line break to make OSX sed happy. sed -i.bak -e '1{/^$/d }' "$stderr_path" - rm "$stderr_path.bak" + rm "$stderr_path.bak" "$stdout_path.bak" fi # Remove path to cpp file sed -i.bak -e 's/^\(Exception while assembling:\).*/\1/' "$stderr_path" @@ -175,6 +175,8 @@ function test_solc_behaviour() exit 1 fi fi + + rm -f "$stdout_path" "$stderr_path" } From 55eda85a302e477519a3ae3ed5a3baf75d8a4be1 Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Fri, 24 Apr 2020 08:06:15 -0500 Subject: [PATCH 34/55] docs/cheatsheet.rst: Add ``type(I).interfaceId`` description. --- docs/cheatsheet.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/cheatsheet.rst b/docs/cheatsheet.rst index 26d7480b9..5d6169a48 100644 --- a/docs/cheatsheet.rst +++ b/docs/cheatsheet.rst @@ -121,6 +121,7 @@ Global Variables - ``type(C).name`` (``string``): the name of the contract - ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information`. - ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information`. +- ``type(I).interfaceId`` (``bytes4``): value containing the EIP-165 interface identifier of the given interface, see :ref:`Type Information`. .. note:: Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness, From 5a515240acd90372b47d9b5b451caa1dffdef3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Thu, 16 Apr 2020 17:13:56 +0200 Subject: [PATCH 35/55] OptimiserSuite: Use brackets instead of parentheses as syntax for repeating abbreviation sequences - We want to start accepting abbreviation sequences on the command line and parentheses would always have to be escaped in that context. - There wasn't any important reason behind choosing () rather than [] or {} and it still isn't too late to switch. --- libyul/optimiser/Suite.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index d1c6a6dd4..4a175eb02 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -95,7 +95,7 @@ void OptimiserSuite::run( suite.runSequence( "dhfoDgvulfnTUtnIf" // None of these can make stack problems worse - "(" + "[" "xarrscLM" // Turn into SSA and simplify "cCTUtTOntnfDIul" // Perform structural simplification "Lcul" // Simplify again @@ -108,7 +108,7 @@ void OptimiserSuite::run( "xarrcL" // Turn into SSA again and simplify "gvif" // Run full inliner "CTUcarrLsTOtfDncarrIulc" // SSA plus simplify - ")" + "]" "jmuljuljul VcTOcul jmul", // Make source short and pretty ast ); @@ -256,12 +256,12 @@ void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast) for (char abbreviation: input) switch (abbreviation) { - case '(': - assertThrow(!insideLoop, OptimizerException, "Nested parentheses not supported"); + case '[': + assertThrow(!insideLoop, OptimizerException, "Nested brackets not supported"); insideLoop = true; break; - case ')': - assertThrow(insideLoop, OptimizerException, "Unbalanced parenthesis"); + case ']': + assertThrow(insideLoop, OptimizerException, "Unbalanced bracket"); insideLoop = false; break; default: @@ -271,7 +271,7 @@ void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast) "Invalid optimisation step abbreviation" ); } - assertThrow(!insideLoop, OptimizerException, "Unbalanced parenthesis"); + assertThrow(!insideLoop, OptimizerException, "Unbalanced bracket"); auto abbreviationsToSteps = [](string const& _sequence) -> vector { @@ -281,21 +281,21 @@ void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast) return steps; }; - // The sequence has now been validated and must consist of pairs of segments that look like this: `aaa(bbb)` - // `aaa` or `(bbb)` can be empty. For example we consider a sequence like `fgo(aaf)Oo` to have - // four segments, the last of which is an empty parenthesis. + // The sequence has now been validated and must consist of pairs of segments that look like this: `aaa[bbb]` + // `aaa` or `[bbb]` can be empty. For example we consider a sequence like `fgo[aaf]Oo` to have + // four segments, the last of which is an empty bracket. size_t currentPairStart = 0; while (currentPairStart < input.size()) { - size_t openingParenthesis = input.find('(', currentPairStart); - size_t closingParenthesis = input.find(')', openingParenthesis); - size_t firstCharInside = (openingParenthesis == string::npos ? input.size() : openingParenthesis + 1); - yulAssert((openingParenthesis == string::npos) == (closingParenthesis == string::npos), ""); + size_t openingBracket = input.find('[', currentPairStart); + size_t closingBracket = input.find(']', openingBracket); + size_t firstCharInside = (openingBracket == string::npos ? input.size() : openingBracket + 1); + yulAssert((openingBracket == string::npos) == (closingBracket == string::npos), ""); - runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingParenthesis - currentPairStart)), _ast); - runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingParenthesis - firstCharInside)), _ast); + runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingBracket - currentPairStart)), _ast); + runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingBracket - firstCharInside)), _ast); - currentPairStart = (closingParenthesis == string::npos ? input.size() : closingParenthesis + 1); + currentPairStart = (closingBracket == string::npos ? input.size() : closingBracket + 1); } } From e2c0e6331c8c9896c007e536bb5e88ca76e09097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 24 Apr 2020 14:13:34 +0200 Subject: [PATCH 36/55] OptimiserSuite: Define NonStepAbbreviations and use it for extra sanity checks --- libyul/optimiser/Suite.cpp | 11 +++++++++++ libyul/optimiser/Suite.h | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 4a175eb02..1a0628b00 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -67,6 +67,7 @@ #include +#include #include using namespace std; @@ -235,6 +236,12 @@ map const& OptimiserSuite::stepNameToAbbreviationMap() {VarDeclInitializer::name, 'd'}, }; yulAssert(lookupTable.size() == allSteps().size(), ""); + yulAssert(( + util::convertContainer>(string(NonStepAbbreviations)) - + util::convertContainer>(lookupTable | boost::adaptors::map_values) + ).size() == string(NonStepAbbreviations).size(), + "Step abbreviation conflicts with a character reserved for another syntactic element" + ); return lookupTable; } @@ -265,6 +272,10 @@ void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast) insideLoop = false; break; default: + yulAssert( + string(NonStepAbbreviations).find(abbreviation) == string::npos, + "Unhandled syntactic element in the abbreviation sequence" + ); assertThrow( stepAbbreviationToNameMap().find(abbreviation) != stepAbbreviationToNameMap().end(), OptimizerException, diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 3b0ed0163..e8a6c7a56 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -47,6 +47,10 @@ class OptimiserSuite public: static constexpr size_t MaxRounds = 12; + /// Special characters that do not represent optimiser steps but are allowed in abbreviation sequences. + /// Some of them (like whitespace) are ignored, others (like brackets) are a part of the syntax. + static constexpr char NonStepAbbreviations[] = " \n[]"; + enum class Debug { None, From 69b79f848b1faab99def4ebc066ac854857bde93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 24 Apr 2020 14:15:45 +0200 Subject: [PATCH 37/55] OptimiserSuite: Allow validating the optimisation sequence without executing it - Create a separate validateSequence() that can be used independently. - Tweak the exception messages a bit to be usable as command-line errors --- libyul/optimiser/Suite.cpp | 28 ++++++++++++++++++---------- libyul/optimiser/Suite.h | 4 ++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 1a0628b00..338acd0e8 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -253,22 +253,21 @@ map const& OptimiserSuite::stepAbbreviationToNameMap() return lookupTable; } -void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast) +void OptimiserSuite::validateSequence(string const& _stepAbbreviations) { - string input = _stepAbbreviations; - boost::remove_erase(input, ' '); - boost::remove_erase(input, '\n'); - bool insideLoop = false; - for (char abbreviation: input) + for (char abbreviation: _stepAbbreviations) switch (abbreviation) { + case ' ': + case '\n': + break; case '[': - assertThrow(!insideLoop, OptimizerException, "Nested brackets not supported"); + assertThrow(!insideLoop, OptimizerException, "Nested brackets are not supported"); insideLoop = true; break; case ']': - assertThrow(insideLoop, OptimizerException, "Unbalanced bracket"); + assertThrow(insideLoop, OptimizerException, "Unbalanced brackets"); insideLoop = false; break; default: @@ -279,10 +278,19 @@ void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast) assertThrow( stepAbbreviationToNameMap().find(abbreviation) != stepAbbreviationToNameMap().end(), OptimizerException, - "Invalid optimisation step abbreviation" + "'"s + abbreviation + "' is not a valid step abbreviation" ); } - assertThrow(!insideLoop, OptimizerException, "Unbalanced bracket"); + assertThrow(!insideLoop, OptimizerException, "Unbalanced brackets"); +} + +void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast) +{ + validateSequence(_stepAbbreviations); + + string input = _stepAbbreviations; + boost::remove_erase(input, ' '); + boost::remove_erase(input, '\n'); auto abbreviationsToSteps = [](string const& _sequence) -> vector { diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index e8a6c7a56..36df60ca6 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -65,6 +65,10 @@ public: std::set const& _externallyUsedIdentifiers = {} ); + /// Ensures that specified sequence of step abbreviations is well-formed and can be executed. + /// @throw OptimizerException if the sequence is invalid + static void validateSequence(std::string const& _stepAbbreviations); + void runSequence(std::vector const& _steps, Block& _ast); void runSequence(std::string const& _stepAbbreviations, Block& _ast); void runSequenceUntilStable( From c41a832f6504815ce423a1b6f6e9c5f1719058ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 24 Apr 2020 14:19:36 +0200 Subject: [PATCH 38/55] Move the default optimisation steps from OptimiserSuite to OptimiserSettings - Now it's a mandatory parameter in OptimiserSuite::run() --- libsolidity/codegen/CompilerContext.cpp | 1 + libsolidity/interface/OptimiserSettings.h | 25 +++++++++++++++++++++++ libyul/AssemblyStack.cpp | 3 ++- libyul/optimiser/Suite.cpp | 24 ++++++---------------- libyul/optimiser/Suite.h | 1 + test/libyul/YulOptimizerTest.cpp | 4 +++- 6 files changed, 38 insertions(+), 20 deletions(-) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 2b4a888ee..2d81e833b 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -502,6 +502,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _ &meter, _object, _optimiserSettings.optimizeStackAllocation, + _optimiserSettings.yulOptimiserSteps, _externalIdentifiers ); diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index 16aae9493..cd82290af 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -23,12 +23,31 @@ #pragma once #include +#include namespace solidity::frontend { struct OptimiserSettings { + static char constexpr DefaultYulOptimiserSteps[] = + "dhfoDgvulfnTUtnIf" // None of these can make stack problems worse + "[" + "xarrscLM" // Turn into SSA and simplify + "cCTUtTOntnfDIul" // Perform structural simplification + "Lcul" // Simplify again + "Vcul jj" // Reverse SSA + + // should have good "compilability" property here. + + "eul" // Run functional expression inliner + "xarulrul" // Prune a bit more in SSA + "xarrcL" // Turn into SSA again and simplify + "gvif" // Run full inliner + "CTUcarrLsTOtfDncarrIulc" // SSA plus simplify + "]" + "jmuljuljul VcTOcul jmul"; // Make source short and pretty + /// No optimisations at all - not recommended. static OptimiserSettings none() { @@ -74,6 +93,7 @@ struct OptimiserSettings runConstantOptimiser == _other.runConstantOptimiser && optimizeStackAllocation == _other.optimizeStackAllocation && runYulOptimiser == _other.runYulOptimiser && + yulOptimiserSteps == _other.yulOptimiserSteps && expectedExecutionsPerDeployment == _other.expectedExecutionsPerDeployment; } @@ -95,6 +115,11 @@ struct OptimiserSettings bool optimizeStackAllocation = false; /// Yul optimiser with default settings. Will only run on certain parts of the code for now. bool runYulOptimiser = false; + /// Sequence of optimisation steps to be performed by Yul optimiser. + /// Note that there are some hard-coded steps in the optimiser and you cannot disable + /// them just by setting this to an empty string. Set @a runYulOptimiser to false if you want + /// no optimisations. + std::string yulOptimiserSteps = DefaultYulOptimiserSteps; /// This specifies an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. size_t expectedExecutionsPerDeployment = 200; diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index e64420bd9..3988bc3fd 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -183,7 +183,8 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation) dialect, meter.get(), _object, - m_optimiserSettings.optimizeStackAllocation + m_optimiserSettings.optimizeStackAllocation, + m_optimiserSettings.yulOptimiserSteps ); } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 338acd0e8..9f2511e72 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -79,6 +79,7 @@ void OptimiserSuite::run( GasMeter const* _meter, Object& _object, bool _optimizeStackAllocation, + string const& _optimisationSequence, set const& _externallyUsedIdentifiers ) { @@ -94,25 +95,12 @@ void OptimiserSuite::run( OptimiserSuite suite(_dialect, reservedIdentifiers, Debug::None, ast); - suite.runSequence( - "dhfoDgvulfnTUtnIf" // None of these can make stack problems worse - "[" - "xarrscLM" // Turn into SSA and simplify - "cCTUtTOntnfDIul" // Perform structural simplification - "Lcul" // Simplify again - "Vcul jj" // Reverse SSA + // Some steps depend on properties ensured by FunctionHoister, FunctionGrouper and + // ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely. + suite.runSequence("fgo", ast); - // should have good "compilability" property here. - - "eul" // Run functional expression inliner - "xarulrul" // Prune a bit more in SSA - "xarrcL" // Turn into SSA again and simplify - "gvif" // Run full inliner - "CTUcarrLsTOtfDncarrIulc" // SSA plus simplify - "]" - "jmuljuljul VcTOcul jmul", // Make source short and pretty - ast - ); + // Now the user-supplied part + suite.runSequence(_optimisationSequence, ast); // This is a tuning parameter, but actually just prevents infinite loops. size_t stackCompressorMaxIterations = 16; diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 36df60ca6..790b2f9ad 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -62,6 +62,7 @@ public: GasMeter const* _meter, Object& _object, bool _optimizeStackAllocation, + std::string const& _optimisationSequence, std::set const& _externallyUsedIdentifiers = {} ); diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 2a5209b25..042d528db 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -74,6 +74,8 @@ #include +#include + #include #include @@ -342,7 +344,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line yul::Object obj; obj.code = m_ast; obj.analysisInfo = m_analysisInfo; - OptimiserSuite::run(*m_dialect, &meter, obj, true); + OptimiserSuite::run(*m_dialect, &meter, obj, true, solidity::frontend::OptimiserSettings::DefaultYulOptimiserSteps); } else { From 35cc64e33d6773ec30dbccfb4eaeff18238a71fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 24 Apr 2020 14:24:48 +0200 Subject: [PATCH 39/55] Add --yul-optimizations option to the command-line interface --- solc/CommandLineInterface.cpp | 30 ++++++++++++++-- test/cmdlineTests/yul_optimizer_steps/args | 1 + .../yul_optimizer_steps/input.sol | 6 ++++ test/cmdlineTests/yul_optimizer_steps/output | 34 +++++++++++++++++++ .../yul_optimizer_steps_disabled/args | 1 + .../yul_optimizer_steps_disabled/err | 1 + .../yul_optimizer_steps_disabled/exit | 1 + .../yul_optimizer_steps_disabled/input.sol | 6 ++++ .../args | 1 + .../err | 1 + .../exit | 1 + .../input.sol | 6 ++++ .../yul_optimizer_steps_invalid_nesting/args | 1 + .../yul_optimizer_steps_invalid_nesting/err | 1 + .../yul_optimizer_steps_invalid_nesting/exit | 1 + .../input.sol | 6 ++++ .../args | 1 + .../err | 1 + .../exit | 1 + .../input.sol | 6 ++++ 20 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 test/cmdlineTests/yul_optimizer_steps/args create mode 100644 test/cmdlineTests/yul_optimizer_steps/input.sol create mode 100644 test/cmdlineTests/yul_optimizer_steps/output create mode 100644 test/cmdlineTests/yul_optimizer_steps_disabled/args create mode 100644 test/cmdlineTests/yul_optimizer_steps_disabled/err create mode 100644 test/cmdlineTests/yul_optimizer_steps_disabled/exit create mode 100644 test/cmdlineTests/yul_optimizer_steps_disabled/input.sol create mode 100644 test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/args create mode 100644 test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/err create mode 100644 test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/exit create mode 100644 test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/input.sol create mode 100644 test/cmdlineTests/yul_optimizer_steps_invalid_nesting/args create mode 100644 test/cmdlineTests/yul_optimizer_steps_invalid_nesting/err create mode 100644 test/cmdlineTests/yul_optimizer_steps_invalid_nesting/exit create mode 100644 test/cmdlineTests/yul_optimizer_steps_invalid_nesting/input.sol create mode 100644 test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/args create mode 100644 test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/err create mode 100644 test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/exit create mode 100644 test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/input.sol diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 79da75289..da14c58f9 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include @@ -145,6 +146,7 @@ static string const g_strOpcodes = "opcodes"; static string const g_strOptimize = "optimize"; static string const g_strOptimizeRuns = "optimize-runs"; static string const g_strOptimizeYul = "optimize-yul"; +static string const g_strYulOptimizations = "yul-optimizations"; static string const g_strOutputDir = "output-dir"; static string const g_strOverwrite = "overwrite"; static string const g_strRevertStrings = "revert-strings"; @@ -781,7 +783,6 @@ Allowed options)", "Import ASTs to be compiled, assumes input holds the AST in compact JSON format. " "Supported Inputs is the output of the --standard-json or the one produced by --combined-json ast,compact-format" ) - ( g_argAssemble.c_str(), "Switch to assembly mode, ignoring all options except --machine, --yul-dialect and --optimize and assumes input is assembly." @@ -835,7 +836,12 @@ Allowed options)", "Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage." ) (g_strOptimizeYul.c_str(), "Legacy option, ignored. Use the general --optimize to enable Yul optimizer.") - (g_strNoOptimizeYul.c_str(), "Disable Yul optimizer in Solidity."); + (g_strNoOptimizeYul.c_str(), "Disable Yul optimizer in Solidity.") + ( + g_strYulOptimizations.c_str(), + po::value()->value_name("steps"), + "Forces yul optimizer to use the specified sequence of optimization steps instead of the built-in one." + ); desc.add(optimizerOptions); po::options_description outputComponents("Output Components"); outputComponents.add_options() @@ -1168,6 +1174,26 @@ bool CommandLineInterface::processInput() settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as(); if (m_args.count(g_strNoOptimizeYul)) settings.runYulOptimiser = false; + if (m_args.count(g_strYulOptimizations)) + { + if (!settings.runYulOptimiser) + { + serr() << "--" << g_strYulOptimizations << " is invalid if Yul optimizer is disabled" << endl; + return false; + } + + try + { + yul::OptimiserSuite::validateSequence(m_args[g_strYulOptimizations].as()); + } + catch (yul::OptimizerException const& _exception) + { + serr() << "Invalid optimizer step sequence in --" << g_strYulOptimizations << ": " << _exception.what() << endl; + return false; + } + + settings.yulOptimiserSteps = m_args[g_strYulOptimizations].as(); + } settings.optimizeStackAllocation = settings.runYulOptimiser; m_compiler->setOptimiserSettings(settings); diff --git a/test/cmdlineTests/yul_optimizer_steps/args b/test/cmdlineTests/yul_optimizer_steps/args new file mode 100644 index 000000000..1f84565de --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps/args @@ -0,0 +1 @@ +--ir-optimized --optimize --yul-optimizations dhfoDgvulfnTUtnIf diff --git a/test/cmdlineTests/yul_optimizer_steps/input.sol b/test/cmdlineTests/yul_optimizer_steps/input.sol new file mode 100644 index 000000000..f787d2fcf --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps/input.sol @@ -0,0 +1,6 @@ +pragma solidity >=0.0; + +contract C +{ + constructor() public {} +} diff --git a/test/cmdlineTests/yul_optimizer_steps/output b/test/cmdlineTests/yul_optimizer_steps/output new file mode 100644 index 000000000..847c731e9 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps/output @@ -0,0 +1,34 @@ +Optimized IR: +/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + +object "C_6" { + code { + { + mstore(64, 128) + if callvalue() { revert(0, 0) } + codecopy(0, dataoffset("C_6_deployed"), datasize("C_6_deployed")) + return(0, datasize("C_6_deployed")) + } + } + object "C_6_deployed" { + code { + { + mstore(64, 128) + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + pop(selector) + } + pop(iszero(calldatasize())) + revert(0, 0) + } + function shift_right_224_unsigned(value) -> newValue + { newValue := shr(224, value) } + } + } +} diff --git a/test/cmdlineTests/yul_optimizer_steps_disabled/args b/test/cmdlineTests/yul_optimizer_steps_disabled/args new file mode 100644 index 000000000..449d8191c --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_disabled/args @@ -0,0 +1 @@ +--ir-optimized --yul-optimizations dhfoDgvulfnTUtnIf diff --git a/test/cmdlineTests/yul_optimizer_steps_disabled/err b/test/cmdlineTests/yul_optimizer_steps_disabled/err new file mode 100644 index 000000000..ab77d4e1c --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_disabled/err @@ -0,0 +1 @@ +--yul-optimizations is invalid if Yul optimizer is disabled diff --git a/test/cmdlineTests/yul_optimizer_steps_disabled/exit b/test/cmdlineTests/yul_optimizer_steps_disabled/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_disabled/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/yul_optimizer_steps_disabled/input.sol b/test/cmdlineTests/yul_optimizer_steps_disabled/input.sol new file mode 100644 index 000000000..363a4c721 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_disabled/input.sol @@ -0,0 +1,6 @@ +pragma solidity >=0.0; + +contract C +{ + function f() public pure {} +} diff --git a/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/args b/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/args new file mode 100644 index 000000000..e10815d81 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/args @@ -0,0 +1 @@ +--ir-optimized --optimize --yul-optimizations abcdefg{hijklmno}pqr[st]uvwxyz diff --git a/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/err b/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/err new file mode 100644 index 000000000..b66c6c60f --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/err @@ -0,0 +1 @@ +Invalid optimizer step sequence in --yul-optimizations: 'b' is not a valid step abbreviation diff --git a/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/exit b/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/input.sol b/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/input.sol new file mode 100644 index 000000000..363a4c721 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_invalid_abbreviation/input.sol @@ -0,0 +1,6 @@ +pragma solidity >=0.0; + +contract C +{ + function f() public pure {} +} diff --git a/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/args b/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/args new file mode 100644 index 000000000..724fe4724 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/args @@ -0,0 +1 @@ +--ir-optimized --optimize --yul-optimizations a[a][aa[aa]]a diff --git a/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/err b/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/err new file mode 100644 index 000000000..2f6d9ff47 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/err @@ -0,0 +1 @@ +Invalid optimizer step sequence in --yul-optimizations: Nested brackets are not supported diff --git a/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/exit b/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/input.sol b/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/input.sol new file mode 100644 index 000000000..363a4c721 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_invalid_nesting/input.sol @@ -0,0 +1,6 @@ +pragma solidity >=0.0; + +contract C +{ + function f() public pure {} +} diff --git a/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/args b/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/args new file mode 100644 index 000000000..d0f68f8c4 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/args @@ -0,0 +1 @@ +--ir-optimized --optimize --yul-optimizations a[a][ diff --git a/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/err b/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/err new file mode 100644 index 000000000..1da42b743 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/err @@ -0,0 +1 @@ +Invalid optimizer step sequence in --yul-optimizations: Unbalanced brackets diff --git a/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/exit b/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/input.sol b/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/input.sol new file mode 100644 index 000000000..363a4c721 --- /dev/null +++ b/test/cmdlineTests/yul_optimizer_steps_unbalanced_bracket/input.sol @@ -0,0 +1,6 @@ +pragma solidity >=0.0; + +contract C +{ + function f() public pure {} +} From c8b612536f084e4726849ccd566ba40b4ba0c494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 24 Apr 2020 14:26:31 +0200 Subject: [PATCH 40/55] Add yulDetails.optimizerSteps to the standard JSON interface --- libsolidity/interface/CompilerStack.cpp | 1 + libsolidity/interface/StandardCompiler.cpp | 32 ++++++++++++++++++- .../input.json | 21 ++++++++++++ .../output.json | 1 + .../input.json | 21 ++++++++++++ .../output.json | 1 + .../input.json | 21 ++++++++++++ .../output.json | 1 + .../input.json | 21 ++++++++++++ .../output.json | 1 + .../input.json | 21 ++++++++++++ .../output.json | 1 + test/libsolidity/StandardCompiler.cpp | 10 +++++- 13 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps/input.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps/output.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_abbreviation/input.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_abbreviation/output.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nesting/input.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nesting/output.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_type/input.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_type/output.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_unbalanced_bracket/input.json create mode 100644 test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_unbalanced_bracket/output.json diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 17348c242..16422320c 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1265,6 +1265,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const { details["yulDetails"] = Json::objectValue; details["yulDetails"]["stackAllocation"] = m_optimiserSettings.optimizeStackAllocation; + details["yulDetails"]["optimizerSteps"] = m_optimiserSettings.yulOptimiserSteps; } meta["settings"]["optimizer"]["details"] = std::move(details); diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 1b35040aa..15989dcf3 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -402,6 +403,33 @@ std::optional checkOptimizerDetail(Json::Value const& _details, std return {}; } +std::optional checkOptimizerDetailSteps(Json::Value const& _details, std::string const& _name, string& _setting) +{ + if (_details.isMember(_name)) + { + if (_details[_name].isString()) + { + try + { + yul::OptimiserSuite::validateSequence(_details[_name].asString()); + } + catch (yul::OptimizerException const& _exception) + { + return formatFatalError( + "JSONError", + "Invalid optimizer step sequence in \"settings.optimizer.details." + _name + "\": " + _exception.what() + ); + } + + _setting = _details[_name].asString(); + } + else + return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be a string"); + + } + return {}; +} + std::optional checkMetadataKeys(Json::Value const& _input) { if (_input.isObject()) @@ -511,10 +539,12 @@ boost::variant parseOptimizerSettings(Json::Valu if (!settings.runYulOptimiser) return formatFatalError("JSONError", "\"Providing yulDetails requires Yul optimizer to be enabled."); - if (auto result = checkKeys(details["yulDetails"], {"stackAllocation"}, "settings.optimizer.details.yulDetails")) + if (auto result = checkKeys(details["yulDetails"], {"stackAllocation", "optimizerSteps"}, "settings.optimizer.details.yulDetails")) return *result; if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation)) return *error; + if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps)) + return *error; } } return { std::move(settings) }; diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps/input.json new file mode 100644 index 000000000..00919a864 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps/input.json @@ -0,0 +1,21 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": "dhfoDgvulfnTUtnIf\n[ xarrscLM\n]\njmuljuljul VcTOcul jmul" + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps/output.json new file mode 100644 index 000000000..59b90c8cc --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps/output.json @@ -0,0 +1 @@ +{"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_abbreviation/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_abbreviation/input.json new file mode 100644 index 000000000..25b3c4bb4 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_abbreviation/input.json @@ -0,0 +1,21 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": "abcdefg{hijklmno}pqr[st]uvwxyz" + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_abbreviation/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_abbreviation/output.json new file mode 100644 index 000000000..84711c002 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_abbreviation/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": 'b' is not a valid step abbreviation","message":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": 'b' is not a valid step abbreviation","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nesting/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nesting/input.json new file mode 100644 index 000000000..c322913d9 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nesting/input.json @@ -0,0 +1,21 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": "a[a][aa[aa]]a" + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nesting/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nesting/output.json new file mode 100644 index 000000000..409fd755f --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_invalid_nesting/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Nested brackets are not supported","message":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Nested brackets are not supported","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_type/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_type/input.json new file mode 100644 index 000000000..c02aa6bb3 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_type/input.json @@ -0,0 +1,21 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": 42 + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_type/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_type/output.json new file mode 100644 index 000000000..d18de5299 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_type/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"\"settings.optimizer.details.optimizerSteps\" must be a string","message":"\"settings.optimizer.details.optimizerSteps\" must be a string","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_unbalanced_bracket/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_unbalanced_bracket/input.json new file mode 100644 index 000000000..d6e1e0dc7 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_unbalanced_bracket/input.json @@ -0,0 +1,21 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { + "yul": true, + "yulDetails": { + "optimizerSteps": "a[a][" + } + } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_unbalanced_bracket/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_unbalanced_bracket/output.json new file mode 100644 index 000000000..d1843709b --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_optimiserSteps_unbalanced_bracket/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Unbalanced brackets","message":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Unbalanced brackets","severity":"error","type":"JSONError"}]} diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index df2aecbf5..ce9a29268 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -21,11 +21,15 @@ #include #include +#include #include #include #include +#include #include +#include + using namespace std; using namespace solidity::evmasm; @@ -1058,8 +1062,12 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) BOOST_CHECK(optimizer["details"]["peephole"].asBool() == true); BOOST_CHECK(optimizer["details"]["yul"].asBool() == true); BOOST_CHECK(optimizer["details"]["yulDetails"].isObject()); - BOOST_CHECK(optimizer["details"]["yulDetails"].getMemberNames() == vector{"stackAllocation"}); + BOOST_CHECK( + util::convertContainer>(optimizer["details"]["yulDetails"].getMemberNames()) == + (set{"stackAllocation", "optimizerSteps"}) + ); BOOST_CHECK(optimizer["details"]["yulDetails"]["stackAllocation"].asBool() == true); + BOOST_CHECK(optimizer["details"]["yulDetails"]["optimizerSteps"].asString() == OptimiserSettings::DefaultYulOptimiserSteps); BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 8); BOOST_CHECK(optimizer["runs"].asUInt() == 600); } From e19d8d1fa376e0478f26c0daeb93e2cbfbddc55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 20 Mar 2020 18:54:10 +0100 Subject: [PATCH 41/55] [yul-phaser] GeneticAlgorithm::runNextRound(): Fix outdated docstring --- tools/yulPhaser/GeneticAlgorithms.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/yulPhaser/GeneticAlgorithms.h b/tools/yulPhaser/GeneticAlgorithms.h index 96c027738..f6c809a8b 100644 --- a/tools/yulPhaser/GeneticAlgorithms.h +++ b/tools/yulPhaser/GeneticAlgorithms.h @@ -58,8 +58,8 @@ public: GeneticAlgorithm& operator=(GeneticAlgorithm const&) = delete; virtual ~GeneticAlgorithm() = default; - /// The method that actually implements the algorithm. Should use @a m_population as input and - /// replace it with the updated state after the round. + /// The method that actually implements the algorithm. Should accept the current population in + /// @a _population and return the updated one after the round. virtual Population runNextRound(Population _population) = 0; }; From 424edecd21ed56582e2b9721cd73a10717f99d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 20 Mar 2020 18:14:28 +0100 Subject: [PATCH 42/55] [yul-phaser] Phaser: List all available values of enum options in --help --- tools/yulPhaser/Phaser.cpp | 49 ++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index ebfdbb897..47f988ce9 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -437,8 +437,15 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ( "mode", po::value()->value_name("")->default_value(PhaserMode::RunAlgorithm), - "Mode of operation. The default is to run the algorithm but you can also tell phaser " - "to do something else with its parameters, e.g. just print the optimised programs and exit." + ( + "Mode of operation. The default is to run the algorithm but you can also tell phaser " + "to do something else with its parameters, e.g. just print the optimised programs and exit.\n" + "\n" + "AVAILABLE MODES:\n" + "* " + toString(PhaserMode::RunAlgorithm) + "\n" + + "* " + toString(PhaserMode::PrintOptimisedPrograms) + "\n" + + "* " + toString(PhaserMode::PrintOptimisedASTs) + ).c_str() ) ; keywordDescription.add(generalDescription); @@ -448,7 +455,14 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ( "algorithm", po::value()->value_name("")->default_value(Algorithm::GEWEP), - "Algorithm" + ( + "Algorithm\n" + "\n" + "AVAILABLE ALGORITHMS:\n" + "* " + toString(Algorithm::GEWEP) + "\n" + + "* " + toString(Algorithm::Classic) + "\n" + + "* " + toString(Algorithm::Random) + ).c_str() ) ( "no-randomise-duplicates", @@ -470,7 +484,14 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ( "crossover", po::value()->value_name("")->default_value(CrossoverChoice::SinglePoint), - "Type of the crossover operator to use." + ( + "Type of the crossover operator to use.\n" + "\n" + "AVAILABLE CROSSOVER OPERATORS:\n" + "* " + toString(CrossoverChoice::SinglePoint) + "\n" + + "* " + toString(CrossoverChoice::TwoPoint) + "\n" + + "* " + toString(CrossoverChoice::Uniform) + ).c_str() ) ( "uniform-crossover-swap-chance", @@ -590,13 +611,27 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ( "metric", po::value()->value_name("")->default_value(MetricChoice::RelativeCodeSize), - "Metric used to evaluate the fitness of a chromosome." + ( + "Metric used to evaluate the fitness of a chromosome.\n" + "\n" + "AVAILABLE METRICS:\n" + "* " + toString(MetricChoice::CodeSize) + "\n" + + "* " + toString(MetricChoice::RelativeCodeSize) + ).c_str() ) ( "metric-aggregator", po::value()->value_name("")->default_value(MetricAggregatorChoice::Average), - "Operator used to combine multiple fitness metric obtained by evaluating a chromosome " - "separately for each input program." + ( + "Operator used to combine multiple fitness metric obtained by evaluating a chromosome " + "separately for each input program.\n" + "\n" + "AVAILABLE METRIC AGGREGATORS:\n" + "* " + toString(MetricAggregatorChoice::Average) + "\n" + + "* " + toString(MetricAggregatorChoice::Sum) + "\n" + + "* " + toString(MetricAggregatorChoice::Maximum) + "\n" + + "* " + toString(MetricAggregatorChoice::Minimum) + ).c_str() ) ( "relative-metric-scale", From 35395a4b9c7dd9db514aa3a4d13173b82f426480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 20 Mar 2020 18:15:44 +0100 Subject: [PATCH 43/55] [yul-phaser] Phaser: Missing word in --metric-aggregator option description --- tools/yulPhaser/Phaser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 47f988ce9..8e34a756b 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -623,8 +623,8 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() "metric-aggregator", po::value()->value_name("")->default_value(MetricAggregatorChoice::Average), ( - "Operator used to combine multiple fitness metric obtained by evaluating a chromosome " - "separately for each input program.\n" + "Operator used to combine multiple fitness metric values obtained by evaluating a " + "chromosome separately for each input program.\n" "\n" "AVAILABLE METRIC AGGREGATORS:\n" "* " + toString(MetricAggregatorChoice::Average) + "\n" + From 163e35dd23b228d6c620f4717ff73fa972851d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 20 Mar 2020 18:44:45 +0100 Subject: [PATCH 44/55] [yul-phaser] Tweak default values according to experiment results - Long chromosomes in the intial population are better. Set minimum and maximum to 100. - The classic algorithm does not work well without elite. 50% performed better but I think it might be too large. Let's set it to 25%. - Switch to uniform crossover since this is what was used in most experiments and performed well. --- tools/yulPhaser/Phaser.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/yulPhaser/Phaser.cpp b/tools/yulPhaser/Phaser.cpp index 8e34a756b..d5b3bdda7 100644 --- a/tools/yulPhaser/Phaser.cpp +++ b/tools/yulPhaser/Phaser.cpp @@ -473,17 +473,17 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() ) ( "min-chromosome-length", - po::value()->value_name("")->default_value(12), + po::value()->value_name("")->default_value(100), "Minimum length of randomly generated chromosomes." ) ( "max-chromosome-length", - po::value()->value_name("")->default_value(30), + po::value()->value_name("")->default_value(100), "Maximum length of randomly generated chromosomes." ) ( "crossover", - po::value()->value_name("")->default_value(CrossoverChoice::SinglePoint), + po::value()->value_name("")->default_value(CrossoverChoice::Uniform), ( "Type of the crossover operator to use.\n" "\n" @@ -542,7 +542,7 @@ Phaser::CommandLineDescription Phaser::buildCommandLineDescription() classicGeneticAlgorithmDescription.add_options() ( "classic-elite-pool-size", - po::value()->value_name("")->default_value(0), + po::value()->value_name("")->default_value(0.25), "Percentage of population to regenerate using mutations in each round." ) ( From ee915008bdb29249f9a1301c367d7b69804df84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Fri, 20 Mar 2020 07:47:13 +0100 Subject: [PATCH 45/55] [yul-phaser] README --- tools/yulPhaser/README.md | 91 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tools/yulPhaser/README.md diff --git a/tools/yulPhaser/README.md b/tools/yulPhaser/README.md new file mode 100644 index 000000000..abc2a0e47 --- /dev/null +++ b/tools/yulPhaser/README.md @@ -0,0 +1,91 @@ +## yul-phaser +`yul-phaser` is an internal tool for finding good sequences of [optimisation steps](/libyul/optimiser/README.md) for Yul optimiser. + +### How it works +The space of possible solutions to this problem (usually referred to as _phase-ordering problem_) is extremely large and there may even be no single sequence that produces optimal results for all possible programs. + +The tool uses genetic algorithms to find sequences that result in better programs than others and to iteratively refine them. +The input is a set of one or more [Yul](/docs/yul.rst) programs and each sequence is applied to all of these programs. +Optimised programs are given numeric scores according to the selected metric. + +Optimisation step sequences are presented in an abbreviated form - as strings of letters where each character represents one step. +The abbreviations are defined in [`OptimiserSuite::stepNameToAbbreviationMap()`](/libyul/optimiser/Suite.cpp#L388-L423). + +### How to use it +The application has sensible defaults for most parameters. +An invocation can be as simple as: + +``` bash +tools/yul-phaser ../test/libyul/yulOptimizerTests/fullSuite/*.yul \ + --random-population 100 +``` + +This assumes that you have a working copy of the Solidity repository and you're in the build directory within that working copy. + +Run `yul-phaser --help` for a full list of available options. + +#### Restarting from a previous state +`yul-phaser` can save the list of sequences found after each round: + +``` bash +tools/yul-phaser *.yul \ + --random-population 100 \ + --population-autosave /tmp/population.txt +``` + +If you stop the application, you can later use the file to continue the search from the point you left off: + +``` bash +tools/yul-phaser *.yul \ + --population-from-file /tmp/population.txt \ + --population-autosave /tmp/population.txt +``` + +#### Analysing a sequence +Apart from running the genetic algorithm, `yul-phaser` can also provide useful information about a particular sequence. + +For example, to see the value of a particular metric for a given sequence and program run: +``` bash +tools/yul-phaser *.yul \ + --show-initial-population \ + --rounds 0 \ + --metric code-size \ + --metric-aggregator sum \ + --population +``` + +You can also easily see program code after being optimised using that sequence: +``` bash +tools/yul-phaser *.yul \ + --rounds 0 \ + --mode print-optimised-programs \ + --population +``` + +#### Using output from Solidity compiler +`yul-phaser` can process the intermediate representation produced by `solc`: + +``` bash +solc/solc \ + --ir \ + --no-optimize-yul \ + --output-dir +``` + +After running this command you'll find one or more .yul files in the output directory. +These files contain whole Yul objects rather than just raw Yul programs but `yul-phaser` is prepared to handle them. + +### How to choose good parameters +Choosing good parameters for a genetic algorithm is not a trivial task but phaser's defaults are generally enough to find a sequence that gives results comparable or better than one hand-crafted by an experienced developer for a given set of programs. +The difficult part is providing a fairly representative set of input files. +If the files you give don't need certain optimisations the tool will find sequences that don't use these optimisations and perform badly for programs that could benefit from them. +If all the provided files greatly benefit from a specific optimisation, the sequence may not work well for programs that do not. + +We have conducted [a set of rough experiments](https://github.com/ethereum/solidity/issues/7806#issuecomment-598644491) to evaluate some combinations of parameter values. +The conclusions were used to adjust the defaults but you might still benefit from some general observations: + +1. The algorithm that performed the best was `GEWEP`. +2. Using longer sequences in the initial population yields better results. The algorithm is good at removing superfluous steps. +3. Preserving the top sequences from previous rounds improves results. Elite should contain at least a few individuals, especially when using the `classic` algorithm. +4. Don't set mutation/deletion/addition chance too high. It makes results worse because it destroys the good patterns preserved by crossover. Values around 1-5% seem to work best. +5. Keep the algorithm running for 1000 rounds or more. It usually finds good sequences faster than that but it can shorten them significantly if you let it run longer. This is especially important when starting with long sequences. From 2fa26f4e92b1f297697d814c1c079d02e59f362e Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Fri, 24 Apr 2020 17:03:41 -0500 Subject: [PATCH 46/55] [Sol - Yul] Add support for built-in selfdestruct(..). --- libsolidity/codegen/ir/IRGeneratorForStatements.cpp | 6 ++++++ test/libsolidity/SolidityEndToEndTest.cpp | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 08a5fa900..1dd6dcdd1 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -812,6 +812,12 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) define(_functionCall) << "gas()\n"; break; } + case FunctionType::Kind::Selfdestruct: + { + solAssert(arguments.size() == 1, ""); + define(_functionCall) << "selfdestruct(" << expressionAsType(*arguments.front(), *parameterTypes.front()) << ")\n"; + break; + } default: solUnimplemented("FunctionKind " + toString(static_cast(functionType->kind())) + " not yet implemented"); } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 69e645494..a0e59170b 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1238,11 +1238,13 @@ BOOST_AUTO_TEST_CASE(selfdestruct) } )"; u256 amount(130); - compileAndRun(sourceCode, amount); u160 address(23); - ABI_CHECK(callContractFunction("a(address)", address), bytes()); - BOOST_CHECK(!addressHasCode(m_contractAddress)); - BOOST_CHECK_EQUAL(balanceAt(address), amount); + ALSO_VIA_YUL( + compileAndRun(sourceCode, amount); + ABI_CHECK(callContractFunction("a(address)", address), bytes()); + BOOST_CHECK(!addressHasCode(m_contractAddress)); + BOOST_CHECK_EQUAL(balanceAt(address), amount); + ) } BOOST_AUTO_TEST_CASE(keccak256) From 66edaf43f4330a4a88de867bec2209f6e63f57a7 Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Fri, 24 Apr 2020 16:33:43 -0500 Subject: [PATCH 47/55] [Sol - Yul] Add support for built-in logN(). --- .../codegen/ir/IRGeneratorForStatements.cpp | 27 ++++ test/libsolidity/SolidityEndToEndTest.cpp | 146 ++++++++++-------- 2 files changed, 108 insertions(+), 65 deletions(-) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 1dd6dcdd1..d8cb8f49c 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -818,6 +818,33 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) define(_functionCall) << "selfdestruct(" << expressionAsType(*arguments.front(), *parameterTypes.front()) << ")\n"; break; } + case FunctionType::Kind::Log0: + case FunctionType::Kind::Log1: + case FunctionType::Kind::Log2: + case FunctionType::Kind::Log3: + case FunctionType::Kind::Log4: + { + unsigned logNumber = int(functionType->kind()) - int(FunctionType::Kind::Log0); + solAssert(arguments.size() == logNumber + 1, ""); + ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector()); + string indexedArgs; + for (unsigned arg = 0; arg < logNumber; ++arg) + indexedArgs += ", " + expressionAsType(*arguments[arg + 1], *(parameterTypes[arg + 1])); + Whiskers templ(R"({ + let := + let := (, ) + (, sub(, ) ) + })"); + templ("pos", m_context.newYulVariable()); + templ("end", m_context.newYulVariable()); + templ("freeMemory", freeMemory()); + templ("encode", abi.tupleEncoder({arguments.front()->annotation().type},{parameterTypes.front()})); + templ("nonIndexedArgs", IRVariable(*arguments.front()).commaSeparatedList()); + templ("log", "log" + to_string(logNumber)); + templ("indexedArgs", indexedArgs); + m_code << templ.render(); + break; + } default: solUnimplemented("FunctionKind " + toString(static_cast(functionType->kind())) + " not yet implemented"); } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index a0e59170b..153d6a8f4 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1126,12 +1126,14 @@ BOOST_AUTO_TEST_CASE(log0) } } )"; - compileAndRun(sourceCode); - callContractFunction("a()"); - BOOST_REQUIRE_EQUAL(numLogs(), 1); - BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); - BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); - BOOST_CHECK_EQUAL(numLogTopics(0), 0); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + callContractFunction("a()"); + BOOST_REQUIRE_EQUAL(numLogs(), 1); + BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); + BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); + BOOST_CHECK_EQUAL(numLogTopics(0), 0); + ) } BOOST_AUTO_TEST_CASE(log1) @@ -1143,13 +1145,15 @@ BOOST_AUTO_TEST_CASE(log1) } } )"; - compileAndRun(sourceCode); - callContractFunction("a()"); - BOOST_REQUIRE_EQUAL(numLogs(), 1); - BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); - BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); - BOOST_REQUIRE_EQUAL(numLogTopics(0), 1); - BOOST_CHECK_EQUAL(logTopic(0, 0), h256(u256(2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + callContractFunction("a()"); + BOOST_REQUIRE_EQUAL(numLogs(), 1); + BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); + BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); + BOOST_REQUIRE_EQUAL(numLogTopics(0), 1); + BOOST_CHECK_EQUAL(logTopic(0, 0), h256(u256(2))); + ) } BOOST_AUTO_TEST_CASE(log2) @@ -1161,14 +1165,16 @@ BOOST_AUTO_TEST_CASE(log2) } } )"; - compileAndRun(sourceCode); - callContractFunction("a()"); - BOOST_REQUIRE_EQUAL(numLogs(), 1); - BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); - BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); - BOOST_REQUIRE_EQUAL(numLogTopics(0), 2); - for (unsigned i = 0; i < 2; ++i) - BOOST_CHECK_EQUAL(logTopic(0, i), h256(u256(i + 2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + callContractFunction("a()"); + BOOST_REQUIRE_EQUAL(numLogs(), 1); + BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); + BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); + BOOST_REQUIRE_EQUAL(numLogTopics(0), 2); + for (unsigned i = 0; i < 2; ++i) + BOOST_CHECK_EQUAL(logTopic(0, i), h256(u256(i + 2))); + ) } BOOST_AUTO_TEST_CASE(log3) @@ -1180,14 +1186,16 @@ BOOST_AUTO_TEST_CASE(log3) } } )"; - compileAndRun(sourceCode); - callContractFunction("a()"); - BOOST_REQUIRE_EQUAL(numLogs(), 1); - BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); - BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); - BOOST_REQUIRE_EQUAL(numLogTopics(0), 3); - for (unsigned i = 0; i < 3; ++i) - BOOST_CHECK_EQUAL(logTopic(0, i), h256(u256(i + 2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + callContractFunction("a()"); + BOOST_REQUIRE_EQUAL(numLogs(), 1); + BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); + BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); + BOOST_REQUIRE_EQUAL(numLogTopics(0), 3); + for (unsigned i = 0; i < 3; ++i) + BOOST_CHECK_EQUAL(logTopic(0, i), h256(u256(i + 2))); + ) } BOOST_AUTO_TEST_CASE(log4) @@ -1199,14 +1207,16 @@ BOOST_AUTO_TEST_CASE(log4) } } )"; - compileAndRun(sourceCode); - callContractFunction("a()"); - BOOST_REQUIRE_EQUAL(numLogs(), 1); - BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); - BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); - BOOST_REQUIRE_EQUAL(numLogTopics(0), 4); - for (unsigned i = 0; i < 4; ++i) - BOOST_CHECK_EQUAL(logTopic(0, i), h256(u256(i + 2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + callContractFunction("a()"); + BOOST_REQUIRE_EQUAL(numLogs(), 1); + BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); + BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); + BOOST_REQUIRE_EQUAL(numLogTopics(0), 4); + for (unsigned i = 0; i < 4; ++i) + BOOST_CHECK_EQUAL(logTopic(0, i), h256(u256(i + 2))); + ) } BOOST_AUTO_TEST_CASE(log_in_constructor) @@ -1218,12 +1228,14 @@ BOOST_AUTO_TEST_CASE(log_in_constructor) } } )"; - compileAndRun(sourceCode); - BOOST_REQUIRE_EQUAL(numLogs(), 1); - BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); - BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); - BOOST_REQUIRE_EQUAL(numLogTopics(0), 1); - BOOST_CHECK_EQUAL(logTopic(0, 0), h256(u256(2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_REQUIRE_EQUAL(numLogs(), 1); + BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); + BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(1))); + BOOST_REQUIRE_EQUAL(numLogTopics(0), 1); + BOOST_CHECK_EQUAL(logTopic(0, 0), h256(u256(2))); + ) } BOOST_AUTO_TEST_CASE(selfdestruct) @@ -1776,20 +1788,22 @@ BOOST_AUTO_TEST_CASE(event) } } )"; - compileAndRun(sourceCode); - u256 value(18); - u256 id(0x1234); - for (bool manually: {true, false}) - { - callContractFunctionWithValue("deposit(bytes32,bool)", value, id, manually); - BOOST_REQUIRE_EQUAL(numLogs(), 1); - BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); - BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(value))); - BOOST_REQUIRE_EQUAL(numLogTopics(0), 3); - BOOST_CHECK_EQUAL(logTopic(0, 0), util::keccak256(string("Deposit(address,bytes32,uint256)"))); - BOOST_CHECK_EQUAL(logTopic(0, 1), h256(m_sender, h256::AlignRight)); - BOOST_CHECK_EQUAL(logTopic(0, 2), h256(id)); - } + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 value(18); + u256 id(0x1234); + for (bool manually: {true, false}) + { + callContractFunctionWithValue("deposit(bytes32,bool)", value, id, manually); + BOOST_REQUIRE_EQUAL(numLogs(), 1); + BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); + BOOST_CHECK_EQUAL(h256(logData(0)), h256(u256(value))); + BOOST_REQUIRE_EQUAL(numLogTopics(0), 3); + BOOST_CHECK_EQUAL(logTopic(0, 0), util::keccak256(string("Deposit(address,bytes32,uint256)"))); + BOOST_CHECK_EQUAL(logTopic(0, 1), h256(m_sender, h256::AlignRight)); + BOOST_CHECK_EQUAL(logTopic(0, 2), h256(id)); + } + ) } BOOST_AUTO_TEST_CASE(event_emit) @@ -1803,7 +1817,7 @@ BOOST_AUTO_TEST_CASE(event_emit) } )"; ALSO_VIA_YUL( - compileAndRun(sourceCode); + compileAndRun(sourceCode); u256 value(18); u256 id(0x1234); callContractFunctionWithValue("deposit(bytes32)", value, id); @@ -1852,13 +1866,15 @@ BOOST_AUTO_TEST_CASE(event_access_through_base_name_emit) } } )"; - compileAndRun(sourceCode); - callContractFunction("f()"); - BOOST_REQUIRE_EQUAL(numLogs(), 1); - BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); - BOOST_CHECK(logData(0).empty()); - BOOST_REQUIRE_EQUAL(numLogTopics(0), 1); - BOOST_CHECK_EQUAL(logTopic(0, 0), util::keccak256(string("x()"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + callContractFunction("f()"); + BOOST_REQUIRE_EQUAL(numLogs(), 1); + BOOST_CHECK_EQUAL(logAddress(0), m_contractAddress); + BOOST_CHECK(logData(0).empty()); + BOOST_REQUIRE_EQUAL(numLogTopics(0), 1); + BOOST_CHECK_EQUAL(logTopic(0, 0), util::keccak256(string("x()"))); + ); } BOOST_AUTO_TEST_CASE(events_with_same_name) From a481ea719f901071a04a9282e102a39d6c7c4b5d Mon Sep 17 00:00:00 2001 From: Noel Maersk Date: Sun, 26 Apr 2020 11:36:05 +0300 Subject: [PATCH 48/55] docs: use Yul lexer to highlight Yul code segments. Many commits squashed; turns out that with the combination of: * Python v2.7, * Sphinx v1.8.5, and * Pygments v2.3.1 versions (old!) used in the CI, the only viable approach is: * to use `code-block` directives with explicit language specification, * to provide no file-local default using `highlight`, and * to set language as `none` for grammar specifications. Underlying are the following issues (again, for the old versions listed above): * Generic RST `code` doesn't work when language is `none`: Warning, treated as error: /root/project/docs/yul.rst:430:Cannot analyze code. No Pygments lexer found for "none". Additionally, it might be trying to fall back to the default (Solidity) if left unspecified. * If a file-local default is specified using `highlight`, then `code-block` _must_ also provide a language: Warning, treated as error: /root/project/docs/yul.rst:185:Error in "code-block" directive: 1 argument(s) required, 0 supplied. * Sphinx seems to try the file-local default "yul" (specified with `highlight`) on `code` marked having language `json`: Warning, treated as error: /root/project/docs/yul.rst:130:Could not lex literal_block as "yul". Highlighting skipped. * The only well-lexed highlighter for two of the three grammar specifications is `peg`, but it was added in Pygments v2.6. One of the grammars - in the "Formal Specification" section, the one after "We will use a destructuring notation for the AST nodes." - _must_ be left unhighlighted, with language set to `none`: all lexers do really poorly. ... And one should never, ever start treating warnings as mere warnings, without having exhausted all other options. Otherwise, it's a slippery slope, - and look where that brought Gandhi: to being a strawman in every lousy argument to be had!.. --- docs/conf.py | 3 ++- docs/requirements.txt | 2 +- docs/yul.rst | 38 +++++++++++++++++++------------------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9a9d574fc..1ff794722 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,7 +17,7 @@ import sys import os import re -from pygments_lexer_solidity import SolidityLexer +from pygments_lexer_solidity import SolidityLexer, YulLexer # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -27,6 +27,7 @@ def setup(sphinx): thisdir = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, thisdir + '/utils') sphinx.add_lexer('Solidity', SolidityLexer()) + sphinx.add_lexer('Yul', YulLexer()) sphinx.add_stylesheet('css/custom.css') diff --git a/docs/requirements.txt b/docs/requirements.txt index 5fba631c7..8f67f9594 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx_rtd_theme>=0.3.1 -pygments-lexer-solidity>=0.3.1 +pygments-lexer-solidity>=0.5.1 diff --git a/docs/yul.rst b/docs/yul.rst index c6fb28986..2fcae1175 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -70,7 +70,7 @@ The following example program is written in the EVM dialect and computes exponen It can be compiled using ``solc --strict-assembly``. The builtin functions ``mul`` and ``div`` compute product and division, respectively. -.. code:: +.. code-block:: yul { function power(base, exponent) -> result @@ -91,7 +91,7 @@ It is also possible to implement the same function using a for-loop instead of with recursion. Here, ``lt(a, b)`` computes whether ``a`` is less than ``b``. less-than comparison. -.. code:: +.. code-block:: yul { function power(base, exponent) -> result @@ -115,7 +115,7 @@ This will use the :ref:`Yul object notation ` so that it is possible to code as data to deploy contracts. This Yul mode is available for the commandline compiler (use ``--strict-assembly``) and for the :ref:`standard-json interface `: -:: +.. code-block:: json { "language": "Yul", @@ -180,14 +180,14 @@ bitwise ``and`` with the string "abc" is computed. The final value is assigned to a local variable called ``x``. Strings are stored left-aligned and cannot be longer than 32 bytes. -.. code:: +.. code-block:: yul let x := and("abc", add(3, 2)) Unless it is the default type, the type of a literal has to be specified after a colon: -.. code:: +.. code-block:: yul let x := and("abc":uint32, add(3:uint256, 2:uint256)) @@ -201,7 +201,7 @@ If the function returns a single value, it can be directly used inside an expression again. If it returns multiple values, they have to be assigned to local variables. -.. code:: +.. code-block:: yul mstore(0x80, add(mload(0x80), 3)) // Here, the user-defined function `f` returns @@ -242,7 +242,7 @@ Future dialects migh introduce specific types for such pointers. When a variable is referenced, its current value is copied. For the EVM, this translates to a ``DUP`` instruction. -.. code:: +.. code-block:: yul { let zero := 0 @@ -260,7 +260,7 @@ you denote that following a colon. You can also declare multiple variables in one statement when you assign from a function call that returns multiple values. -.. code:: +.. code-block:: yul { let zero:uint32 := 0:uint32 @@ -283,7 +283,7 @@ values have to match. If you want to assign the values returned from a function that has multiple return parameters, you have to provide multiple variables. -.. code:: +.. code-block:: yul let v := 0 // re-assign v @@ -301,7 +301,7 @@ The if statement can be used for conditionally executing code. No "else" block can be defined. Consider using "switch" instead (see below) if you need multiple alternatives. -.. code:: +.. code-block:: yul if eq(value, 0) { revert(0, 0) } @@ -317,7 +317,7 @@ Contrary to other programming languages, for safety reasons, control flow does not continue from one case to the next. There can be a fallback or default case called ``default`` which is taken if none of the literal constants matches. -.. code:: +.. code-block:: yul { let x := 0 @@ -349,7 +349,7 @@ or skip to the post-part, respectively. The following example computes the sum of an area in memory. -.. code:: +.. code-block:: yul { let x := 0 @@ -361,7 +361,7 @@ The following example computes the sum of an area in memory. For loops can also be used as a replacement for while loops: Simply leave the initialization and post-iteration parts empty. -.. code:: +.. code-block:: yul { let x := 0 @@ -404,7 +404,7 @@ the current yul function. The following example implements the power function by square-and-multiply. -.. code:: +.. code-block:: yul { function power(base, exponent) -> result { @@ -425,7 +425,7 @@ Specification of Yul This chapter describes Yul code formally. Yul code is usually placed inside Yul objects, which are explained in their own chapter. -Grammar:: +.. code-block:: none Block = '{' Statement* '}' Statement = @@ -588,7 +588,7 @@ For an identifier ``v``, let ``$v`` be the name of the identifier. We will use a destructuring notation for the AST nodes. -.. code:: +.. code-block:: none E(G, L, <{St1, ..., Stn}>: Block) = let G1, L1, mode = E(G, L, St1, ..., Stn) @@ -915,7 +915,7 @@ Hex strings can be used to specify data in hex encoding, regular strings in native encoding. For code, ``datacopy`` will access its assembled binary representation. -Grammar:: +.. code-block:: none Object = 'object' StringLiteral '{' Code ( Object | Data )* '}' Code = 'code' Block @@ -927,7 +927,7 @@ Above, ``Block`` refers to ``Block`` in the Yul code grammar explained in the pr An example Yul Object is shown below: -.. code:: +.. code-block:: yul // A contract consists of a single object with sub-objects representing // the code to be deployed or other contracts it can create. @@ -1010,7 +1010,7 @@ for more details about its internals. If you want to use Solidity in stand-alone Yul mode, you activate the optimizer using ``--optimize``: -:: +.. code-block:: sh solc --strict-assembly --optimize From aa8107f45a33ca195f85d68f043958279b52b038 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 23 Apr 2020 16:29:17 +0200 Subject: [PATCH 49/55] Conditional strings for Whiskers. --- docs/contributing.rst | 5 +++++ libsolutil/Whiskers.cpp | 30 ++++++++++++++++++++++++------ libsolutil/Whiskers.h | 3 +++ test/libsolutil/Whiskers.cpp | 13 +++++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 98a01fd6d..395439018 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -356,6 +356,11 @@ by as many concatenations of its contents as there were sets of variables suppli each time replacing any ```` items by their respective value. Top-level variables can also be used inside such areas. +There are also conditionals of the form ``......``, where template replacements +continue recursively either in the first or the second segment depending on the value of the boolean +parameter ``name``. If ``......`` is used, then the check is whether +the string parameter ``name`` is non-empty. + .. _documentation-style: Documentation Style Guide diff --git a/libsolutil/Whiskers.cpp b/libsolutil/Whiskers.cpp index 208719d43..aebe55c69 100644 --- a/libsolutil/Whiskers.cpp +++ b/libsolutil/Whiskers.cpp @@ -132,7 +132,11 @@ string Whiskers::replace( map> const& _listParameters ) { - static regex listOrTag("<(" + paramRegex() + ")>|<#(" + paramRegex() + ")>((?:.|\\r|\\n)*?)|<\\?(" + paramRegex() + ")>((?:.|\\r|\\n)*?)(((?:.|\\r|\\n)*?))?"); + static regex listOrTag( + "<(" + paramRegex() + ")>|" + "<#(" + paramRegex() + ")>((?:.|\\r|\\n)*?)|" + "<\\?(\\+?" + paramRegex() + ")>((?:.|\\r|\\n)*?)(((?:.|\\r|\\n)*?))?" + ); return regex_replace(_template, listOrTag, [&](match_results _match) -> string { string tagName(_match[1]); @@ -164,12 +168,26 @@ string Whiskers::replace( else { assertThrow(!conditionName.empty(), WhiskersError, ""); - assertThrow( - _conditions.count(conditionName), - WhiskersError, "Condition parameter " + conditionName + " not set." - ); + bool conditionValue = false; + if (conditionName[0] == '+') + { + string tag = conditionName.substr(1); + assertThrow( + _parameters.count(tag), + WhiskersError, "Tag " + tag + " used as condition but was not set." + ); + conditionValue = !_parameters.at(tag).empty(); + } + else + { + assertThrow( + _conditions.count(conditionName), + WhiskersError, "Condition parameter " + conditionName + " not set." + ); + conditionValue = _conditions.at(conditionName); + } return replace( - _conditions.at(conditionName) ? _match[5] : _match[7], + conditionValue ? _match[5] : _match[7], _parameters, _conditions, _listParameters diff --git a/libsolutil/Whiskers.h b/libsolutil/Whiskers.h index 0165429fb..5eb00dce5 100644 --- a/libsolutil/Whiskers.h +++ b/libsolutil/Whiskers.h @@ -59,6 +59,9 @@ DEV_SIMPLE_EXCEPTION(WhiskersError); * - Condition parameter: ......, where "" is optional * replaced (and recursively expanded) by the first part if the condition is true * and by the second (or empty string if missing) if the condition is false + * - Conditional string parameter: ...... + * Works similar to a conditional parameter where the checked condition is + * that the regular (string) parameter called "name" is non-empty. * - List parameter: <#list>... * The part between the tags is repeated as often as values are provided * in the mapping. Each list element can have its own parameter -> value mapping. diff --git a/test/libsolutil/Whiskers.cpp b/test/libsolutil/Whiskers.cpp index 3f2a48733..093b0acb6 100644 --- a/test/libsolutil/Whiskers.cpp +++ b/test/libsolutil/Whiskers.cpp @@ -114,6 +114,19 @@ BOOST_AUTO_TEST_CASE(conditional_plus_list) BOOST_CHECK_EQUAL(m.render(), " - ab - "); } +BOOST_AUTO_TEST_CASE(string_as_conditional) +{ + string templ = "+-"; + BOOST_CHECK_EQUAL(Whiskers(templ)("b", "abc").render(), "+abc"); + BOOST_CHECK_EQUAL(Whiskers(templ)("b", "").render(), "-"); +} + +BOOST_AUTO_TEST_CASE(string_as_conditional_wrong) +{ + string templ = "+"; + BOOST_CHECK_EQUAL(Whiskers(templ)("b", "abc").render(), "+abc"); +} + BOOST_AUTO_TEST_CASE(complicated_replacement) { string templ = "a x \n >."; From dda883b585c2f00ac60c3729feaa9a8fda782b22 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 20 Apr 2020 23:26:00 +0200 Subject: [PATCH 50/55] IR generation for sha256. --- .../codegen/ir/IRGeneratorForStatements.cpp | 53 +++++++++++++++---- .../builtinFunctions/sha256_empty.sol | 3 +- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index d8cb8f49c..2edad9329 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -760,6 +760,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) "))\n"; break; } + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: + { + solAssert(!_functionCall.annotation().tryCall, ""); + appendExternalFunctionCall(_functionCall, arguments); + break; + } case FunctionType::Kind::ArrayPop: { auto const& memberAccessExpression = dynamic_cast(_functionCall.expression()).expression(); @@ -1460,7 +1468,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall( for (auto const& arg: _arguments) { argumentTypes.emplace_back(&type(*arg)); - argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList()); + if (IRVariable(*arg).type().sizeOnStack() > 0) + argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList()); } string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings)); @@ -1478,7 +1487,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall( ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector()); - solUnimplementedAssert(!funType.isBareCall(), ""); Whiskers templ(R"( if iszero(extcodesize(
)) { revert(0, 0) } @@ -1486,8 +1494,18 @@ void IRGeneratorForStatements::appendExternalFunctionCall( // storage for arguments and returned data let := - mstore(, ()) - let := (add(, 4) ) + + + mstore(, ()) + + let := ( + + + + add(, 4) + + + ) let := (,
, , , sub(, ), , ) @@ -1509,14 +1527,25 @@ void IRGeneratorForStatements::appendExternalFunctionCall( )"); templ("pos", m_context.newYulVariable()); templ("end", m_context.newYulVariable()); + templ("bareCall", funType.isBareCall()); if (_functionCall.annotation().tryCall) templ("success", m_context.trySuccessConditionVariable(_functionCall)); else templ("success", m_context.newYulVariable()); templ("freeMemory", freeMemory()); templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4))); - templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name()); - templ("address", IRVariable(_functionCall.expression()).part("address").name()); + + if (!funType.isBareCall()) + templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name()); + + if (funKind == FunctionType::Kind::ECRecover) + templ("address", "1"); + else if (funKind == FunctionType::Kind::SHA256) + templ("address", "2"); + else if (funKind == FunctionType::Kind::RIPEMD160) + templ("address", "3"); + else + templ("address", IRVariable(_functionCall.expression()).part("address").name()); // Always use the actual return length, and not our calculated expected length, if returndatacopy is supported. // This ensures it can catch badly formatted input from external calls. @@ -1548,9 +1577,15 @@ void IRGeneratorForStatements::appendExternalFunctionCall( // but all parameters of ecrecover are value types anyway. encodeInPlace = false; bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall; - solUnimplementedAssert(!encodeInPlace, ""); - solUnimplementedAssert(funType.padArguments(), ""); - templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall)); + + solUnimplementedAssert(encodeInPlace == !funType.padArguments(), ""); + if (encodeInPlace) + { + solUnimplementedAssert(!encodeForLibraryCall, ""); + templ("encodeArgs", abi.tupleEncoderPacked(argumentTypes, funType.parameterTypes())); + } + else + templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall)); templ("argumentString", argumentString); // Output data will replace input data, unless we have ECRecover (then, output diff --git a/test/libsolidity/semanticTests/builtinFunctions/sha256_empty.sol b/test/libsolidity/semanticTests/builtinFunctions/sha256_empty.sol index 69b9e15f5..ededa2fab 100644 --- a/test/libsolidity/semanticTests/builtinFunctions/sha256_empty.sol +++ b/test/libsolidity/semanticTests/builtinFunctions/sha256_empty.sol @@ -3,6 +3,7 @@ contract C { return sha256(""); } } - +// ==== +// compileViaYul: also // ---- // f() -> 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 From aa0a69b47f9d307c29213320c6471211ff3646ad Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 27 Apr 2020 12:03:44 +0100 Subject: [PATCH 51/55] IRGenerator: include assertion for FunctionType::Kind::Declaration --- libsolidity/codegen/ir/IRGeneratorForStatements.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index d8cb8f49c..a356b4e8c 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -578,6 +578,9 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) solUnimplementedAssert(!functionType->bound(), ""); switch (functionType->kind()) { + case FunctionType::Kind::Declaration: + solAssert(false, "Attempted to generate code for calling a function definition."); + break; case FunctionType::Kind::Internal: { vector args; From d0bed502601777b0db3e99aacb25630d4a42f7b6 Mon Sep 17 00:00:00 2001 From: Alexander Arlt Date: Tue, 31 Mar 2020 13:05:58 -0500 Subject: [PATCH 52/55] [ci] add chk_shellscripts --- .circleci/config.yml | 6 +++ scripts/chk_shellscripts/chk_shellscripts.sh | 27 +++++++++++ scripts/chk_shellscripts/ignore.txt | 50 ++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100755 scripts/chk_shellscripts/chk_shellscripts.sh create mode 100644 scripts/chk_shellscripts/ignore.txt diff --git a/.circleci/config.yml b/.circleci/config.yml index bfadb1139..d3bc4a068 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -287,9 +287,15 @@ jobs: - image: buildpack-deps:disco steps: - checkout + - run: + name: Install shellcheck + command: apt -q update && apt install -y shellcheck - run: name: Check for C++ coding style command: ./scripts/check_style.sh + - run: + name: checking shell scripts + command: ./scripts/chk_shellscripts/chk_shellscripts.sh chk_pylint: docker: diff --git a/scripts/chk_shellscripts/chk_shellscripts.sh b/scripts/chk_shellscripts/chk_shellscripts.sh new file mode 100755 index 000000000..8eccc5517 --- /dev/null +++ b/scripts/chk_shellscripts/chk_shellscripts.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -e + +REPO_ROOT="$(dirname "$0")"/../.. +REPO_ROOT=$(realpath "${REPO_ROOT}") +IGNORE_FILENAME="ignore.txt" +IGNORE_FILE="${REPO_ROOT}/scripts/chk_shellscripts/${IGNORE_FILENAME}" + +FOUND_FILES_TMP=$(mktemp) +IGNORE_FILES_TMP=$(mktemp) +trap 'rm -f ${FOUND_FILES_TMP} ; rm -f ${IGNORE_FILES_TMP}' EXIT + +sort < "${IGNORE_FILE}" >"${IGNORE_FILES_TMP}" +cd "${REPO_ROOT}" +find . -type f -name "*.sh" | sort >"${FOUND_FILES_TMP}" + +SHELLCHECK=${SHELLCHECK:-"$(command -v -- shellcheck)"} +if [ ! -f "${SHELLCHECK}" ]; then + echo "error: shellcheck '${SHELLCHECK}' not found." + exit 1 +fi + +FILES=$(join -v2 "${IGNORE_FILES_TMP}" "${FOUND_FILES_TMP}") + +# shellcheck disable=SC2086 +"${SHELLCHECK}" ${FILES[*]} diff --git a/scripts/chk_shellscripts/ignore.txt b/scripts/chk_shellscripts/ignore.txt new file mode 100644 index 000000000..098607764 --- /dev/null +++ b/scripts/chk_shellscripts/ignore.txt @@ -0,0 +1,50 @@ +./test/docsCodeStyle.sh +./test/cmdlineTests.sh +./test/externalTests.sh +./test/externalTests/common.sh +./test/externalTests/gnosis.sh +./test/externalTests/zeppelin.sh +./test/externalTests/colony.sh +./test/externalTests/solc-js/solc-js.sh +./scripts/common.sh +./scripts/isoltest.sh +./scripts/get_version.sh +./scripts/soltest.sh +./scripts/test_emscripten.sh +./scripts/wasm-rebuild/docker-scripts/rebuild_tags.sh +./scripts/wasm-rebuild/docker-scripts/rebuild_current.sh +./scripts/wasm-rebuild/docker-scripts/genbytecode.sh +./scripts/wasm-rebuild/docker-scripts/patch.sh +./scripts/wasm-rebuild/rebuild.sh +./scripts/build_emscripten.sh +./scripts/travis-emscripten/build_emscripten.sh +./scripts/travis-emscripten/install_deps.sh +./scripts/travis-emscripten/publish_binary.sh +./scripts/docker_build.sh +./scripts/docs_version_pragma_check.sh +./scripts/uniqueErrors.sh +./scripts/report_errors.sh +./scripts/tests.sh +./scripts/docker_deploy.sh +./scripts/bytecodecompare/storebytecode.sh +./scripts/deps-ppa/static_z3.sh +./scripts/ASTImportTest.sh +./scripts/install_static_z3.sh +./scripts/install_obsolete_jsoncpp_1_7_4.sh +./scripts/install_deps.sh +./scripts/build.sh +./scripts/check_style.sh +./scripts/run_proofs.sh +./scripts/common_cmdline.sh +./scripts/docker_deploy_manual.sh +./scripts/endToEndExtraction/create_traces.sh +./scripts/release.sh +./scripts/download_ossfuzz_corpus.sh +./scripts/release_ppa.sh +./scripts/install_cmake.sh +./scripts/release_emscripten.sh +./scripts/create_source_tarball.sh +./scripts/docs.sh +./.circleci/soltest.sh +./.circleci/osx_install_dependencies.sh +./.circleci/soltest_all.sh From 9d06dd070d3a8980042443ba3142867a179da206 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 27 Apr 2020 15:26:25 +0200 Subject: [PATCH 53/55] CircleCI: Change from Ubuntu Disco (19.04) to Ubuntu Focal (20.04) due to support EOL --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bfadb1139..bc0c3cf02 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -284,7 +284,7 @@ jobs: chk_coding_style: docker: - - image: buildpack-deps:disco + - image: buildpack-deps:focal steps: - checkout - run: From 3d772edc7a18451f16d71aebecb9acbd84461eea Mon Sep 17 00:00:00 2001 From: ssi91 Date: Fri, 17 Apr 2020 03:29:40 +0700 Subject: [PATCH 54/55] handle file prefix add the description to the changelog fix: use the right method to search in string follow the codestyle using tabs delete redundant declaration implement the handling inline Update Changelog.md Co-Authored-By: Leonardo --- Changelog.md | 1 + solc/CommandLineInterface.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 6a7042e6f..ec45f9da7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -15,6 +15,7 @@ Bugfixes: * Type Checker: Fix several internal errors by performing size and recursiveness checks of types before the full type checking. * Type Checker: Fix internal error when assigning to empty tuples. * Type Checker: Perform recursiveness check on structs declared at the file level. + * Standard Json Input: Fix error when using prefix ``file://`` in the field ``urls``. Build System: * soltest.sh: ``SOLIDITY_BUILD_DIR`` is no longer relative to ``REPO_ROOT`` to allow for build directories outside of the source tree. diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index da14c58f9..537b2096b 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -953,7 +953,10 @@ bool CommandLineInterface::processInput() "ReadFile callback used as callback kind " + _kind )); - auto path = boost::filesystem::path(_path); + string validPath = _path; + if (validPath.find("file://") == 0) + validPath.erase(0, 7); + auto path = boost::filesystem::path(validPath); auto canonicalPath = boost::filesystem::weakly_canonical(path); bool isAllowed = false; for (auto const& allowedDir: m_allowedDirectories) From f3f729549d09d7c28b4af5695cb9eed19e6726d6 Mon Sep 17 00:00:00 2001 From: Djordje Mijovic Date: Thu, 9 Apr 2020 21:59:17 +0200 Subject: [PATCH 55/55] [Sol->Yul] Enabling creation function call --- libsolidity/codegen/YulUtilFunctions.cpp | 28 ++++++ libsolidity/codegen/YulUtilFunctions.h | 7 ++ .../codegen/ir/IRGenerationContext.cpp | 15 +++ libsolidity/codegen/ir/IRGenerationContext.h | 15 ++- libsolidity/codegen/ir/IRGenerator.cpp | 43 +++++---- libsolidity/codegen/ir/IRGenerator.h | 12 ++- .../codegen/ir/IRGeneratorForStatements.cpp | 54 ++++++++++- libsolidity/interface/CompilerStack.cpp | 8 +- .../ir_compiler_inheritance_nosubobjects/args | 1 + .../input.sol | 7 ++ .../output | 55 +++++++++++ test/cmdlineTests/ir_compiler_subobjects/args | 1 + test/cmdlineTests/ir_compiler_subobjects/err | 5 + .../ir_compiler_subobjects/input.sol | 8 ++ .../ir_compiler_subobjects/output | 96 +++++++++++++++++++ .../standard_ir_requested/output.json | 2 + .../yul_string_format_ascii/output.json | 2 + .../output.json | 2 + .../output.json | 2 + .../yul_string_format_ascii_long/output.json | 2 + .../yul_string_format_hex/output.json | 2 + .../array/fixed_arrays_as_return_type.sol | 2 + .../array/function_array_cross_calls.sol | 2 + .../creation_function_call_no_args.sol | 15 +++ .../creation_function_call_with_args.sol | 20 ++++ .../creation_function_call_with_salt.sol | 21 ++++ .../gas_and_value_brace_syntax.sol | 4 +- .../interface_inheritance_conversions.sol | 2 + .../address_overload_resolution.sol | 2 + ...d_function_calldata_calldata_interface.sol | 2 + ...ted_function_calldata_memory_interface.sol | 2 + .../various/contract_binary_dependencies.sol | 2 + .../various/external_types_in_calls.sol | 2 + .../semanticTests/various/senders_balance.sol | 2 + .../various/staticcall_for_view_and_pure.sol | 1 + .../various/write_storage_external.sol | 2 + 36 files changed, 420 insertions(+), 28 deletions(-) create mode 100644 test/cmdlineTests/ir_compiler_inheritance_nosubobjects/args create mode 100644 test/cmdlineTests/ir_compiler_inheritance_nosubobjects/input.sol create mode 100644 test/cmdlineTests/ir_compiler_inheritance_nosubobjects/output create mode 100644 test/cmdlineTests/ir_compiler_subobjects/args create mode 100644 test/cmdlineTests/ir_compiler_subobjects/err create mode 100644 test/cmdlineTests/ir_compiler_subobjects/input.sol create mode 100644 test/cmdlineTests/ir_compiler_subobjects/output create mode 100644 test/libsolidity/semanticTests/functionCall/creation_function_call_no_args.sol create mode 100644 test/libsolidity/semanticTests/functionCall/creation_function_call_with_args.sol create mode 100644 test/libsolidity/semanticTests/functionCall/creation_function_call_with_salt.sol diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index ab449a730..75d297b34 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -1337,6 +1337,34 @@ string YulUtilFunctions::allocationFunction() }); } +string YulUtilFunctions::allocationTemporaryMemoryFunction() +{ + string functionName = "allocateTemporaryMemory"; + return m_functionCollector.createFunction(functionName, [&]() { + return Whiskers(R"( + function () -> memPtr { + memPtr := mload() + } + )") + ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) + ("functionName", functionName) + .render(); + }); +} + +string YulUtilFunctions::releaseTemporaryMemoryFunction() +{ + string functionName = "releaseTemporaryMemory"; + return m_functionCollector.createFunction(functionName, [&](){ + return Whiskers(R"( + function () { + } + )") + ("functionName", functionName) + .render(); + }); +} + string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type) { if (_type.baseType()->hasSimpleZeroValueInMemory()) diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index b16eb0672..4f7556f5f 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -253,6 +253,13 @@ public: /// Return value: pointer std::string allocationFunction(); + /// @returns the name of the function that allocates temporary memory with predefined size + /// Return value: pointer + std::string allocationTemporaryMemoryFunction(); + + /// @returns the name of the function that releases previously allocated temporary memory + std::string releaseTemporaryMemoryFunction(); + /// @returns the name of a function that zeroes an array. /// signature: (dataStart, dataSizeInBytes) -> std::string zeroMemoryArrayFunction(ArrayType const& _type); diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index aa6ec7619..43e9f95e2 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -96,6 +97,15 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl) return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id()); } +string IRGenerationContext::creationObjectName(ContractDefinition const& _contract) const +{ + return _contract.name() + "_" + toString(_contract.id()); +} +string IRGenerationContext::runtimeObjectName(ContractDefinition const& _contract) const +{ + return _contract.name() + "_" + toString(_contract.id()) + "_deployed"; +} + string IRGenerationContext::newYulVariable() { return "_" + to_string(++m_varCounter); @@ -170,6 +180,11 @@ YulUtilFunctions IRGenerationContext::utils() return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions); } +ABIFunctions IRGenerationContext::abiFunctions() +{ + return ABIFunctions(m_evmVersion, m_revertStrings, m_functions); +} + std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message) { return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message); diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index acbe6250b..0a07f2f29 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -20,6 +20,7 @@ #pragma once +#include #include #include #include @@ -38,11 +39,8 @@ namespace solidity::frontend { -class ContractDefinition; -class VariableDeclaration; -class FunctionDefinition; -class Expression; class YulUtilFunctions; +class ABIFunctions; /** * Class that contains contextual information during IR generation. @@ -93,6 +91,9 @@ public: std::string functionName(FunctionDefinition const& _function); std::string functionName(VariableDeclaration const& _varDecl); + std::string creationObjectName(ContractDefinition const& _contract) const; + std::string runtimeObjectName(ContractDefinition const& _contract) const; + std::string newYulVariable(); std::string internalDispatch(size_t _in, size_t _out); @@ -102,6 +103,8 @@ public: langutil::EVMVersion evmVersion() const { return m_evmVersion; }; + ABIFunctions abiFunctions(); + /// @returns code that stores @param _message for revert reason /// if m_revertStrings is debug. std::string revertReasonIfDebug(std::string const& _message = ""); @@ -112,6 +115,8 @@ public: /// function call that was invoked as part of the try statement. std::string trySuccessConditionVariable(Expression const& _expression) const; + std::set& subObjectsCreated() { return m_subObjects; } + private: langutil::EVMVersion m_evmVersion; RevertStrings m_revertStrings; @@ -131,6 +136,8 @@ private: /// long as the order of Yul functions in the generated code is deterministic and the same on /// all platforms - which is a property guaranteed by MultiUseYulFunctionCollector. std::set m_functionGenerationQueue; + + std::set m_subObjects; }; } diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 68a6f2a9d..aed373c10 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -48,9 +48,12 @@ using namespace solidity; using namespace solidity::util; using namespace solidity::frontend; -pair IRGenerator::run(ContractDefinition const& _contract) +pair IRGenerator::run( + ContractDefinition const& _contract, + map const& _otherYulSources +) { - string const ir = yul::reindent(generate(_contract)); + string const ir = yul::reindent(generate(_contract, _otherYulSources)); yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); if (!asmStack.parseAndAnalyze("", ir)) @@ -73,8 +76,19 @@ pair IRGenerator::run(ContractDefinition const& _contract) return {warning + ir, warning + asmStack.print()}; } -string IRGenerator::generate(ContractDefinition const& _contract) +string IRGenerator::generate( + ContractDefinition const& _contract, + map const& _otherYulSources +) { + auto subObjectSources = [&_otherYulSources](std::set const& subObjects) -> string + { + std::string subObjectsSources; + for (ContractDefinition const* subObject: subObjects) + subObjectsSources += _otherYulSources.at(subObject); + return subObjectsSources; + }; + Whiskers t(R"( object "" { code { @@ -93,13 +107,15 @@ string IRGenerator::generate(ContractDefinition const& _contract) } + } + } )"); resetContext(_contract); - t("CreationObject", creationObjectName(_contract)); + t("CreationObject", m_context.creationObjectName(_contract)); t("memoryInit", memoryInit()); t("notLibrary", !_contract.isLibrary()); @@ -112,7 +128,7 @@ string IRGenerator::generate(ContractDefinition const& _contract) constructorParams.emplace_back(m_context.newYulVariable()); t( "copyConstructorArguments", - m_utils.copyConstructorArgumentsToMemoryFunction(_contract, creationObjectName(_contract)) + m_utils.copyConstructorArgumentsToMemoryFunction(_contract, m_context.creationObjectName(_contract)) ); } t("constructorParams", joinHumanReadable(constructorParams)); @@ -123,12 +139,14 @@ string IRGenerator::generate(ContractDefinition const& _contract) generateImplicitConstructors(_contract); generateQueuedFunctions(); t("functions", m_context.functionCollector().requestedFunctions()); + t("subObjects", subObjectSources(m_context.subObjectsCreated())); resetContext(_contract); - t("RuntimeObject", runtimeObjectName(_contract)); + t("RuntimeObject", m_context.runtimeObjectName(_contract)); t("dispatch", dispatchRoutine(_contract)); generateQueuedFunctions(); t("runtimeFunctions", m_context.functionCollector().requestedFunctions()); + t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated())); return t.render(); } @@ -313,6 +331,7 @@ string IRGenerator::initStateVariables(ContractDefinition const& _contract) return generator.code(); } + void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contract) { auto listAllParams = [&]( @@ -375,7 +394,7 @@ string IRGenerator::deployCode(ContractDefinition const& _contract) codecopy(0, dataoffset(""), datasize("")) return(0, datasize("")) )X"); - t("object", runtimeObjectName(_contract)); + t("object", m_context.runtimeObjectName(_contract)); return t.render(); } @@ -384,16 +403,6 @@ string IRGenerator::callValueCheck() return "if callvalue() { revert(0, 0) }"; } -string IRGenerator::creationObjectName(ContractDefinition const& _contract) -{ - return _contract.name() + "_" + to_string(_contract.id()); -} - -string IRGenerator::runtimeObjectName(ContractDefinition const& _contract) -{ - return _contract.name() + "_" + to_string(_contract.id()) + "_deployed"; -} - string IRGenerator::implicitConstructorName(ContractDefinition const& _contract) { return "constructor_" + _contract.name() + "_" + to_string(_contract.id()); diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index fde382cd7..a4298a902 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -50,10 +50,16 @@ public: /// Generates and returns the IR code, in unoptimized and optimized form /// (or just pretty-printed, depending on the optimizer settings). - std::pair run(ContractDefinition const& _contract); + std::pair run( + ContractDefinition const& _contract, + std::map const& _otherYulSources + ); private: - std::string generate(ContractDefinition const& _contract); + std::string generate( + ContractDefinition const& _contract, + std::map const& _otherYulSources + ); std::string generate(Block const& _block); /// Generates code for all the functions from the function generation queue. @@ -87,8 +93,6 @@ private: std::string deployCode(ContractDefinition const& _contract); std::string callValueCheck(); - std::string creationObjectName(ContractDefinition const& _contract); - std::string runtimeObjectName(ContractDefinition const& _contract); std::string implicitConstructorName(ContractDefinition const& _contract); std::string dispatchRoutine(ContractDefinition const& _contract); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index a5706bd9c..252ddffa7 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -849,11 +849,63 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) templ("pos", m_context.newYulVariable()); templ("end", m_context.newYulVariable()); templ("freeMemory", freeMemory()); - templ("encode", abi.tupleEncoder({arguments.front()->annotation().type},{parameterTypes.front()})); + templ("encode", abi.tupleEncoder({arguments.front()->annotation().type}, {parameterTypes.front()})); templ("nonIndexedArgs", IRVariable(*arguments.front()).commaSeparatedList()); templ("log", "log" + to_string(logNumber)); templ("indexedArgs", indexedArgs); m_code << templ.render(); + + break; + } + case FunctionType::Kind::Creation: + { + solAssert(!functionType->gasSet(), "Gas limit set for contract creation."); + solAssert( + functionType->returnParameterTypes().size() == 1, + "Constructor should return only one type" + ); + + TypePointers argumentTypes; + string constructorParams; + for (ASTPointer const& arg: arguments) + { + argumentTypes.push_back(arg->annotation().type); + constructorParams += ", " + IRVariable{*arg}.commaSeparatedList(); + } + + ContractDefinition const* contract = + &dynamic_cast(*functionType->returnParameterTypes().front()).contractDefinition(); + m_context.subObjectsCreated().insert(contract); + + Whiskers t(R"( + let := () + let := add(, datasize("")) + if or(gt(, 0xffffffffffffffff), lt(, )) { revert(0, 0) } + datacopy(, dataoffset(""), datasize("")) + := () + + let := create2(, , sub(, ), ) + + let := create(, , sub(, )) + + () + )"); + t("memPos", m_context.newYulVariable()); + t("memEnd", m_context.newYulVariable()); + t("allocateTemporaryMemory", m_utils.allocationTemporaryMemoryFunction()); + t("releaseTemporaryMemory", m_utils.releaseTemporaryMemoryFunction()); + t("object", m_context.creationObjectName(*contract)); + t("abiEncode", + m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(),false) + ); + t("constructorParams", constructorParams); + t("value", functionType->valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0"); + t("saltSet", functionType->saltSet()); + if (functionType->saltSet()) + t("salt", IRVariable(_functionCall.expression()).part("salt").name()); + t("retVars", IRVariable(_functionCall).commaSeparatedList()); + m_code << t.render(); + break; } default: diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 16422320c..0af68bda7 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -1129,15 +1129,21 @@ void CompilerStack::generateIR(ContractDefinition const& _contract) if (!_contract.canBeDeployed()) return; + map otherYulSources; + Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); if (!compiledContract.yulIR.empty()) return; + string dependenciesSource; for (auto const* dependency: _contract.annotation().contractDependencies) + { generateIR(*dependency); + otherYulSources.emplace(dependency, m_contracts.at(dependency->fullyQualifiedName()).yulIR); + } IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings); - tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract); + tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract, otherYulSources); } void CompilerStack::generateEwasm(ContractDefinition const& _contract) diff --git a/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/args b/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/args new file mode 100644 index 000000000..cae21e720 --- /dev/null +++ b/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/args @@ -0,0 +1 @@ +--ir-optimized --optimize \ No newline at end of file diff --git a/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/input.sol b/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/input.sol new file mode 100644 index 000000000..69f553304 --- /dev/null +++ b/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/input.sol @@ -0,0 +1,7 @@ +pragma solidity >=0.6.0; + +contract C { + constructor() public {} +} +contract D is C { +} \ No newline at end of file diff --git a/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/output b/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/output new file mode 100644 index 000000000..fd372367a --- /dev/null +++ b/test/cmdlineTests/ir_compiler_inheritance_nosubobjects/output @@ -0,0 +1,55 @@ +Optimized IR: +/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + +object "C_6" { + code { + { + mstore(64, 128) + if callvalue() { revert(0, 0) } + let _1 := datasize("C_6_deployed") + codecopy(0, dataoffset("C_6_deployed"), _1) + return(0, _1) + } + } + object "C_6_deployed" { + code { + { + mstore(64, 128) + revert(0, 0) + } + } + } +} + +Optimized IR: +/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + +object "D_9" { + code { + { + mstore(64, 128) + if callvalue() { revert(0, 0) } + let _1 := datasize("D_9_deployed") + codecopy(0, dataoffset("D_9_deployed"), _1) + return(0, _1) + } + } + object "D_9_deployed" { + code { + { + mstore(64, 128) + revert(0, 0) + } + } + } +} diff --git a/test/cmdlineTests/ir_compiler_subobjects/args b/test/cmdlineTests/ir_compiler_subobjects/args new file mode 100644 index 000000000..cae21e720 --- /dev/null +++ b/test/cmdlineTests/ir_compiler_subobjects/args @@ -0,0 +1 @@ +--ir-optimized --optimize \ No newline at end of file diff --git a/test/cmdlineTests/ir_compiler_subobjects/err b/test/cmdlineTests/ir_compiler_subobjects/err new file mode 100644 index 000000000..a28ad1966 --- /dev/null +++ b/test/cmdlineTests/ir_compiler_subobjects/err @@ -0,0 +1,5 @@ +Warning: Unused local variable. + --> ir_compiler_subobjects/input.sol:6:9: + | +6 | C c = new C(); + | ^^^ diff --git a/test/cmdlineTests/ir_compiler_subobjects/input.sol b/test/cmdlineTests/ir_compiler_subobjects/input.sol new file mode 100644 index 000000000..e0fdbbc13 --- /dev/null +++ b/test/cmdlineTests/ir_compiler_subobjects/input.sol @@ -0,0 +1,8 @@ +pragma solidity >=0.6.0; + +contract C {} +contract D { + function f() public { + C c = new C(); + } +} diff --git a/test/cmdlineTests/ir_compiler_subobjects/output b/test/cmdlineTests/ir_compiler_subobjects/output new file mode 100644 index 000000000..6b5f8e677 --- /dev/null +++ b/test/cmdlineTests/ir_compiler_subobjects/output @@ -0,0 +1,96 @@ +Optimized IR: +/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + +object "C_2" { + code { + { + mstore(64, 128) + if callvalue() { revert(0, 0) } + let _1 := datasize("C_2_deployed") + codecopy(0, dataoffset("C_2_deployed"), _1) + return(0, _1) + } + } + object "C_2_deployed" { + code { + { + mstore(64, 128) + revert(0, 0) + } + } + } +} + +Optimized IR: +/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + +object "D_13" { + code { + { + mstore(64, 128) + if callvalue() { revert(0, 0) } + let _1 := datasize("D_13_deployed") + codecopy(0, dataoffset("D_13_deployed"), _1) + return(0, _1) + } + } + object "D_13_deployed" { + code { + { + mstore(64, 128) + if iszero(lt(calldatasize(), 4)) + { + let _1 := 0 + if eq(0x26121ff0, shr(224, calldataload(_1))) + { + if callvalue() { revert(_1, _1) } + if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) } + let _2 := datasize("C_2") + let _3 := add(128, _2) + if or(gt(_3, 0xffffffffffffffff), lt(_3, 128)) { revert(_1, _1) } + datacopy(128, dataoffset("C_2"), _2) + pop(create(_1, 128, _2)) + return(allocateMemory(_1), _1) + } + } + revert(0, 0) + } + function allocateMemory(size) -> memPtr + { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + } + object "C_2" { + code { + { + mstore(64, 128) + if callvalue() { revert(0, 0) } + let _1 := datasize("C_2_deployed") + codecopy(0, dataoffset("C_2_deployed"), _1) + return(0, _1) + } + } + object "C_2_deployed" { + code { + { + mstore(64, 128) + revert(0, 0) + } + } + } + } + } +} diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json index 2e7aef192..bf9b20ed8 100644 --- a/test/cmdlineTests/standard_ir_requested/output.json +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -76,7 +76,9 @@ object \"C_6\" { } } + } + } "}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii/output.json b/test/cmdlineTests/yul_string_format_ascii/output.json index 0ca1c28dc..81fa10ca7 100644 --- a/test/cmdlineTests/yul_string_format_ascii/output.json +++ b/test/cmdlineTests/yul_string_format_ascii/output.json @@ -131,7 +131,9 @@ object \"C_10\" { } } + } + } "}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json index 062a56a54..a171f87b5 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json @@ -99,7 +99,9 @@ object \"C_10\" { } } + } + } "}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json index 6779e68c0..3dccf690d 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json @@ -111,7 +111,9 @@ object \"C_10\" { } } + } + } "}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii_long/output.json b/test/cmdlineTests/yul_string_format_ascii_long/output.json index 4b4b11347..77360e5a4 100644 --- a/test/cmdlineTests/yul_string_format_ascii_long/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_long/output.json @@ -135,7 +135,9 @@ object \"C_10\" { } } + } + } "}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_hex/output.json b/test/cmdlineTests/yul_string_format_hex/output.json index d011fdade..81853dadb 100644 --- a/test/cmdlineTests/yul_string_format_hex/output.json +++ b/test/cmdlineTests/yul_string_format_hex/output.json @@ -111,7 +111,9 @@ object \"C_10\" { } } + } + } "}}},"sources":{"A":{"id":0}}} diff --git a/test/libsolidity/semanticTests/array/fixed_arrays_as_return_type.sol b/test/libsolidity/semanticTests/array/fixed_arrays_as_return_type.sol index 61d1a33f5..c09d397ee 100644 --- a/test/libsolidity/semanticTests/array/fixed_arrays_as_return_type.sol +++ b/test/libsolidity/semanticTests/array/fixed_arrays_as_return_type.sol @@ -17,5 +17,7 @@ contract B { } } +// ==== +// compileViaYul: also // ---- // f() -> 2, 3, 4, 5, 6, 1000, 1001, 1002, 1003, 1004 diff --git a/test/libsolidity/semanticTests/array/function_array_cross_calls.sol b/test/libsolidity/semanticTests/array/function_array_cross_calls.sol index 64f8c7402..d81240069 100644 --- a/test/libsolidity/semanticTests/array/function_array_cross_calls.sol +++ b/test/libsolidity/semanticTests/array/function_array_cross_calls.sol @@ -41,5 +41,7 @@ contract C { } } +// ==== +// compileViaYul: also // ---- // test() -> 5, 6, 7 diff --git a/test/libsolidity/semanticTests/functionCall/creation_function_call_no_args.sol b/test/libsolidity/semanticTests/functionCall/creation_function_call_no_args.sol new file mode 100644 index 000000000..eac910f05 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/creation_function_call_no_args.sol @@ -0,0 +1,15 @@ +contract C { + uint public i; + constructor() public { + i = 2; + } +} +contract D { + function f() public returns (uint r) { + return new C().i(); + } +} +// ==== +// compileViaYul: also +// ---- +// f() -> 2 diff --git a/test/libsolidity/semanticTests/functionCall/creation_function_call_with_args.sol b/test/libsolidity/semanticTests/functionCall/creation_function_call_with_args.sol new file mode 100644 index 000000000..32c2910e2 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/creation_function_call_with_args.sol @@ -0,0 +1,20 @@ +contract C { + uint public i; + constructor(uint newI) public { + i = newI; + } +} +contract D { + C c; + constructor(uint v) public { + c = new C(v); + } + function f() public returns (uint r) { + return c.i(); + } +} +// ==== +// compileViaYul: also +// ---- +// constructor(): 2 -> +// f() -> 2 diff --git a/test/libsolidity/semanticTests/functionCall/creation_function_call_with_salt.sol b/test/libsolidity/semanticTests/functionCall/creation_function_call_with_salt.sol new file mode 100644 index 000000000..3bb9765a9 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/creation_function_call_with_salt.sol @@ -0,0 +1,21 @@ +contract C { + uint public i; + constructor(uint newI) public { + i = newI; + } +} +contract D { + C c; + constructor(uint v) public { + c = new C{salt: "abc"}(v); + } + function f() public returns (uint r) { + return c.i(); + } +} +// ==== +// EVMVersion: >=constantinople +// compileViaYul: also +// ---- +// constructor(): 2 -> +// f() -> 2 diff --git a/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol index dbd524deb..46c922a9c 100644 --- a/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol +++ b/test/libsolidity/semanticTests/functionCall/gas_and_value_brace_syntax.sol @@ -36,8 +36,10 @@ contract test { } } +// ==== +// compileViaYul: also // ---- // constructor(), 20 wei -> // sendAmount(uint256): 5 -> 5 // outOfGas() -> FAILURE # call to helper should not succeed but amount should be transferred anyway # -// checkState() -> false, 15 \ No newline at end of file +// checkState() -> false, 15 diff --git a/test/libsolidity/semanticTests/interface_inheritance_conversions.sol b/test/libsolidity/semanticTests/interface_inheritance_conversions.sol index 4241afe81..62213211a 100644 --- a/test/libsolidity/semanticTests/interface_inheritance_conversions.sol +++ b/test/libsolidity/semanticTests/interface_inheritance_conversions.sol @@ -33,6 +33,8 @@ contract C { } } +// ==== +// compileViaYul: also // ---- // convertParent() -> 1 // convertSubA() -> 1, 2 diff --git a/test/libsolidity/semanticTests/intheritance/address_overload_resolution.sol b/test/libsolidity/semanticTests/intheritance/address_overload_resolution.sol index 9b68cc154..640fcce09 100644 --- a/test/libsolidity/semanticTests/intheritance/address_overload_resolution.sol +++ b/test/libsolidity/semanticTests/intheritance/address_overload_resolution.sol @@ -19,6 +19,8 @@ contract D { } } +// ==== +// compileViaYul: also // ---- // f() -> 1 // g() -> 5 diff --git a/test/libsolidity/semanticTests/intheritance/inherited_function_calldata_calldata_interface.sol b/test/libsolidity/semanticTests/intheritance/inherited_function_calldata_calldata_interface.sol index 9812ca520..cb89901a4 100644 --- a/test/libsolidity/semanticTests/intheritance/inherited_function_calldata_calldata_interface.sol +++ b/test/libsolidity/semanticTests/intheritance/inherited_function_calldata_calldata_interface.sol @@ -21,5 +21,7 @@ contract B { } } +// ==== +// compileViaYul: also // ---- // g() -> 42 diff --git a/test/libsolidity/semanticTests/intheritance/inherited_function_calldata_memory_interface.sol b/test/libsolidity/semanticTests/intheritance/inherited_function_calldata_memory_interface.sol index 9a2c1a1e7..febaf9f04 100644 --- a/test/libsolidity/semanticTests/intheritance/inherited_function_calldata_memory_interface.sol +++ b/test/libsolidity/semanticTests/intheritance/inherited_function_calldata_memory_interface.sol @@ -21,5 +21,7 @@ contract B { } } +// ==== +// compileViaYul: also // ---- // g() -> 42 diff --git a/test/libsolidity/semanticTests/various/contract_binary_dependencies.sol b/test/libsolidity/semanticTests/various/contract_binary_dependencies.sol index f1220d351..aa3734b3a 100644 --- a/test/libsolidity/semanticTests/various/contract_binary_dependencies.sol +++ b/test/libsolidity/semanticTests/various/contract_binary_dependencies.sol @@ -16,5 +16,7 @@ contract C { } } +// ==== +// compileViaYul: also // ---- // constructor() -> diff --git a/test/libsolidity/semanticTests/various/external_types_in_calls.sol b/test/libsolidity/semanticTests/various/external_types_in_calls.sol index 9906bd52b..0e65511c1 100644 --- a/test/libsolidity/semanticTests/various/external_types_in_calls.sol +++ b/test/libsolidity/semanticTests/various/external_types_in_calls.sol @@ -23,6 +23,8 @@ contract C { } } +// ==== +// compileViaYul: also // ---- // test() -> 9, 7 // t2() -> 9 diff --git a/test/libsolidity/semanticTests/various/senders_balance.sol b/test/libsolidity/semanticTests/various/senders_balance.sol index 0c84352b6..7da77aab2 100644 --- a/test/libsolidity/semanticTests/various/senders_balance.sol +++ b/test/libsolidity/semanticTests/various/senders_balance.sol @@ -15,6 +15,8 @@ contract D { } } +// ==== +// compileViaYul: also // ---- // constructor(), 27 wei -> // f() -> 27 diff --git a/test/libsolidity/semanticTests/various/staticcall_for_view_and_pure.sol b/test/libsolidity/semanticTests/various/staticcall_for_view_and_pure.sol index 96c5419be..c2f045082 100644 --- a/test/libsolidity/semanticTests/various/staticcall_for_view_and_pure.sol +++ b/test/libsolidity/semanticTests/various/staticcall_for_view_and_pure.sol @@ -32,6 +32,7 @@ contract D { } } // ==== +// compileViaYul: also // EVMVersion: >=byzantium // ---- // f() -> 0x1 # This should work, next should throw # diff --git a/test/libsolidity/semanticTests/various/write_storage_external.sol b/test/libsolidity/semanticTests/various/write_storage_external.sol index 0bbe52248..7ed94b6f8 100644 --- a/test/libsolidity/semanticTests/various/write_storage_external.sol +++ b/test/libsolidity/semanticTests/various/write_storage_external.sol @@ -34,6 +34,8 @@ contract D { } } +// ==== +// compileViaYul: also // ---- // f() -> 3 // g() -> 8