diff options
Diffstat (limited to 'libsolidity')
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 73 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.h | 3 | ||||
-rw-r--r-- | libsolidity/ast/AST.cpp | 2 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 10 | ||||
-rw-r--r-- | libsolidity/ast/Types.h | 9 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerContext.cpp | 16 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerContext.h | 10 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.cpp | 60 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.h | 12 | ||||
-rw-r--r-- | libsolidity/codegen/ContractCompiler.cpp | 13 | ||||
-rw-r--r-- | libsolidity/codegen/ExpressionCompiler.cpp | 17 | ||||
-rw-r--r-- | libsolidity/formal/Why3Translator.cpp | 898 | ||||
-rw-r--r-- | libsolidity/formal/Why3Translator.h | 147 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmCodeGen.cpp | 5 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.cpp | 15 | ||||
-rw-r--r-- | libsolidity/interface/CompilerStack.h | 7 | ||||
-rw-r--r-- | libsolidity/parsing/ParserBase.cpp | 20 | ||||
-rw-r--r-- | libsolidity/parsing/ParserBase.h | 9 |
18 files changed, 197 insertions, 1129 deletions
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 4194e1c2..ef8a9345 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -22,6 +22,7 @@ #include <libsolidity/analysis/TypeChecker.h> #include <memory> +#include <boost/algorithm/string/predicate.hpp> #include <boost/range/adaptor/reversed.hpp> #include <libsolidity/ast/AST.h> #include <libsolidity/inlineasm/AsmAnalysis.h> @@ -363,6 +364,35 @@ void TypeChecker::checkLibraryRequirements(ContractDefinition const& _contract) m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables"); } +void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) +{ + TupleType const& lhs = dynamic_cast<TupleType const&>(*type(_assignment.leftHandSide())); + TupleType const& rhs = dynamic_cast<TupleType const&>(*type(_assignment.rightHandSide())); + + bool fillRight = !lhs.components().empty() && (!lhs.components().back() || lhs.components().front()); + size_t storageToStorageCopies = 0; + size_t toStorageCopies = 0; + for (size_t i = 0; i < lhs.components().size(); ++i) + { + ReferenceType const* ref = dynamic_cast<ReferenceType const*>(lhs.components()[i].get()); + if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer()) + continue; + size_t rhsPos = fillRight ? i : rhs.components().size() - (lhs.components().size() - i); + solAssert(rhsPos < rhs.components().size(), ""); + toStorageCopies++; + if (rhs.components()[rhsPos]->dataStoredIn(DataLocation::Storage)) + storageToStorageCopies++; + } + if (storageToStorageCopies >= 1 && toStorageCopies >= 2) + m_errorReporter.warning( + _assignment.location(), + "This assignment performs two copies to storage. Since storage copies do not first " + "copy to a temporary location, one of them might be overwritten before the second " + "is executed and thus may have unexpected effects. It is safer to perform the copies " + "separately or assign to storage pointers first." + ); +} + void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) { auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); @@ -466,13 +496,26 @@ bool TypeChecker::visit(FunctionDefinition const& _function) var->accept(*this); } + set<Declaration const*> modifiers; for (ASTPointer<ModifierInvocation> const& modifier: _function.modifiers()) + { visitManually( *modifier, _function.isConstructor() ? dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts : vector<ContractDefinition const*>() ); + Declaration const* decl = &dereference(*modifier->name()); + if (modifiers.count(decl)) + { + if (dynamic_cast<ContractDefinition const*>(decl)) + m_errorReporter.declarationError(modifier->location(), "Base constructor already provided."); + else + m_errorReporter.declarationError(modifier->location(), "Modifier already used for this function."); + } + else + modifiers.insert(decl); + } if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) { if (_function.isImplemented()) @@ -1033,6 +1076,8 @@ bool TypeChecker::visit(Assignment const& _assignment) // Sequenced assignments of tuples is not valid, make the result a "void" type. _assignment.annotation().type = make_shared<TupleType>(); expectType(_assignment.rightHandSide(), *tupleType); + + checkDoubleStorageAssignment(_assignment); } else if (t->category() == Type::Category::Mapping) { @@ -1726,10 +1771,7 @@ void TypeChecker::endVisit(Literal const& _literal) if (_literal.looksLikeAddress()) { if (_literal.passesAddressChecksum()) - { _literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address); - return; - } else m_errorReporter.warning( _literal.location(), @@ -1737,10 +1779,13 @@ void TypeChecker::endVisit(Literal const& _literal) "If this is not used as an address, please prepend '00'." ); } - _literal.annotation().type = Type::forLiteral(_literal); - _literal.annotation().isPure = true; + if (!_literal.annotation().type) + _literal.annotation().type = Type::forLiteral(_literal); + if (!_literal.annotation().type) m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value."); + + _literal.annotation().isPure = true; } bool TypeChecker::contractDependenciesAreCyclic( @@ -1800,7 +1845,23 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte _expectedType.toString() + "." ); - } + } + + if ( + type(_expression)->category() == Type::Category::RationalNumber && + _expectedType.category() == Type::Category::FixedBytes + ) + { + auto literal = dynamic_cast<Literal const*>(&_expression); + + if (literal && !boost::starts_with(literal->value(), "0x")) + m_errorReporter.warning( + _expression.location(), + "Decimal literal assigned to bytesXX variable will be left-aligned. " + "Use an explicit conversion to silence this warning." + ); + } + } void TypeChecker::requireLValue(Expression const& _expression) diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 2fa66f97..ee43d13a 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -69,6 +69,9 @@ private: void checkContractExternalTypeClashes(ContractDefinition const& _contract); /// Checks that all requirements for a library are fulfilled if this is a library. void checkLibraryRequirements(ContractDefinition const& _contract); + /// Checks (and warns) if a tuple assignment might cause unexpected overwrites in storage. + /// Should only be called if the left hand side is tuple-typed. + void checkDoubleStorageAssignment(Assignment const& _assignment); virtual void endVisit(InheritanceSpecifier const& _inheritance) override; virtual void endVisit(UsingForDirective const& _usingFor) override; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 40dfa348..b929b6fe 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -534,6 +534,8 @@ bool Literal::looksLikeAddress() const { if (subDenomination() != SubDenomination::None) return false; + if (token() != Token::Number) + return false; string lit = value(); return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index bd3346f9..7dc6c4a6 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2248,6 +2248,16 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const return TypePointer(); } +TypePointer FunctionType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const +{ + if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual)) + return TypePointer(); + FunctionType const& other = dynamic_cast<FunctionType const&>(*_other); + if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1) + return commonType(shared_from_this(), _other); + return TypePointer(); +} + string FunctionType::canonicalName(bool) const { solAssert(m_kind == Kind::External, ""); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index c4ffc44c..f7a73ab5 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -933,6 +933,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override; virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; @@ -1038,6 +1039,7 @@ public: virtual std::string toString(bool _short) const override; virtual std::string canonicalName(bool _addDataLocation) const override; virtual bool canLiveOutsideStorage() const override { return false; } + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual TypePointer encodingType() const override { return std::make_shared<IntegerType>(256); @@ -1116,11 +1118,7 @@ public: explicit ModuleType(SourceUnit const& _source): m_sourceUnit(_source) {} - virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override - { - return TypePointer(); - } - + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } @@ -1178,6 +1176,7 @@ public: virtual std::string identifier() const override { return "t_inaccessible"; } virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; } virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; } + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; } virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; } diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index d98efcad..6875bda1 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -26,6 +26,7 @@ #include <libsolidity/codegen/Compiler.h> #include <libsolidity/interface/Version.h> #include <libsolidity/interface/ErrorReporter.h> +#include <libsolidity/parsing/Scanner.h> #include <libsolidity/inlineasm/AsmParser.h> #include <libsolidity/inlineasm/AsmCodeGen.h> #include <libsolidity/inlineasm/AsmAnalysis.h> @@ -123,6 +124,7 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); + solAssert(m_localVariables.count(&_declaration) == 0, "Variable already present"); m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent; } @@ -243,6 +245,20 @@ CompilerContext& CompilerContext::appendConditionalInvalid() return *this; } +CompilerContext& CompilerContext::appendRevert() +{ + return *this << u256(0) << u256(0) << Instruction::REVERT; +} + +CompilerContext& CompilerContext::appendConditionalRevert() +{ + *this << Instruction::ISZERO; + eth::AssemblyItem afterTag = appendConditionalJump(); + appendRevert(); + *this << afterTag; + return *this; +} + void CompilerContext::resetVisitedNodes(ASTNode const* _node) { stack<ASTNode const*> newStack; diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 030b35a6..1968c1e1 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -136,11 +136,15 @@ public: /// Appends a JUMP to a new tag and @returns the tag eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } /// Appends a JUMP to a tag already on the stack - CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); + CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); /// Appends an INVALID instruction - CompilerContext& appendInvalid(); + CompilerContext& appendInvalid(); /// Appends a conditional INVALID instruction - CompilerContext& appendConditionalInvalid(); + CompilerContext& appendConditionalInvalid(); + /// Appends a REVERT(0, 0) call + CompilerContext& appendRevert(); + /// Appends a conditional REVERT(0, 0) call + CompilerContext& appendConditionalRevert(); /// Appends a JUMP to a specific tag CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } /// Appends pushing of a new tag and @returns the new tag. diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 3baaaddf..4edec155 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -128,7 +128,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound m_context << Instruction::DUP1; storeStringData(bytesConstRef(str->value())); if (_padToWordBoundaries) - m_context << u256(((str->value().size() + 31) / 32) * 32); + m_context << u256(max<size_t>(32, ((str->value().size() + 31) / 32) * 32)); else m_context << u256(str->value().size()); m_context << Instruction::ADD; @@ -305,15 +305,9 @@ void CompilerUtils::memoryCopy32() m_context.appendInlineAssembly(R"( { - jumpi(end, eq(len, 0)) - start: - mstore(dst, mload(src)) - jumpi(end, iszero(gt(len, 32))) - dst := add(dst, 32) - src := add(src, 32) - len := sub(len, 32) - jump(start) - end: + for { let i := 0 } lt(i, len) { i := add(i, 32) } { + mstore(add(dst, i), mload(add(src, i))) + } } )", { "len", "dst", "src" } @@ -327,21 +321,22 @@ void CompilerUtils::memoryCopy() m_context.appendInlineAssembly(R"( { - // copy 32 bytes at once - start32: - jumpi(end32, lt(len, 32)) - mstore(dst, mload(src)) - dst := add(dst, 32) - src := add(src, 32) - len := sub(len, 32) - jump(start32) - end32: - - // copy the remainder (0 < len < 32) - let mask := sub(exp(256, sub(32, len)), 1) - let srcpart := and(mload(src), not(mask)) - let dstpart := and(mload(dst), mask) - mstore(dst, or(srcpart, dstpart)) + // copy 32 bytes at once + for + {} + iszero(lt(len, 32)) + { + dst := add(dst, 32) + src := add(src, 32) + len := sub(len, 32) + } + { mstore(dst, mload(src)) } + + // copy the remainder (0 < len < 32) + let mask := sub(exp(256, sub(32, len)), 1) + let srcpart := and(mload(src), not(mask)) + let dstpart := and(mload(dst), mask) + mstore(dst, or(srcpart, dstpart)) } )", { "len", "dst", "src" } @@ -392,7 +387,13 @@ void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) Instruction::OR; } -void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded, bool _chopSignBits) +void CompilerUtils::convertType( + Type const& _typeOnStack, + Type const& _targetType, + bool _cleanupNeeded, + bool _chopSignBits, + bool _asPartOfArgumentDecoding +) { // For a type extension, we need to remove all higher-order bits that we might have ignored in // previous operations. @@ -450,7 +451,10 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - m_context.appendConditionalInvalid(); + if (_asPartOfArgumentDecoding) + m_context.appendConditionalRevert(); + else + m_context.appendConditionalInvalid(); enumOverflowCheckPending = false; } break; @@ -985,7 +989,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda m_context << shiftFactor << Instruction::MUL; } if (_fromCalldata) - convertType(_type, _type, true); + convertType(_type, _type, true, false, true); return numBytes; } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index a88951bc..0ee053a9 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -110,10 +110,12 @@ public: void zeroInitialiseMemoryArray(ArrayType const& _type); /// Copies full 32 byte words in memory (regions cannot overlap), i.e. may copy more than length. + /// Length can be zero, in this case, it copies nothing. /// Stack pre: <size> <target> <source> /// Stack post: void memoryCopy32(); /// Copies data in memory (regions cannot overlap). + /// Length can be zero, in this case, it copies nothing. /// Stack pre: <size> <target> <source> /// Stack post: void memoryCopy(); @@ -135,7 +137,15 @@ public: /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be /// necessary. /// If @a _chopSignBits, the function resets the signed bits out of the width of the signed integer. - void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false, bool _chopSignBits = false); + /// If @a _asPartOfArgumentDecoding is true, failed conversions are flagged via REVERT, + /// otherwise they are flagged with INVALID. + void convertType( + Type const& _typeOnStack, + Type const& _targetType, + bool _cleanupNeeded = false, + bool _chopSignBits = false, + bool _asPartOfArgumentDecoding = false + ); /// Creates a zero-value for the given type and puts it onto the stack. This might allocate /// memory for memory references. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index dc090634..977a2c7c 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -111,7 +111,7 @@ void ContractCompiler::appendCallValueCheck() { // Throw if function is not payable but call contained ether. m_context << Instruction::CALLVALUE; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); } void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) @@ -276,7 +276,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary()); } else - m_context.appendInvalid(); + m_context.appendRevert(); for (auto const& it: interfaceFunctions) { @@ -368,7 +368,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter // copy to memory // move calldata type up again CompilerUtils(m_context).moveIntoStack(calldataType->sizeOnStack()); - CompilerUtils(m_context).convertType(*calldataType, arrayType); + CompilerUtils(m_context).convertType(*calldataType, arrayType, false, false, true); // fetch next pointer again CompilerUtils(m_context).moveToStackTop(arrayType.sizeOnStack()); } @@ -805,8 +805,7 @@ bool ContractCompiler::visit(Throw const& _throw) { CompilerContext::LocationSetter locationSetter(m_context, _throw); // Do not send back an error detail. - m_context << u256(0) << u256(0); - m_context << Instruction::REVERT; + m_context.appendRevert(); return false; } @@ -879,6 +878,7 @@ void ContractCompiler::appendModifierOrFunctionCode() solAssert(m_currentFunction, ""); unsigned stackSurplus = 0; Block const* codeBlock = nullptr; + vector<VariableDeclaration const*> addedVariables; m_modifierDepth++; @@ -902,6 +902,7 @@ void ContractCompiler::appendModifierOrFunctionCode() for (unsigned i = 0; i < modifier.parameters().size(); ++i) { m_context.addVariable(*modifier.parameters()[i]); + addedVariables.push_back(modifier.parameters()[i].get()); compileExpression( *modifierInvocation->arguments()[i], modifier.parameters()[i]->annotation().type @@ -928,6 +929,8 @@ void ContractCompiler::appendModifierOrFunctionCode() m_returnTags.pop_back(); CompilerUtils(m_context).popStackSlots(stackSurplus); + for (auto var: addedVariables) + m_context.removeVariable(*var); } m_modifierDepth--; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 03bba80c..a65549fd 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -587,7 +587,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::CREATE; // Check if zero (out of stack or not enough balance). m_context << Instruction::DUP1 << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); if (function.valueSet()) m_context << swapInstruction(1) << Instruction::POP; break; @@ -651,7 +651,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { // Check if zero (out of stack or not enough balance). m_context << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); } break; case FunctionType::Kind::Selfdestruct: @@ -660,9 +660,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::SELFDESTRUCT; break; case FunctionType::Kind::Revert: - // memory offset returned - zero length - m_context << u256(0) << u256(0); - m_context << Instruction::REVERT; + m_context.appendRevert(); break; case FunctionType::Kind::SHA3: { @@ -888,9 +886,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) auto success = m_context.appendConditionalJump(); if (function.kind() == FunctionType::Kind::Assert) // condition was not met, flag an error - m_context << Instruction::INVALID; + m_context.appendInvalid(); else - m_context << u256(0) << u256(0) << Instruction::REVERT; + m_context.appendRevert(); // the success branch m_context << success; break; @@ -1368,6 +1366,7 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) { + solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types."); if (_operator == Token::Equal || _operator == Token::NotEqual) { if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type)) @@ -1695,7 +1694,7 @@ void ExpressionCompiler::appendExternalFunctionCall( if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall) { m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); existenceChecked = true; } @@ -1731,7 +1730,7 @@ void ExpressionCompiler::appendExternalFunctionCall( { //Propagate error condition (if CALL pushes 0 on stack). m_context << Instruction::ISZERO; - m_context.appendConditionalInvalid(); + m_context.appendConditionalRevert(); } utils().popStackSlots(remainsSize); diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp deleted file mode 100644 index fecab2de..00000000 --- a/libsolidity/formal/Why3Translator.cpp +++ /dev/null @@ -1,898 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see <http://www.gnu.org/licenses/>. -*/ -/** - * @author Christian <c@ethdev.com> - * @date 2015 - * Component that translates Solidity code into the why3 programming language. - */ - -#include <libsolidity/formal/Why3Translator.h> -#include <boost/algorithm/string/predicate.hpp> - -using namespace std; -using namespace dev; -using namespace dev::solidity; - -bool Why3Translator::process(SourceUnit const& _source) -{ - try - { - if (m_lines.size() != 1 || !m_lines.back().contents.empty()) - fatalError(_source, string("Multiple source units not yet supported")); - appendPreface(); - _source.accept(*this); - } - catch (NoFormalType&) - { - solAssert(false, "There is a call to toFormalType() that does not catch NoFormalType exceptions."); - } - catch (FatalError& /*_e*/) - { - solAssert(m_errorOccured, ""); - } - return !m_errorOccured; -} - -string Why3Translator::translation() const -{ - string result; - for (auto const& line: m_lines) - result += string(line.indentation, '\t') + line.contents + "\n"; - return result; -} - -string Why3Translator::toFormalType(Type const& _type) const -{ - if (_type.category() == Type::Category::Bool) - return "bool"; - else if (auto type = dynamic_cast<IntegerType const*>(&_type)) - { - if (!type->isAddress() && !type->isSigned() && type->numBits() == 256) - return "uint256"; - } - else if (auto type = dynamic_cast<ArrayType const*>(&_type)) - { - if (!type->isByteArray() && type->isDynamicallySized() && type->dataStoredIn(DataLocation::Memory)) - { - // Not catching NoFormalType exception. Let the caller deal with it. - string base = toFormalType(*type->baseType()); - return "array " + base; - } - } - else if (auto mappingType = dynamic_cast<MappingType const*>(&_type)) - { - solAssert(mappingType->keyType(), "A mappingType misses a keyType."); - if (dynamic_cast<IntegerType const*>(&*mappingType->keyType())) - { - //@TODO Use the information from the key type and specify the length of the array as an invariant. - // Also the constructor need to specify the length of the array. - solAssert(mappingType->valueType(), "A mappingType misses a valueType."); - // Not catching NoFormalType exception. Let the caller deal with it. - string valueTypeFormal = toFormalType(*mappingType->valueType()); - return "array " + valueTypeFormal; - } - } - - BOOST_THROW_EXCEPTION(NoFormalType() - << errinfo_noFormalTypeFrom(_type.toString(true))); -} - -void Why3Translator::addLine(string const& _line) -{ - newLine(); - add(_line); - newLine(); -} - -void Why3Translator::add(string const& _str) -{ - m_lines.back().contents += _str; -} - -void Why3Translator::newLine() -{ - if (!m_lines.back().contents.empty()) - m_lines.push_back({"", m_lines.back().indentation}); -} - -void Why3Translator::unindent() -{ - newLine(); - solAssert(m_lines.back().indentation > 0, ""); - m_lines.back().indentation--; -} - -bool Why3Translator::visit(ContractDefinition const& _contract) -{ - if (m_seenContract) - error(_contract, "More than one contract not supported."); - m_seenContract = true; - m_currentContract.contract = &_contract; - if (_contract.isLibrary()) - error(_contract, "Libraries not supported."); - - addLine("module Contract_" + _contract.name()); - indent(); - addLine("use import int.Int"); - addLine("use import ref.Ref"); - addLine("use import map.Map"); - addLine("use import array.Array"); - addLine("use import int.ComputerDivision"); - addLine("use import mach.int.Unsigned"); - addLine("use import UInt256"); - addLine("exception Revert"); - addLine("exception Return"); - - if (_contract.stateVariables().empty()) - addLine("type state = ()"); - else - { - addLine("type state = {"); - indent(); - m_currentContract.stateVariables = _contract.stateVariables(); - for (VariableDeclaration const* variable: m_currentContract.stateVariables) - { - string varType; - try - { - varType = toFormalType(*variable->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - string typeName = typeNamePtr ? " \"" + *typeNamePtr + "\"" : ""; - fatalError(*variable, "Type" + typeName + " not supported for state variable."); - } - addLine("mutable _" + variable->name() + ": " + varType); - } - unindent(); - addLine("}"); - } - - addLine("type account = {"); - indent(); - addLine("mutable balance: uint256;"); - addLine("storage: state"); - unindent(); - addLine("}"); - - addLine("val external_call (this: account): bool"); - indent(); - addLine("ensures { result = false -> this = (old this) }"); - addLine("writes { this }"); - addSourceFromDocStrings(m_currentContract.contract->annotation()); - unindent(); - - if (!_contract.baseContracts().empty()) - error(*_contract.baseContracts().front(), "Inheritance not supported."); - if (!_contract.definedStructs().empty()) - error(*_contract.definedStructs().front(), "User-defined types not supported."); - if (!_contract.definedEnums().empty()) - error(*_contract.definedEnums().front(), "User-defined types not supported."); - if (!_contract.events().empty()) - error(*_contract.events().front(), "Events not supported."); - if (!_contract.functionModifiers().empty()) - error(*_contract.functionModifiers().front(), "Modifiers not supported."); - - ASTNode::listAccept(_contract.definedFunctions(), *this); - - return false; -} - -void Why3Translator::endVisit(ContractDefinition const&) -{ - m_currentContract.reset(); - unindent(); - addLine("end"); -} - -bool Why3Translator::visit(FunctionDefinition const& _function) -{ - if (!_function.isImplemented()) - { - error(_function, "Unimplemented functions not supported."); - return false; - } - if (_function.name().empty()) - { - error(_function, "Fallback functions not supported."); - return false; - } - if (!_function.modifiers().empty()) - { - error(_function, "Modifiers not supported."); - return false; - } - - m_localVariables.clear(); - for (auto const& var: _function.parameters()) - m_localVariables[var->name()] = var.get(); - for (auto const& var: _function.returnParameters()) - m_localVariables[var->name()] = var.get(); - for (auto const& var: _function.localVariables()) - m_localVariables[var->name()] = var; - - add("let rec _" + _function.name()); - add(" (this: account)"); - for (auto const& param: _function.parameters()) - { - string paramType; - try - { - paramType = toFormalType(*param->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*param, "Parameter type \"" + (typeName ? *typeName : "") + "\" not supported."); - } - if (param->name().empty()) - error(*param, "Anonymous function parameters not supported."); - add(" (arg_" + param->name() + ": " + paramType + ")"); - } - add(":"); - - indent(); - indent(); - string retString = "("; - for (auto const& retParam: _function.returnParameters()) - { - string paramType; - try - { - paramType = toFormalType(*retParam->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*retParam, "Parameter type " + (typeName ? *typeName : "") + " not supported."); - } - if (retString.size() != 1) - retString += ", "; - retString += paramType; - } - add(retString + ")"); - unindent(); - - addSourceFromDocStrings(_function.annotation()); - if (!m_currentContract.contract) - error(_function, "Only functions inside contracts allowed."); - addSourceFromDocStrings(m_currentContract.contract->annotation()); - - if (_function.isDeclaredConst()) - addLine("ensures { (old this) = this }"); - else - addLine("writes { this }"); - - addLine("="); - - // store the prestate in the case we need to revert - addLine("let prestate = {balance = this.balance; storage = " + copyOfStorage() + "} in "); - - // initialise local variables - for (auto const& variable: _function.parameters()) - addLine("let _" + variable->name() + " = ref arg_" + variable->name() + " in"); - for (auto const& variable: _function.returnParameters()) - { - if (variable->name().empty()) - error(*variable, "Unnamed return variables not yet supported."); - string varType; - try - { - varType = toFormalType(*variable->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in return parameter not yet supported."); - } - addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in"); - } - for (VariableDeclaration const* variable: _function.localVariables()) - { - if (variable->name().empty()) - error(*variable, "Unnamed variables not yet supported."); - string varType; - try - { - varType = toFormalType(*variable->annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in variable declaration not yet supported."); - } - addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in"); - } - addLine("try"); - - _function.body().accept(*this); - add(";"); - addLine("raise Return"); - - string retVals; - for (auto const& variable: _function.returnParameters()) - { - if (!retVals.empty()) - retVals += ", "; - retVals += "!_" + variable->name(); - } - addLine("with Return -> (" + retVals + ") |"); - string reversion = " Revert -> this.balance <- prestate.balance; "; - for (auto const* variable: m_currentContract.stateVariables) - reversion += "this.storage._" + variable->name() + " <- prestate.storage._" + variable->name() + "; "; - //@TODO in case of reversion the return values are wrong - we need to change the - // return type to include a bool to signify if an exception was thrown. - reversion += "(" + retVals + ")"; - addLine(reversion); - unindent(); - addLine("end"); - addLine(""); - return false; -} - -void Why3Translator::endVisit(FunctionDefinition const&) -{ - m_localVariables.clear(); -} - -bool Why3Translator::visit(Block const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - add("begin"); - indent(); - for (size_t i = 0; i < _node.statements().size(); ++i) - { - _node.statements()[i]->accept(*this); - if (i != _node.statements().size() - 1) - { - auto it = m_lines.end() - 1; - while (it != m_lines.begin() && it->contents.empty()) - --it; - if (!boost::algorithm::ends_with(it->contents, "begin")) - it->contents += ";"; - } - newLine(); - } - unindent(); - add("end"); - return false; -} - -bool Why3Translator::visit(IfStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - add("if "); - _node.condition().accept(*this); - add(" then"); - visitIndentedUnlessBlock(_node.trueStatement()); - if (_node.falseStatement()) - { - newLine(); - add("else"); - visitIndentedUnlessBlock(*_node.falseStatement()); - } - return false; -} - -bool Why3Translator::visit(WhileStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - // Why3 does not appear to support do-while loops, - // so we will simulate them by performing a while - // loop with the body prepended once. - - if (_node.isDoWhile()) - { - visitIndentedUnlessBlock(_node.body()); - newLine(); - } - - add("while "); - _node.condition().accept(*this); - newLine(); - add("do"); - visitIndentedUnlessBlock(_node.body()); - add("done"); - return false; -} - -bool Why3Translator::visit(Return const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - if (_node.expression()) - { - solAssert(!!_node.annotation().functionReturnParameters, ""); - auto const& params = _node.annotation().functionReturnParameters->parameters(); - if (params.size() != 1) - { - error(_node, "Directly returning tuples not supported. Rather assign to return variable."); - return false; - } - add("begin _" + params.front()->name() + " := "); - _node.expression()->accept(*this); - add("; raise Return end"); - } - else - add("raise Return"); - return false; -} - -bool Why3Translator::visit(Throw const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - add("raise Revert"); - return false; -} - -bool Why3Translator::visit(VariableDeclarationStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - - if (_node.declarations().size() != 1) - { - error(_node, "Multiple variables not supported."); - return false; - } - if (_node.initialValue()) - { - add("_" + _node.declarations().front()->name() + " := "); - _node.initialValue()->accept(*this); - } - return false; -} - -bool Why3Translator::visit(ExpressionStatement const& _node) -{ - addSourceFromDocStrings(_node.annotation()); - return true; -} - -bool Why3Translator::visit(Assignment const& _node) -{ - if (_node.assignmentOperator() != Token::Assign) - error(_node, "Compound assignment not supported."); - - _node.leftHandSide().accept(*this); - - add(m_currentLValueIsRef ? " := " : " <- "); - _node.rightHandSide().accept(*this); - - return false; -} - -bool Why3Translator::visit(TupleExpression const& _node) -{ - if (_node.components().size() != 1) - error(_node, "Only tuples with exactly one component supported."); - add("("); - return true; -} - -bool Why3Translator::visit(UnaryOperation const& _unaryOperation) -{ - try - { - toFormalType(*_unaryOperation.annotation().type); - } - catch (NoFormalType &err) - { - string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err); - error(_unaryOperation, "Type \"" + (typeNamePtr ? *typeNamePtr : "") + "\" supported in unary operation."); - } - - switch (_unaryOperation.getOperator()) - { - case Token::Not: // ! - add("(not "); - break; - default: - error(_unaryOperation, "Operator not supported."); - break; - } - - _unaryOperation.subExpression().accept(*this); - add(")"); - - return false; -} - -bool Why3Translator::visit(BinaryOperation const& _binaryOperation) -{ - Expression const& leftExpression = _binaryOperation.leftExpression(); - Expression const& rightExpression = _binaryOperation.rightExpression(); - solAssert(!!_binaryOperation.annotation().commonType, ""); - Type const& commonType = *_binaryOperation.annotation().commonType; - Token::Value const c_op = _binaryOperation.getOperator(); - - if (commonType.category() == Type::Category::RationalNumber) - { - auto const& constantNumber = dynamic_cast<RationalNumberType const&>(commonType); - if (constantNumber.isFractional()) - error(_binaryOperation, "Fractional numbers not supported."); - else - add("(of_int " + toString(commonType.literalValue(nullptr)) + ")"); - return false; - } - static const map<Token::Value, char const*> optrans({ - {Token::And, " && "}, - {Token::Or, " || "}, - {Token::BitOr, " lor "}, - {Token::BitXor, " lxor "}, - {Token::BitAnd, " land "}, - {Token::Add, " + "}, - {Token::Sub, " - "}, - {Token::Mul, " * "}, - {Token::Div, " / "}, - {Token::Mod, " mod "}, - {Token::Equal, " = "}, - {Token::NotEqual, " <> "}, - {Token::LessThan, " < "}, - {Token::GreaterThan, " > "}, - {Token::LessThanOrEqual, " <= "}, - {Token::GreaterThanOrEqual, " >= "} - }); - if (!optrans.count(c_op)) - { - error(_binaryOperation, "Operator not supported."); - return true; - } - - add("("); - leftExpression.accept(*this); - add(optrans.at(c_op)); - rightExpression.accept(*this); - add(")"); - - return false; -} - -bool Why3Translator::visit(FunctionCall const& _node) -{ - if (_node.annotation().kind == FunctionCallKind::TypeConversion || _node.annotation().kind == FunctionCallKind::StructConstructorCall) - { - error(_node, "Only ordinary function calls supported."); - return true; - } - FunctionType const& function = dynamic_cast<FunctionType const&>(*_node.expression().annotation().type); - switch (function.kind()) - { - case FunctionType::Kind::AddMod: - case FunctionType::Kind::MulMod: - { - //@todo require that third parameter is not zero - add("(of_int (mod (Int.("); - add(function.kind() == FunctionType::Kind::AddMod ? "+" : "*"); - add(") (to_int "); - _node.arguments().at(0)->accept(*this); - add(") (to_int "); - _node.arguments().at(1)->accept(*this); - add(")) (to_int "); - _node.arguments().at(2)->accept(*this); - add(")))"); - return false; - } - case FunctionType::Kind::Internal: - { - if (!_node.names().empty()) - { - error(_node, "Function calls with named arguments not supported."); - return true; - } - - //@TODO check type conversions - - add("("); - _node.expression().accept(*this); - add(" state"); - for (auto const& arg: _node.arguments()) - { - add(" "); - arg->accept(*this); - } - add(")"); - return false; - } - case FunctionType::Kind::Bare: - { - if (!_node.arguments().empty()) - { - error(_node, "Function calls with named arguments not supported."); - return true; - } - - add("("); - indent(); - add("let amount = 0 in "); - _node.expression().accept(*this); - addLine("if amount <= this.balance then"); - indent(); - addLine("let balance_precall = this.balance in"); - addLine("begin"); - indent(); - addLine("this.balance <- this.balance - amount;"); - addLine("if not (external_call this) then begin this.balance = balance_precall; false end else true"); - unindent(); - addLine("end"); - unindent(); - addLine("else false"); - - unindent(); - add(")"); - return false; - } - case FunctionType::Kind::SetValue: - { - add("let amount = "); - solAssert(_node.arguments().size() == 1, ""); - _node.arguments()[0]->accept(*this); - add(" in "); - return false; - } - default: - error(_node, "Only internal function calls supported."); - return true; - } -} - -bool Why3Translator::visit(MemberAccess const& _node) -{ - if ( - _node.expression().annotation().type->category() == Type::Category::Array && - _node.memberName() == "length" && - !_node.annotation().lValueRequested - ) - { - add("(of_int "); - _node.expression().accept(*this); - add(".length"); - add(")"); - } - else if ( - _node.memberName() == "call" && - *_node.expression().annotation().type == IntegerType(160, IntegerType::Modifier::Address) - ) - { - // Do nothing, do not even visit the address because this will be an external call - //@TODO ensure that the expression itself does not have side-effects - return false; - } - else - error(_node, "Member access: Only call and array length supported."); - return false; -} - -bool Why3Translator::visit(IndexAccess const& _node) -{ - auto baseType = dynamic_cast<ArrayType const*>(_node.baseExpression().annotation().type.get()); - if (!baseType) - { - error(_node, "Index access only supported for arrays."); - return true; - } - if (_node.annotation().lValueRequested) - { - error(_node, "Assignment to array elements not supported."); - return true; - } - add("("); - _node.baseExpression().accept(*this); - add("[to_int "); - _node.indexExpression()->accept(*this); - add("]"); - add(")"); - - return false; -} - -bool Why3Translator::visit(Identifier const& _identifier) -{ - Declaration const* declaration = _identifier.annotation().referencedDeclaration; - if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) - add("_" + functionDef->name()); - else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration)) - { - bool isStateVar = isStateVariable(variable); - bool lvalue = _identifier.annotation().lValueRequested; - if (isStateVar) - add("this.storage."); - else if (!lvalue) - add("!("); - add("_" + variable->name()); - if (!isStateVar && !lvalue) - add(")"); - m_currentLValueIsRef = !isStateVar; - } - else - error(_identifier, "Not supported."); - return false; -} - -bool Why3Translator::visit(Literal const& _literal) -{ - TypePointer type = _literal.annotation().type; - switch (type->category()) - { - case Type::Category::Bool: - if (type->literalValue(&_literal) == 0) - add("false"); - else - add("true"); - break; - case Type::Category::RationalNumber: - { - auto const& constantNumber = dynamic_cast<RationalNumberType const&>(*type); - if (constantNumber.isFractional()) - error(_literal, "Fractional numbers not supported."); - else - add("(of_int " + toString(type->literalValue(&_literal)) + ")"); - break; - } - default: - error(_literal, "Not supported."); - } - return false; -} - -bool Why3Translator::visit(PragmaDirective const& _pragma) -{ - if (_pragma.tokens().empty()) - error(_pragma, "Not supported"); - else if (_pragma.literals().empty()) - error(_pragma, "Not supported"); - else if (_pragma.literals()[0] != "solidity") - error(_pragma, "Not supported"); - else if (_pragma.tokens()[0] != Token::Identifier) - error(_pragma, "A literal 'solidity' is not an identifier. Strange"); - - return false; -} - -bool Why3Translator::isStateVariable(VariableDeclaration const* _var) const -{ - return contains(m_currentContract.stateVariables, _var); -} - -bool Why3Translator::isStateVariable(string const& _name) const -{ - for (auto const& var: m_currentContract.stateVariables) - if (var->name() == _name) - return true; - return false; -} - -bool Why3Translator::isLocalVariable(VariableDeclaration const* _var) const -{ - for (auto const& var: m_localVariables) - if (var.second == _var) - return true; - return false; -} - -bool Why3Translator::isLocalVariable(string const& _name) const -{ - return m_localVariables.count(_name); -} - -string Why3Translator::copyOfStorage() const -{ - if (m_currentContract.stateVariables.empty()) - return "()"; - string ret = "{"; - bool first = true; - for (auto const* variable: m_currentContract.stateVariables) - { - if (first) - first = false; - else - ret += "; "; - ret += "_" + variable->name() + " = this.storage._" + variable->name(); - } - return ret + "}"; -} - -void Why3Translator::visitIndentedUnlessBlock(Statement const& _statement) -{ - bool isBlock = !!dynamic_cast<Block const*>(&_statement); - if (isBlock) - newLine(); - else - indent(); - _statement.accept(*this); - if (isBlock) - newLine(); - else - unindent(); -} - -void Why3Translator::addSourceFromDocStrings(DocumentedAnnotation const& _annotation) -{ - auto why3Range = _annotation.docTags.equal_range("why3"); - for (auto i = why3Range.first; i != why3Range.second; ++i) - addLine(transformVariableReferences(i->second.content)); -} - -string Why3Translator::transformVariableReferences(string const& _annotation) -{ - string ret; - auto pos = _annotation.begin(); - while (true) - { - auto hash = find(pos, _annotation.end(), '#'); - ret.append(pos, hash); - if (hash == _annotation.end()) - break; - - auto hashEnd = find_if(hash + 1, _annotation.end(), [](char _c) - { - return - (_c != '_' && _c != '$') && - !('a' <= _c && _c <= 'z') && - !('A' <= _c && _c <= 'Z') && - !('0' <= _c && _c <= '9'); - }); - string varName(hash + 1, hashEnd); - if (isLocalVariable(varName)) - ret += "(!_" + varName + ")"; - else if (isStateVariable(varName)) - ret += "(this.storage._" + varName + ")"; - //@todo return variables - else - ret.append(hash, hashEnd); - - pos = hashEnd; - } - return ret; -} - -void Why3Translator::appendPreface() -{ - m_lines.push_back(Line{R"( -module UInt256 - use import mach.int.Unsigned - type uint256 - constant max_uint256: int = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - clone export mach.int.Unsigned with - type t = uint256, - constant max = max_uint256 -end - -module Address - use import mach.int.Unsigned - type address - constant max_address: int = 0xffffffffffffffffffffffffffffffffffffffff (* 160 bit = 40 f's *) - clone export mach.int.Unsigned with - type t = address, - constant max = max_address -end - )", 0}); -} - -void Why3Translator::error(ASTNode const& _source, std::string const& _description) -{ - m_errorOccured = true; - m_errorReporter.why3TranslatorError(_source, _description); -} -void Why3Translator::fatalError(ASTNode const& _source, std::string const& _description) -{ - m_errorOccured = true; - m_errorReporter.fatalWhy3TranslatorError(_source, _description); -} - diff --git a/libsolidity/formal/Why3Translator.h b/libsolidity/formal/Why3Translator.h deleted file mode 100644 index b48317be..00000000 --- a/libsolidity/formal/Why3Translator.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see <http://www.gnu.org/licenses/>. -*/ -/** - * @author Christian <c@ethdev.com> - * @date 2015 - * Component that translates Solidity code into the why3 programming language. - */ - -#pragma once - -#include <libsolidity/ast/ASTVisitor.h> -#include <libsolidity/interface/ErrorReporter.h> -#include <string> - -namespace dev -{ -namespace solidity -{ - -class SourceUnit; - -/** - * Simple translator from Solidity to Why3. - * - * @todo detect side effects in sub-expressions and limit them to one per statement. #1043 - * @todo `x = y = z` - * @todo implicit and explicit type conversion - */ -class Why3Translator: private ASTConstVisitor -{ -public: - Why3Translator(ErrorReporter& _errorReporter): m_lines(std::vector<Line>{{std::string(), 0}}), m_errorReporter(_errorReporter) {} - - /// Appends formalisation of the given source unit to the output. - /// @returns false on error. - bool process(SourceUnit const& _source); - - std::string translation() const; - -private: - /// Appends imports and constants use throughout the formal code. - void appendPreface(); - - /// @returns a string representation of the corresponding formal type or throws NoFormalType exception. - std::string toFormalType(Type const& _type) const; - using errinfo_noFormalTypeFrom = boost::error_info<struct tag_noFormalTypeFrom, std::string /* name of the type that cannot be translated */ >; - struct NoFormalType: virtual Exception {}; - - void error(ASTNode const& _source, std::string const& _description); - void fatalError(ASTNode const& _source, std::string const& _description); - - void indent() { newLine(); m_lines.back().indentation++; } - void unindent(); - void addLine(std::string const& _line); - void add(std::string const& _str); - void newLine(); - void appendSemicolon(); - - virtual bool visit(SourceUnit const&) override { return true; } - virtual bool visit(ContractDefinition const& _contract) override; - virtual void endVisit(ContractDefinition const& _contract) override; - virtual bool visit(FunctionDefinition const& _function) override; - virtual void endVisit(FunctionDefinition const& _function) override; - virtual bool visit(Block const&) override; - virtual bool visit(IfStatement const& _node) override; - virtual bool visit(WhileStatement const& _node) override; - virtual bool visit(Return const& _node) override; - virtual bool visit(Throw const& _node) override; - virtual bool visit(VariableDeclarationStatement const& _node) override; - virtual bool visit(ExpressionStatement const&) override; - virtual bool visit(Assignment const& _node) override; - virtual bool visit(TupleExpression const& _node) override; - virtual void endVisit(TupleExpression const&) override { add(")"); } - virtual bool visit(UnaryOperation const& _node) override; - virtual bool visit(BinaryOperation const& _node) override; - virtual bool visit(FunctionCall const& _node) override; - virtual bool visit(MemberAccess const& _node) override; - virtual bool visit(IndexAccess const& _node) override; - virtual bool visit(Identifier const& _node) override; - virtual bool visit(Literal const& _node) override; - virtual bool visit(PragmaDirective const& _node) override; - - virtual bool visitNode(ASTNode const& _node) override - { - m_errorReporter.why3TranslatorError(_node, "Code not supported for formal verification."); - return false; - } - - bool isStateVariable(VariableDeclaration const* _var) const; - bool isStateVariable(std::string const& _name) const; - bool isLocalVariable(VariableDeclaration const* _var) const; - bool isLocalVariable(std::string const& _name) const; - - /// @returns a string representing an expression that is a copy of this.storage - std::string copyOfStorage() const; - - /// Visits the given statement and indents it unless it is a block - /// (which does its own indentation). - void visitIndentedUnlessBlock(Statement const& _statement); - - void addSourceFromDocStrings(DocumentedAnnotation const& _annotation); - /// Transforms substring like `#varName` and `#stateVarName` to code that evaluates to their value. - std::string transformVariableReferences(std::string const& _annotation); - - /// True if we have already seen a contract. For now, only a single contract - /// is supported. - bool m_seenContract = false; - bool m_errorOccured = false; - - /// Metadata relating to the current contract - struct ContractMetadata - { - ContractDefinition const* contract = nullptr; - std::vector<VariableDeclaration const*> stateVariables; - - void reset() { contract = nullptr; stateVariables.clear(); } - }; - - ContractMetadata m_currentContract; - bool m_currentLValueIsRef = false; - std::map<std::string, VariableDeclaration const*> m_localVariables; - - struct Line - { - std::string contents; - unsigned indentation; - }; - std::vector<Line> m_lines; - ErrorReporter& m_errorReporter; -}; - -} -} diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 3c7c62c6..2bbd1b70 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -121,6 +121,11 @@ public: solAssert(false, "RETURNSUB not implemented for EVM 1.0"); } + virtual void appendAssemblySize() override + { + m_assembly.appendProgramSize(); + } + private: LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const { diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 8be2c8dd..e2507821 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -40,7 +40,6 @@ #include <libsolidity/interface/ABI.h> #include <libsolidity/interface/Natspec.h> #include <libsolidity/interface/GasEstimator.h> -#include <libsolidity/formal/Why3Translator.h> #include <libevmasm/Exceptions.h> @@ -316,20 +315,6 @@ void CompilerStack::link() } } -bool CompilerStack::prepareFormalAnalysis(ErrorReporter* _errorReporter) -{ - if (!_errorReporter) - _errorReporter = &m_errorReporter; - Why3Translator translator(*_errorReporter); - for (Source const* source: m_sourceOrder) - if (!translator.process(*source->ast)) - return false; - - m_formalTranslation = translator.translation(); - - return true; -} - eth::AssemblyItems const* CompilerStack::assemblyItems(string const& _contractName) const { Contract const& currentContract = contract(_contractName); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index f7435f0e..c51ae9c9 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -129,12 +129,6 @@ public: /// @returns false on error. bool compile(std::string const& _sourceCode, bool _optimize = false, unsigned _runs = 200); - /// Tries to translate all source files into a language suitable for formal analysis. - /// @param _errors list to store errors - defaults to the internal error list. - /// @returns false on error. - bool prepareFormalAnalysis(ErrorReporter* _errorReporter = nullptr); - std::string const& formalTranslation() const { return m_formalTranslation; } - /// @returns the assembled object for a contract. eth::LinkerObject const& object(std::string const& _contractName = "") const; /// @returns the runtime object for the contract. @@ -290,7 +284,6 @@ private: std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes; std::vector<Source const*> m_sourceOrder; std::map<std::string const, Contract> m_contracts; - std::string m_formalTranslation; ErrorList m_errorList; ErrorReporter m_errorReporter; bool m_metadataLiteralSources = false; diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp index 9987b82c..5657c2c0 100644 --- a/libsolidity/parsing/ParserBase.cpp +++ b/libsolidity/parsing/ParserBase.cpp @@ -43,6 +43,26 @@ int ParserBase::endPosition() const return m_scanner->currentLocation().end; } +Token::Value ParserBase::currentToken() const +{ + return m_scanner->currentToken(); +} + +Token::Value ParserBase::peekNextToken() const +{ + return m_scanner->peekNextToken(); +} + +std::string ParserBase::currentLiteral() const +{ + return m_scanner->currentLiteral(); +} + +Token::Value ParserBase::advance() +{ + return m_scanner->next(); +} + void ParserBase::expectToken(Token::Value _value) { Token::Value tok = m_scanner->currentToken(); diff --git a/libsolidity/parsing/ParserBase.h b/libsolidity/parsing/ParserBase.h index ae56cead..5b03ab5e 100644 --- a/libsolidity/parsing/ParserBase.h +++ b/libsolidity/parsing/ParserBase.h @@ -23,7 +23,6 @@ #pragma once #include <memory> -#include <libsolidity/parsing/Scanner.h> #include <libsolidity/parsing/Token.h> namespace dev @@ -51,10 +50,10 @@ protected: ///@name Helper functions /// If current token value is not _value, throw exception otherwise advance token. void expectToken(Token::Value _value); - Token::Value currentToken() const { return m_scanner->currentToken(); } - Token::Value peekNextToken() const { return m_scanner->peekNextToken(); } - std::string currentLiteral() const { return m_scanner->currentLiteral(); } - Token::Value advance() { return m_scanner->next(); } + Token::Value currentToken() const; + Token::Value peekNextToken() const; + std::string currentLiteral() const; + Token::Value advance(); ///@} /// Creates a @ref ParserError and annotates it with the current position and the |