diff options
author | chriseth <chris@ethereum.org> | 2016-09-01 17:02:50 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-01 17:02:50 +0800 |
commit | b5d941d3d9f32193c7f9094dee20511585508f6a (patch) | |
tree | 05b5156cbc8fb901102890994842412f18504d06 | |
parent | 4a26adfb7d4e962de094f4c6f02139181fac1699 (diff) | |
parent | 4abba77ddc0b4402597d13d5c29adcf5cac82e11 (diff) | |
download | dexon-solidity-b5d941d3d9f32193c7f9094dee20511585508f6a.tar.gz dexon-solidity-b5d941d3d9f32193c7f9094dee20511585508f6a.tar.zst dexon-solidity-b5d941d3d9f32193c7f9094dee20511585508f6a.zip |
Merge pull request #935 from chriseth/pragma
Version pragma
28 files changed, 906 insertions, 66 deletions
diff --git a/Changelog.md b/Changelog.md index 5cf2898b..25890890 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,9 @@ enforce some safety features. The most important change is Breaking Changes: + * Source files have to specify the compiler version they are + compatible with using e.g. `pragma solidity ^0.4.0;` or + `pragma solidity >=0.4.0 <0.4.8;` * Contracts that want to receive Ether have to implement a fallback function (contracts now throw if no fallback function is defined and no function matches the signature). diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index 6ef06961..fdb7b5e8 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -2,7 +2,36 @@ Layout of a Solidity Source File ******************************** -Source files can contain an arbitrary number of contract definitions and include directives. +Source files can contain an arbitrary number of contract definitions, include directives +and pragma directives. + +.. index:: ! pragma, version + +Version Pragma +============== + +Source files can (and should) be annotated with a so-called version pragma to reject +being compiled with future compiler versions that might introduce incompatible +changes. We try to keep such changes to an absolute minimum and especially +introduce changes in a way that changes in semantics will also require changes +in the syntax, but this is of course not always possible. Because of that, it is always +a good idea to read through the changelog at least for releases that contain +breaking changes, those releases will always have versions of the form +``0.x.0`` or ``x.0.0``. + +The version pragma is used as follows:: + + pragma solidity ^0.4.0; + +Such a source file will not compile with a compiler earlier than version 0.4.0 +and it will also not work on a compiler starting form version 0.5.0 (this +second condition is added by using ``^``). The idea behind this is that +there will be no breaking changes until version ``0.5.0``, so we can always +be sure that our code will compile the way we intended it to. We do not fix +the exact version of the compiler, so that bugfix releases are still possible. + +It is possible to specify much more complex rules for the compiler version, +the expression follows those used by npm. .. index:: source file, ! import diff --git a/libsolidity/analysis/SemVerHandler.cpp b/libsolidity/analysis/SemVerHandler.cpp new file mode 100644 index 00000000..c7b212b2 --- /dev/null +++ b/libsolidity/analysis/SemVerHandler.cpp @@ -0,0 +1,290 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <chris@ethereum.org> + * @date 2016 + * Utilities to handle semantic versioning. + */ + +#include <libsolidity/analysis/SemVerHandler.h> +#include <functional> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +SemVerVersion::SemVerVersion(string const& _versionString) +{ + auto i = _versionString.begin(); + auto end = _versionString.end(); + + for (unsigned level = 0; level < 3; ++level) + { + unsigned v = 0; + for (; i != end && '0' <= *i && *i <= '9'; ++i) + v = v * 10 + (*i - '0'); + numbers[level] = v; + if (level < 2) + { + if (i == end || *i != '.') + throw SemVerError(); + else + ++i; + } + } + if (i != end && *i == '-') + { + auto prereleaseStart = ++i; + while (i != end && *i != '+') ++i; + prerelease = string(prereleaseStart, i); + } + if (i != end && *i == '+') + { + auto buildStart = ++i; + while (i != end) ++i; + build = string(buildStart, i); + } + if (i != end) + throw SemVerError(); +} + +bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _version) const +{ + if (prefix == Token::BitNot) + { + MatchComponent comp = *this; + + comp.prefix = Token::GreaterThanOrEqual; + if (!comp.matches(_version)) + return false; + + if (levelsPresent >= 2) + comp.levelsPresent = 2; + else + comp.levelsPresent = 1; + comp.prefix = Token::LessThanOrEqual; + return comp.matches(_version); + } + else if (prefix == Token::BitXor) + { + MatchComponent comp = *this; + + comp.prefix = Token::GreaterThanOrEqual; + if (!comp.matches(_version)) + return false; + + if (comp.version.numbers[0] == 0) + comp.levelsPresent = 2; + else + comp.levelsPresent = 1; + comp.prefix = Token::LessThanOrEqual; + return comp.matches(_version); + } + else + { + int cmp = 0; + bool didCompare = false; + for (unsigned i = 0; i < levelsPresent && cmp == 0; i++) + if (version.numbers[i] != unsigned(-1)) + { + didCompare = true; + cmp = _version.numbers[i] - version.numbers[i]; + } + if (cmp == 0 && !_version.prerelease.empty() && didCompare) + cmp = -1; + if (prefix == Token::Assign) + return cmp == 0; + else if (prefix == Token::LessThan) + return cmp < 0; + else if (prefix == Token::LessThanOrEqual) + return cmp <= 0; + else if (prefix == Token::GreaterThan) + return cmp > 0; + else if (prefix == Token::GreaterThanOrEqual) + return cmp >= 0; + else + solAssert(false, "Invalid SemVer expression"); + return false; + } +} + +bool SemVerMatchExpression::Conjunction::matches(SemVerVersion const& _version) const +{ + for (auto const& component: components) + if (!component.matches(_version)) + return false; + return true; +} + +bool SemVerMatchExpression::matches(SemVerVersion const& _version) const +{ + if (!isValid()) + return false; + for (auto const& range: m_disjunction) + if (range.matches(_version)) + return true; + return false; +} + +SemVerMatchExpression SemVerMatchExpressionParser::parse() +{ + reset(); + + try + { + while (true) + { + parseMatchExpression(); + if (m_pos >= m_tokens.size()) + break; + if (currentToken() != Token::Or) + throw SemVerError(); + nextToken(); + } + } + catch (SemVerError const&) + { + reset(); + } + + return m_expression; +} + + +void SemVerMatchExpressionParser::reset() +{ + m_expression = SemVerMatchExpression(); + m_pos = 0; + m_posInside = 0; +} + +void SemVerMatchExpressionParser::parseMatchExpression() +{ + // component - component (range) + // or component component* (conjunction) + + SemVerMatchExpression::Conjunction range; + range.components.push_back(parseMatchComponent()); + if (currentToken() == Token::Sub) + { + range.components[0].prefix = Token::GreaterThanOrEqual; + nextToken(); + range.components.push_back(parseMatchComponent()); + range.components[1].prefix = Token::LessThanOrEqual; + } + else + while (currentToken() != Token::Or && currentToken() != Token::Illegal) + range.components.push_back(parseMatchComponent()); + m_expression.m_disjunction.push_back(range); +} + +SemVerMatchExpression::MatchComponent SemVerMatchExpressionParser::parseMatchComponent() +{ + SemVerMatchExpression::MatchComponent component; + Token::Value token = currentToken(); + if ( + token == Token::BitXor || + token == Token::BitNot || + token == Token::LessThan || + token == Token::LessThanOrEqual|| + token == Token::GreaterThan || + token == Token::GreaterThanOrEqual || + token == Token::Assign + ) + { + component.prefix = token; + nextToken(); + } + else + component.prefix = Token::Assign; + + component.levelsPresent = 0; + while (component.levelsPresent < 3) + { + component.version.numbers[component.levelsPresent] = parseVersionPart(); + component.levelsPresent++; + if (currentChar() == '.') + nextChar(); + else + break; + } + // TODO we do not support pre and build version qualifiers for now in match expressions + // (but we do support them in the actual versions) + return component; +} + +unsigned SemVerMatchExpressionParser::parseVersionPart() +{ + auto startPos = m_pos; + char c = currentChar(); + nextChar(); + if (c == 'x' || c == 'X' || c == '*') + return unsigned(-1); + else if (c == '0') + return 0; + else if ('1' <= c && c <= '9') + { + unsigned v = c - '0'; + // If we skip to the next token, the current number is terminated. + while (m_pos == startPos && '0' <= currentChar() && currentChar() <= '9') + { + c = currentChar(); + if (v * 10 < v || v * 10 + (c - '0') < v * 10) + throw SemVerError(); + v = v * 10 + c - '0'; + nextChar(); + } + return v; + } + else + throw SemVerError(); +} + +char SemVerMatchExpressionParser::currentChar() const +{ + if (m_pos >= m_literals.size()) + return char(-1); + if (m_posInside >= m_literals[m_pos].size()) + return char(-1); + return m_literals[m_pos][m_posInside]; +} + +char SemVerMatchExpressionParser::nextChar() +{ + if (m_pos < m_literals.size()) + { + if (m_posInside + 1 >= m_literals[m_pos].size()) + nextToken(); + else + ++m_posInside; + } + return currentChar(); +} + +Token::Value SemVerMatchExpressionParser::currentToken() const +{ + if (m_pos < m_tokens.size()) + return m_tokens[m_pos]; + else + return Token::Illegal; +} + +void SemVerMatchExpressionParser::nextToken() +{ + ++m_pos; + m_posInside = 0; +} diff --git a/libsolidity/analysis/SemVerHandler.h b/libsolidity/analysis/SemVerHandler.h new file mode 100644 index 00000000..3c110b19 --- /dev/null +++ b/libsolidity/analysis/SemVerHandler.h @@ -0,0 +1,102 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <chris@ethereum.org> + * @date 2016 + * Utilities to handle semantic versioning. + */ + +#pragma once + +#include <vector> +#include <libsolidity/parsing/Token.h> + +namespace dev +{ +namespace solidity +{ + +class SemVerError: dev::Exception +{ +}; + +struct SemVerVersion +{ + unsigned numbers[3]; + std::string prerelease; + std::string build; + + explicit SemVerVersion(std::string const& _versionString = "0.0.0"); +}; + +struct SemVerMatchExpression +{ + bool matches(SemVerVersion const& _version) const; + + bool isValid() const { return !m_disjunction.empty(); } + + struct MatchComponent + { + /// Prefix from < > <= >= ~ ^ + Token::Value prefix = Token::Illegal; + /// Version, where unsigned(-1) in major, minor or patch denotes '*', 'x' or 'X' + SemVerVersion version; + /// Whether we have 1, 1.2 or 1.2.4 + unsigned levelsPresent = 1; + bool matches(SemVerVersion const& _version) const; + }; + + struct Conjunction + { + std::vector<MatchComponent> components; + bool matches(SemVerVersion const& _version) const; + }; + + std::vector<Conjunction> m_disjunction; +}; + +class SemVerMatchExpressionParser +{ +public: + SemVerMatchExpressionParser(std::vector<Token::Value> const& _tokens, std::vector<std::string> const& _literals): + m_tokens(_tokens), m_literals(_literals) + {} + SemVerMatchExpression parse(); + +private: + void reset(); + + void parseMatchExpression(); + SemVerMatchExpression::MatchComponent parseMatchComponent(); + unsigned parseVersionPart(); + + char currentChar() const; + char nextChar(); + Token::Value currentToken() const; + void nextToken(); + + std::vector<Token::Value> m_tokens; + std::vector<std::string> m_literals; + + unsigned m_pos = 0; + unsigned m_posInside = 0; + + SemVerMatchExpression m_expression; +}; + +} +} diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 593f2f69..a95b4879 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -15,9 +15,11 @@ along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. */ +#include <libsolidity/analysis/SyntaxChecker.h> #include <memory> #include <libsolidity/ast/AST.h> -#include <libsolidity/analysis/SyntaxChecker.h> +#include <libsolidity/analysis/SemVerHandler.h> +#include <libsolidity/interface/Version.h> using namespace std; using namespace dev; @@ -27,7 +29,7 @@ using namespace dev::solidity; bool SyntaxChecker::checkSyntax(SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); - return m_errors.empty(); + return Error::containsOnlyWarnings(m_errors); } void SyntaxChecker::syntaxError(SourceLocation const& _location, std::string const& _description) @@ -40,6 +42,52 @@ void SyntaxChecker::syntaxError(SourceLocation const& _location, std::string con m_errors.push_back(err); } +bool SyntaxChecker::visit(SourceUnit const&) +{ + m_versionPragmaFound = false; + return true; +} + +void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit) +{ + if (!m_versionPragmaFound) + { + auto err = make_shared<Error>(Error::Type::Warning); + *err << + errinfo_sourceLocation(_sourceUnit.location()) << + errinfo_comment( + string("Source file does not specify required compiler version! ") + + string("Consider adding \"pragma solidity ^") + VersionNumber + string(";\".") + ); + m_errors.push_back(err); + } +} + +bool SyntaxChecker::visit(PragmaDirective const& _pragma) +{ + solAssert(!_pragma.tokens().empty(), ""); + solAssert(_pragma.tokens().size() == _pragma.literals().size(), ""); + if (_pragma.tokens()[0] != Token::Identifier && _pragma.literals()[0] != "solidity") + syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\""); + else + { + vector<Token::Value> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end()); + vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end()); + SemVerMatchExpressionParser parser(tokens, literals); + auto matchExpression = parser.parse(); + SemVerVersion currentVersion{string(VersionString)}; + if (!matchExpression.matches(currentVersion)) + syntaxError( + _pragma.location(), + "Source file requires different compiler version (current compiler is " + + string(VersionString) + " - note that nightly builds are considered to be " + "strictly less than the released version" + ); + m_versionPragmaFound = true; + } + return true; +} + bool SyntaxChecker::visit(ModifierDefinition const&) { m_placeholderFound = false; @@ -91,7 +139,7 @@ bool SyntaxChecker::visit(Break const& _breakStatement) return true; } -bool SyntaxChecker::visit(const PlaceholderStatement&) +bool SyntaxChecker::visit(PlaceholderStatement const&) { m_placeholderFound = true; return true; diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 3198ffd0..ac8ed872 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -45,6 +45,10 @@ private: /// Adds a new error to the list of errors. void syntaxError(SourceLocation const& _location, std::string const& _description); + virtual bool visit(SourceUnit const& _sourceUnit) override; + virtual void endVisit(SourceUnit const& _sourceUnit) override; + virtual bool visit(PragmaDirective const& _pragma) override; + virtual bool visit(ModifierDefinition const& _modifier) override; virtual void endVisit(ModifierDefinition const& _modifier) override; @@ -63,6 +67,9 @@ private: /// Flag that indicates whether a function modifier actually contains '_'. bool m_placeholderFound = false; + /// Flag that indicates whether some version pragma was present. + bool m_versionPragmaFound = false; + int m_inLoopDepth = 0; }; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index bf275869..761d85fe 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -176,6 +176,34 @@ private: }; /** + * Pragma directive, only version requirements in the form `pragma solidity "^0.4.0";` are + * supported for now. + */ +class PragmaDirective: public ASTNode +{ +public: + PragmaDirective( + SourceLocation const& _location, + std::vector<Token::Value> const& _tokens, + std::vector<ASTString> const& _literals + ): ASTNode(_location), m_tokens(_tokens), m_literals(_literals) + {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector<Token::Value> const& tokens() const { return m_tokens; } + std::vector<ASTString> const& literals() const { return m_literals; } + +private: + + /// Sequence of tokens following the "pragma" keyword. + std::vector<Token::Value> m_tokens; + /// Sequence of literals following the "pragma" keyword. + std::vector<ASTString> m_literals; +}; + +/** * Import directive for referencing other files / source objects. * Example: import "abc.sol" // imports all symbols of "abc.sol" into current scope * Source objects are identified by a string which can be a file name but does not have to be. diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index dad2b2e2..59fc1b57 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -35,6 +35,7 @@ namespace solidity class ASTNode; class SourceUnit; +class PragmaDirective; class ImportDirective; class Declaration; class ContractDefinition; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 35fd0b7d..49ee6d34 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -103,6 +103,15 @@ bool ASTJsonConverter::visit(SourceUnit const&) return true; } +bool ASTJsonConverter::visit(PragmaDirective const& _node) +{ + Json::Value literals(Json::arrayValue); + for (auto const& literal: _node.literals()) + literals.append(literal); + addJsonNode(_node, "PragmaDirective", { make_pair("literals", literals) }); + return true; +} + bool ASTJsonConverter::visit(ImportDirective const& _node) { addJsonNode(_node, "ImportDirective", { make_pair("file", _node.path())}); @@ -401,6 +410,10 @@ void ASTJsonConverter::endVisit(SourceUnit const&) goUp(); } +void ASTJsonConverter::endVisit(PragmaDirective const&) +{ +} + void ASTJsonConverter::endVisit(ImportDirective const&) { } diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 97aa8654..7c7b37f8 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -52,6 +52,7 @@ public: Json::Value const& json(); bool visit(SourceUnit const& _node) override; + bool visit(PragmaDirective const& _node) override; bool visit(ImportDirective const& _node) override; bool visit(ContractDefinition const& _node) override; bool visit(InheritanceSpecifier const& _node) override; @@ -96,6 +97,7 @@ public: bool visit(Literal const& _node) override; void endVisit(SourceUnit const&) override; + void endVisit(PragmaDirective const&) override; void endVisit(ImportDirective const&) override; void endVisit(ContractDefinition const&) override; void endVisit(InheritanceSpecifier const&) override; diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index 9ed9c6d5..a9de457a 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -47,6 +47,13 @@ void ASTPrinter::print(ostream& _stream) } +bool ASTPrinter::visit(PragmaDirective const& _node) +{ + writeLine("PragmaDirective"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(ImportDirective const& _node) { writeLine("ImportDirective \"" + _node.path() + "\""); @@ -355,6 +362,11 @@ bool ASTPrinter::visit(Literal const& _node) return goDeeper(); } +void ASTPrinter::endVisit(PragmaDirective const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(ImportDirective const&) { m_indentation--; diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h index a2546935..f0ab1098 100644 --- a/libsolidity/ast/ASTPrinter.h +++ b/libsolidity/ast/ASTPrinter.h @@ -47,6 +47,7 @@ public: /// Output the string representation of the AST to _stream. void print(std::ostream& _stream); + bool visit(PragmaDirective const& _node) override; bool visit(ImportDirective const& _node) override; bool visit(ContractDefinition const& _node) override; bool visit(InheritanceSpecifier const& _node) override; @@ -89,6 +90,7 @@ public: bool visit(ElementaryTypeNameExpression const& _node) override; bool visit(Literal const& _node) override; + void endVisit(PragmaDirective const&) override; void endVisit(ImportDirective const&) override; void endVisit(ContractDefinition const&) override; void endVisit(InheritanceSpecifier const&) override; diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 5aac2066..3a1b55d3 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -44,6 +44,7 @@ class ASTVisitor { public: virtual bool visit(SourceUnit& _node) { return visitNode(_node); } + virtual bool visit(PragmaDirective& _node) { return visitNode(_node); } virtual bool visit(ImportDirective& _node) { return visitNode(_node); } virtual bool visit(ContractDefinition& _node) { return visitNode(_node); } virtual bool visit(InheritanceSpecifier& _node) { return visitNode(_node); } @@ -88,6 +89,7 @@ public: virtual bool visit(Literal& _node) { return visitNode(_node); } virtual void endVisit(SourceUnit& _node) { endVisitNode(_node); } + virtual void endVisit(PragmaDirective& _node) { endVisitNode(_node); } virtual void endVisit(ImportDirective& _node) { endVisitNode(_node); } virtual void endVisit(ContractDefinition& _node) { endVisitNode(_node); } virtual void endVisit(InheritanceSpecifier& _node) { endVisitNode(_node); } @@ -144,6 +146,7 @@ class ASTConstVisitor { public: virtual bool visit(SourceUnit const& _node) { return visitNode(_node); } + virtual bool visit(PragmaDirective const& _node) { return visitNode(_node); } virtual bool visit(ImportDirective const& _node) { return visitNode(_node); } virtual bool visit(ContractDefinition const& _node) { return visitNode(_node); } virtual bool visit(InheritanceSpecifier const& _node) { return visitNode(_node); } @@ -188,6 +191,7 @@ public: virtual bool visit(Literal const& _node) { return visitNode(_node); } virtual void endVisit(SourceUnit const& _node) { endVisitNode(_node); } + virtual void endVisit(PragmaDirective const& _node) { endVisitNode(_node); } virtual void endVisit(ImportDirective const& _node) { endVisitNode(_node); } virtual void endVisit(ContractDefinition const& _node) { endVisitNode(_node); } virtual void endVisit(InheritanceSpecifier const& _node) { endVisitNode(_node); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index dd2a7d60..b5a3806b 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -45,6 +45,18 @@ void SourceUnit::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void PragmaDirective::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void PragmaDirective::accept(ASTConstVisitor& _visitor) const +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + void ImportDirective::accept(ASTVisitor& _visitor) { _visitor.visit(*this); diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 0d1f68a6..0230729a 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -1,13 +1,16 @@ -SourceUnit = (ImportDirective | ContractDefinition)* +SourceUnit = (PragmaDirective | ImportDirective | ContractDefinition)* -ContractDefinition = ( 'contract' | 'library' ) Identifier - ( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )? - '{' ContractPart* '}' +// Pragma actually parses anything up to the trailing ';' to be fully forward-compatible. +PragmaDirective = 'pragma' Identifier Expression ';' ImportDirective = 'import' StringLiteral ('as' Identifier)? ';' | 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';' | 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';' +ContractDefinition = ( 'contract' | 'library' ) Identifier + ( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )? + '{' ContractPart* '}' + ContractPart = StateVariableDeclaration | UsingForDeclaration | StructDefinition | ModifierDefinition | FunctionDefinition | EventDefinition | EnumDefinition diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index b8f72238..b2f4a156 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -76,6 +76,9 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) { switch (auto token = m_scanner->currentToken()) { + case Token::Pragma: + nodes.push_back(parsePragmaDirective()); + break; case Token::Import: nodes.push_back(parseImportDirective()); break; @@ -97,6 +100,36 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) } } +ASTPointer<PragmaDirective> Parser::parsePragmaDirective() +{ + // pragma anything* ; + // Currently supported: + // pragma solidity ^0.4.0 || ^0.3.0; + ASTNodeFactory nodeFactory(*this); + expectToken(Token::Pragma); + vector<string> literals; + vector<Token::Value> tokens; + do + { + Token::Value token = m_scanner->currentToken(); + if (token == Token::Illegal) + parserError("Token incompatible with Solidity parser as part of pragma directive."); + else + { + string literal = m_scanner->currentLiteral(); + if (literal.empty() && Token::toString(token)) + literal = Token::toString(token); + literals.push_back(literal); + tokens.push_back(token); + } + m_scanner->next(); + } + while (m_scanner->currentToken() != Token::Semicolon && m_scanner->currentToken() != Token::EOS); + nodeFactory.markEndPosition(); + expectToken(Token::Semicolon); + return nodeFactory.createNode<PragmaDirective>(tokens, literals); +} + ASTPointer<ImportDirective> Parser::parseImportDirective() { // import "abc" [as x]; diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index d776c3fd..9c30cf60 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -55,6 +55,7 @@ private: ///@{ ///@name Parsing functions for the AST nodes + ASTPointer<PragmaDirective> parsePragmaDirective(); ASTPointer<ImportDirective> parseImportDirective(); ASTPointer<ContractDefinition> parseContractDefinition(bool _isLibrary); ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index 007baef4..15d4860f 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -167,6 +167,7 @@ namespace solidity K(Modifier, "modifier", 0) \ K(New, "new", 0) \ K(Public, "public", 0) \ + K(Pragma, "pragma", 0) \ K(Private, "private", 0) \ K(Return, "return", 0) \ K(Returns, "returns", 0) \ diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index 6eba2706..681caa26 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -39,7 +39,7 @@ namespace { static char const* registrarCode = R"DELIMITER( -//sol +pragma solidity ^0.3.5; contract NameRegister { function addr(string _name) constant returns (address o_owner); diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp index 3acfba62..fd0861f7 100644 --- a/test/contracts/FixedFeeRegistrar.cpp +++ b/test/contracts/FixedFeeRegistrar.cpp @@ -52,6 +52,8 @@ static char const* registrarCode = R"DELIMITER( // @authors: // Gav Wood <g@ethdev.com> +pragma solidity ^0.3.5; + contract Registrar { event Changed(string indexed name); diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index 9e797af4..55c2e1af 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -54,6 +54,9 @@ static char const* walletCode = R"DELIMITER( // use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by // some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the // interior is executed. + +pragma solidity ^0.3.5; + contract multiowned { // TYPES diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 41204a0a..1f216680 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -46,7 +46,7 @@ public: GasMeterTestFramework() { } void compile(string const& _sourceCode) { - m_compiler.setSource(_sourceCode); + m_compiler.setSource("pragma solidity >= 0.0;" + _sourceCode); ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed"); AssemblyItems const* items = m_compiler.runtimeAssemblyItems(""); diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index 0736a853..1a9e16cc 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -39,106 +39,106 @@ BOOST_AUTO_TEST_SUITE(SolidityImports) BOOST_AUTO_TEST_CASE(smoke_test) { CompilerStack c; - c.addSource("a", "contract C {}"); + c.addSource("a", "contract C {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(regular_import) { CompilerStack c; - c.addSource("a", "contract C {}"); - c.addSource("b", "import \"a\"; contract D is C {}"); + c.addSource("a", "contract C {} pragma solidity >=0.0;"); + c.addSource("b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(import_does_not_clutter_importee) { CompilerStack c; - c.addSource("a", "contract C { D d; }"); - c.addSource("b", "import \"a\"; contract D is C {}"); + c.addSource("a", "contract C { D d; } pragma solidity >=0.0;"); + c.addSource("b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"); BOOST_CHECK(!c.compile()); } BOOST_AUTO_TEST_CASE(import_is_transitive) { CompilerStack c; - c.addSource("a", "contract C { }"); - c.addSource("b", "import \"a\";"); - c.addSource("c", "import \"b\"; contract D is C {}"); + c.addSource("a", "contract C { } pragma solidity >=0.0;"); + c.addSource("b", "import \"a\"; pragma solidity >=0.0;"); + c.addSource("c", "import \"b\"; contract D is C {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(circular_import) { CompilerStack c; - c.addSource("a", "import \"b\"; contract C { D d; }"); - c.addSource("b", "import \"a\"; contract D { C c; }"); + c.addSource("a", "import \"b\"; contract C { D d; } pragma solidity >=0.0;"); + c.addSource("b", "import \"a\"; contract D { C c; } pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(relative_import) { CompilerStack c; - c.addSource("a", "import \"./dir/b\"; contract A is B {}"); - c.addSource("dir/b", "contract B {}"); - c.addSource("dir/c", "import \"../a\"; contract C is A {}"); + c.addSource("a", "import \"./dir/b\"; contract A is B {} pragma solidity >=0.0;"); + c.addSource("dir/b", "contract B {} pragma solidity >=0.0;"); + c.addSource("dir/c", "import \"../a\"; contract C is A {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(relative_import_multiplex) { CompilerStack c; - c.addSource("a", "contract A {}"); - c.addSource("dir/a/b/c", "import \"../../.././a\"; contract B is A {}"); + c.addSource("a", "contract A {} pragma solidity >=0.0;"); + c.addSource("dir/a/b/c", "import \"../../.././a\"; contract B is A {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(simple_alias) { CompilerStack c; - c.addSource("a", "contract A {}"); - c.addSource("dir/a/b/c", "import \"../../.././a\" as x; contract B is x.A { function() { x.A r = x.A(20); } }"); + c.addSource("a", "contract A {} pragma solidity >=0.0;"); + c.addSource("dir/a/b/c", "import \"../../.././a\" as x; contract B is x.A { function() { x.A r = x.A(20); } } pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(library_name_clash) { CompilerStack c; - c.addSource("a", "library A {}"); - c.addSource("b", "library A {}"); + c.addSource("a", "library A {} pragma solidity >=0.0;"); + c.addSource("b", "library A {} pragma solidity >=0.0;"); BOOST_CHECK(!c.compile()); } BOOST_AUTO_TEST_CASE(library_name_clash_with_contract) { CompilerStack c; - c.addSource("a", "contract A {}"); - c.addSource("b", "library A {}"); + c.addSource("a", "contract A {} pragma solidity >=0.0;"); + c.addSource("b", "library A {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(complex_import) { CompilerStack c; - c.addSource("a", "contract A {} contract B {} contract C { struct S { uint a; } }"); + c.addSource("a", "contract A {} contract B {} contract C { struct S { uint a; } } pragma solidity >=0.0;"); c.addSource("b", "import \"a\" as x; import {B as b, C as c, C} from \"a\"; " - "contract D is b { function f(c.S var1, x.C.S var2, C.S var3) internal {} }"); + "contract D is b { function f(c.S var1, x.C.S var2, C.S var3) internal {} } pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } BOOST_AUTO_TEST_CASE(name_clash_in_import) { CompilerStack c; - c.addSource("a", "contract A {}"); - c.addSource("b", "import \"a\"; contract A {} "); + c.addSource("a", "contract A {} pragma solidity >=0.0;"); + c.addSource("b", "import \"a\"; contract A {} pragma solidity >=0.0;"); BOOST_CHECK(!c.compile()); - c.addSource("b", "import \"a\" as A; contract A {} "); + c.addSource("b", "import \"a\" as A; contract A {} pragma solidity >=0.0;"); BOOST_CHECK(!c.compile()); - c.addSource("b", "import {A as b} from \"a\"; contract b {} "); + c.addSource("b", "import {A as b} from \"a\"; contract b {} pragma solidity >=0.0;"); BOOST_CHECK(!c.compile()); - c.addSource("b", "import {A} from \"a\"; contract A {} "); + c.addSource("b", "import {A} from \"a\"; contract A {} pragma solidity >=0.0;"); BOOST_CHECK(!c.compile()); - c.addSource("b", "import {A} from \"a\"; contract B {} "); + c.addSource("b", "import {A} from \"a\"; contract B {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } @@ -146,10 +146,10 @@ BOOST_AUTO_TEST_CASE(remappings) { CompilerStack c; c.setRemappings(vector<string>{"s=s_1.4.6", "t=Tee"}); - c.addSource("a", "import \"s/s.sol\"; contract A is S {}"); - c.addSource("b", "import \"t/tee.sol\"; contract A is Tee {} "); - c.addSource("s_1.4.6/s.sol", "contract S {}"); - c.addSource("Tee/tee.sol", "contract Tee {}"); + c.addSource("a", "import \"s/s.sol\"; contract A is S {} pragma solidity >=0.0;"); + c.addSource("b", "import \"t/tee.sol\"; contract A is Tee {} pragma solidity >=0.0;"); + c.addSource("s_1.4.6/s.sol", "contract S {} pragma solidity >=0.0;"); + c.addSource("Tee/tee.sol", "contract Tee {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } @@ -157,10 +157,10 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings) { CompilerStack c; c.setRemappings(vector<string>{"a:s=s_1.4.6", "b:s=s_1.4.7"}); - c.addSource("a/a.sol", "import \"s/s.sol\"; contract A is SSix {}"); - c.addSource("b/b.sol", "import \"s/s.sol\"; contract B is SSeven {}"); - c.addSource("s_1.4.6/s.sol", "contract SSix {} "); - c.addSource("s_1.4.7/s.sol", "contract SSeven {} "); + c.addSource("a/a.sol", "import \"s/s.sol\"; contract A is SSix {} pragma solidity >=0.0;"); + c.addSource("b/b.sol", "import \"s/s.sol\"; contract B is SSeven {} pragma solidity >=0.0;"); + c.addSource("s_1.4.6/s.sol", "contract SSix {} pragma solidity >=0.0;"); + c.addSource("s_1.4.7/s.sol", "contract SSeven {} pragma solidity >=0.0;"); BOOST_CHECK(c.compile()); } diff --git a/test/libsolidity/SemVerMatcher.cpp b/test/libsolidity/SemVerMatcher.cpp new file mode 100644 index 00000000..80bdf16f --- /dev/null +++ b/test/libsolidity/SemVerMatcher.cpp @@ -0,0 +1,223 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <chris@ethereum.org> + * @date 2016 + * Unit tests for the semantic versioning matcher. + */ + +#include <string> +#include <vector> +#include <tuple> +#include <libsolidity/parsing/Scanner.h> +#include <libsolidity/analysis/SemVerHandler.h> +#include "../TestHelper.h" + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(SemVerMatcher) + +SemVerMatchExpression parseExpression(string const& _input) +{ + Scanner scanner{CharStream(_input)}; + vector<string> literals; + vector<Token::Value> tokens; + while (scanner.currentToken() != Token::EOS) + { + auto token = scanner.currentToken(); + string literal = scanner.currentLiteral(); + if (literal.empty() && Token::toString(token)) + literal = Token::toString(token); + literals.push_back(literal); + tokens.push_back(token); + scanner.next(); + } + + auto expression = SemVerMatchExpressionParser(tokens, literals).parse(); + BOOST_CHECK_MESSAGE( + expression.isValid(), + "Expression \"" + _input + "\" did not parse properly." + ); + return expression; +} + +BOOST_AUTO_TEST_CASE(positive_range) +{ + // Positive range tests + vector<pair<string, string>> tests = { + {"*", "1.2.3-foo"}, + {"1.0.0 - 2.0.0", "1.2.3"}, + {"1.0.0", "1.0.0"}, + {">=*", "0.2.4"}, + {"*", "1.2.3"}, + {">=1.0.0", "1.0.0"}, + {">=1.0.0", "1.0.1"}, + {">=1.0.0", "1.1.0"}, + {">1.0.0", "1.0.1"}, + {">1.0.0", "1.1.0"}, + {"<=2.0.0", "2.0.0"}, + {"<=2.0.0", "1.9999.9999"}, + {"<=2.0.0", "0.2.9"}, + {"<2.0.0", "1.9999.9999"}, + {"<2.0.0", "0.2.9"}, + {">= 1.0.0", "1.0.0"}, + {">= 1.0.0", "1.0.1"}, + {">= 1.0.0", "1.1.0"}, + {"> 1.0.0", "1.0.1"}, + {"> 1.0.0", "1.1.0"}, + {"<= 2.0.0", "2.0.0"}, + {"<= 2.0.0", "1.9999.9999"}, + {"<= 2.0.0", "0.2.9"}, + {"< 2.0.0", "1.9999.9999"}, + {"<\t2.0.0", "0.2.9"}, + {">=0.1.97", "0.1.97"}, + {"0.1.20 || 1.2.4", "1.2.4"}, + {">=0.2.3 || <0.0.1", "0.0.0"}, + {">=0.2.3 || <0.0.1", "0.2.3"}, + {">=0.2.3 || <0.0.1", "0.2.4"}, + {"\"2.x.x\"", "2.1.3"}, + {"1.2.x", "1.2.3"}, + {"\"1.2.x\" || \"2.x\"", "2.1.3"}, + {"\"1.2.x\" || \"2.x\"", "1.2.3"}, + {"x", "1.2.3"}, + {"2.*.*", "2.1.3"}, + {"1.2.*", "1.2.3"}, + {"1.2.* || 2.*", "2.1.3"}, + {"1.2.* || 2.*", "1.2.3"}, + {"*", "1.2.3"}, + {"2", "2.1.2"}, + {"2.3", "2.3.1"}, + {"~2.4", "2.4.0"}, // >=2.4.0 <2.5.0 + {"~2.4", "2.4.5"}, + {"~1", "1.2.3"}, // >=1.0.0 <2.0.0 + {"~1.0", "1.0.2"}, // >=1.0.0 <1.1.0, + {"~ 1.0", "1.0.2"}, + {"~ 1.0.3", "1.0.12"}, + {">=1", "1.0.0"}, + {">= 1", "1.0.0"}, + {"<1.2", "1.1.1"}, + {"< 1.2", "1.1.1"}, + {"=0.7.x", "0.7.2"}, + {"<=0.7.x", "0.7.2"}, + {">=0.7.x", "0.7.2"}, + {"<=0.7.x", "0.6.2"}, + {"~1.2.1 >=1.2.3", "1.2.3"}, + {"~1.2.1 =1.2.3", "1.2.3"}, + {"~1.2.1 1.2.3", "1.2.3"}, + {"~1.2.1 >=1.2.3 1.2.3", "1.2.3"}, + {"~1.2.1 1.2.3 >=1.2.3", "1.2.3"}, + {">=\"1.2.1\" 1.2.3", "1.2.3"}, + {"1.2.3 >=1.2.1", "1.2.3"}, + {">=1.2.3 >=1.2.1", "1.2.3"}, + {">=1.2.1 >=1.2.3", "1.2.3"}, + {">=1.2", "1.2.8"}, + {"^1.2.3", "1.8.1"}, + {"^0.1.2", "0.1.2"}, + {"^0.1", "0.1.2"}, + {"^1.2", "1.4.2"}, + {"<=1.2.3", "1.2.3-beta"}, + {">1.2", "1.3.0-beta"}, + {"<1.2.3", "1.2.3-beta"}, + {"^1.2 ^1", "1.4.2"} + }; + for (auto const& t: tests) + { + SemVerVersion version(t.second); + SemVerMatchExpression expression = parseExpression(t.first); + BOOST_CHECK_MESSAGE( + expression.matches(version), + "Version \"" + t.second + "\" did not satisfy expression \"" + t.first + "\"" + ); + } +} + +BOOST_AUTO_TEST_CASE(negative_range) +{ + // Positive range tests + vector<pair<string, string>> tests = { + {"1.0.0 - 2.0.0", "2.2.3"}, + {"^1.2.3", "1.2.3-pre"}, + {"^1.2", "1.2.0-pre"}, + {"^1.2.3", "1.2.3-beta"}, + {"=0.7.x", "0.7.0-asdf"}, + {">=0.7.x", "0.7.0-asdf"}, + {"1.0.0", "1.0.1"}, + {">=1.0.0", "0.0.0"}, + {">=1.0.0", "0.0.1"}, + {">=1.0.0", "0.1.0"}, + {">1.0.0", "0.0.1"}, + {">1.0.0", "0.1.0"}, + {"<=2.0.0", "3.0.0"}, + {"<=2.0.0", "2.9999.9999"}, + {"<=2.0.0", "2.2.9"}, + {"<2.0.0", "2.9999.9999"}, + {"<2.0.0", "2.2.9"}, + {">=0.1.97", "0.1.93"}, + {"0.1.20 || 1.2.4", "1.2.3"}, + {">=0.2.3 || <0.0.1", "0.0.3"}, + {">=0.2.3 || <0.0.1", "0.2.2"}, + {"\"2.x.x\"", "1.1.3"}, + {"\"2.x.x\"", "3.1.3"}, + {"1.2.x", "1.3.3"}, + {"\"1.2.x\" || \"2.x\"", "3.1.3"}, + {"\"1.2.x\" || \"2.x\"", "1.1.3"}, + {"2.*.*", "1.1.3"}, + {"2.*.*", "3.1.3"}, + {"1.2.*", "1.3.3"}, + {"1.2.* || 2.*", "3.1.3"}, + {"1.2.* || 2.*", "1.1.3"}, + {"2", "1.1.2"}, + {"2.3", "2.4.1"}, + {"~2.4", "2.5.0"}, // >=2.4.0 <2.5.0 + {"~2.4", "2.3.9"}, + {"~1", "0.2.3"}, // >=1.0.0 <2.0.0 + {"~1.0", "1.1.0"}, // >=1.0.0 <1.1.0 + {"<1", "1.0.0"}, + {">=1.2", "1.1.1"}, + {"=0.7.x", "0.8.2"}, + {">=0.7.x", "0.6.2"}, + {"<0.7.x", "0.7.2"}, + {"=1.2.3", "1.2.3-beta"}, + {">1.2", "1.2.8"}, + {"^1.2.3", "2.0.0-alpha"}, + {"^1.2.3", "1.2.2"}, + {"^1.2", "1.1.9"} + }; + for (auto const& t: tests) + { + SemVerVersion version(t.second); + SemVerMatchExpression expression = parseExpression(t.first); + BOOST_CHECK_MESSAGE( + !expression.matches(version), + "Version \"" + t.second + "\" did satisfy expression \"" + t.first + "\" " + + "(although it should not)" + ); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index cfc7b9bd..ec621104 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -39,7 +39,7 @@ public: void checkInterface(std::string const& _code, std::string const& _expectedInterfaceString) { - ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse(_code), "Parsing contract failed"); + ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing contract failed"); std::string generatedInterfaceString = m_compilerStack.metadata("", DocumentationType::ABIInterface); Json::Value generatedInterface; m_reader.parse(generatedInterfaceString, generatedInterface); diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index f4bdc657..6cf7e0ee 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -67,8 +67,10 @@ public: std::map<std::string, Address> const& _libraryAddresses = std::map<std::string, Address>() ) { + // Silence compiler version warning + std::string sourceCode = "pragma solidity >=0.0;\n" + _sourceCode; m_compiler.reset(false); - m_compiler.addSource("", _sourceCode); + m_compiler.addSource("", sourceCode); if (!m_compiler.compile(m_optimize, m_optimizeRuns)) { for (auto const& error: m_compiler.errors()) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index fdd8d7f4..2c126b65 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -45,15 +45,17 @@ namespace { pair<ASTPointer<SourceUnit>, std::shared_ptr<Error::Type const>> -parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false) +parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false, bool _insertVersionPragma = true) { + // Silence compiler version warning + string source = _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source; ErrorList errors; Parser parser(errors); ASTPointer<SourceUnit> sourceUnit; // catch exceptions for a transition period try { - sourceUnit = parser.parse(std::make_shared<Scanner>(CharStream(_source))); + sourceUnit = parser.parse(std::make_shared<Scanner>(CharStream(source))); if(!sourceUnit) return make_pair(sourceUnit, nullptr); @@ -445,8 +447,8 @@ BOOST_AUTO_TEST_CASE(function_no_implementation) "}\n"; ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); - ContractDefinition* contract = dynamic_cast<ContractDefinition*>(nodes[0].get()); - BOOST_CHECK(contract); + ContractDefinition* contract = dynamic_cast<ContractDefinition*>(nodes[1].get()); + BOOST_REQUIRE(contract); BOOST_CHECK(!contract->annotation().isFullyImplemented); BOOST_CHECK(!contract->definedFunctions()[0]->isImplemented()); } @@ -460,12 +462,12 @@ BOOST_AUTO_TEST_CASE(abstract_contract) )"; ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); - ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[0].get()); - ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[1].get()); - BOOST_CHECK(base); + ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get()); + ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get()); + BOOST_REQUIRE(base); BOOST_CHECK(!base->annotation().isFullyImplemented); BOOST_CHECK(!base->definedFunctions()[0]->isImplemented()); - BOOST_CHECK(derived); + BOOST_REQUIRE(derived); BOOST_CHECK(derived->annotation().isFullyImplemented); BOOST_CHECK(derived->definedFunctions()[0]->isImplemented()); } @@ -479,8 +481,8 @@ BOOST_AUTO_TEST_CASE(abstract_contract_with_overload) )"; ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); - ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[0].get()); - ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[1].get()); + ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get()); + ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get()); BOOST_REQUIRE(base); BOOST_CHECK(!base->annotation().isFullyImplemented); BOOST_REQUIRE(derived); @@ -527,9 +529,9 @@ BOOST_AUTO_TEST_CASE(abstract_contract_constructor_args_not_provided) )"; ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed"); std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); - BOOST_CHECK_EQUAL(nodes.size(), 3); - ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get()); - BOOST_CHECK(derived); + BOOST_CHECK_EQUAL(nodes.size(), 4); + ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[3].get()); + BOOST_REQUIRE(derived); BOOST_CHECK(!derived->annotation().isFullyImplemented); } @@ -553,9 +555,9 @@ BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor) )"; ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed"); std::vector<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); - BOOST_CHECK_EQUAL(nodes.size(), 2); - ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[1].get()); - BOOST_CHECK(derived); + BOOST_CHECK_EQUAL(nodes.size(), 3); + ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get()); + BOOST_REQUIRE(derived); BOOST_CHECK(!derived->annotation().isFullyImplemented); } @@ -3853,6 +3855,23 @@ BOOST_AUTO_TEST_CASE(modifier_without_underscore) BOOST_CHECK(expectError(text, true) == Error::Type::SyntaxError); } +BOOST_AUTO_TEST_CASE(warn_nonpresent_pragma) +{ + char const* text = "contract C {}"; + auto sourceAndError = parseAnalyseAndReturnError(text, true, false); + BOOST_REQUIRE(!!sourceAndError.second); + BOOST_REQUIRE(!!sourceAndError.first); + BOOST_CHECK(*sourceAndError.second == Error::Type::Warning); +} + +BOOST_AUTO_TEST_CASE(unsatisfied_version) +{ + char const* text = R"( + pragma solidity ^99.99.0; + )"; + BOOST_CHECK(expectError(text, true) == Error::Type::SyntaxError); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index 56572b43..1f74e928 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -46,7 +46,7 @@ public: ) { std::string generatedDocumentationString; - ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse(_code), "Parsing failed"); + ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing failed"); if (_userDocumentation) generatedDocumentationString = m_compilerStack.metadata("", DocumentationType::NatspecUser); |