Version pragma.

This commit is contained in:
chriseth 2016-08-19 19:57:21 +02:00
parent 52d9f89712
commit 3c412ed2f6
27 changed files with 902 additions and 66 deletions

View File

@ -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 at 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

View File

@ -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;
}

View File

@ -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;
};
}
}

View File

@ -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,51 @@ 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(VersionNumber)};
if (!matchExpression.matches(currentVersion))
syntaxError(
_pragma.location(),
"Source file requires different compiler version (current compiler is " +
string(VersionNumber) + ")."
);
m_versionPragmaFound = true;
}
return true;
}
bool SyntaxChecker::visit(ModifierDefinition const&)
{
m_placeholderFound = false;
@ -91,7 +138,7 @@ bool SyntaxChecker::visit(Break const& _breakStatement)
return true;
}
bool SyntaxChecker::visit(const PlaceholderStatement&)
bool SyntaxChecker::visit(PlaceholderStatement const&)
{
m_placeholderFound = true;
return true;

View File

@ -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;
};

View File

@ -175,6 +175,34 @@ private:
ASTNode const* m_scope;
};
/**
* 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

View File

@ -35,6 +35,7 @@ namespace solidity
class ASTNode;
class SourceUnit;
class PragmaDirective;
class ImportDirective;
class Declaration;
class ContractDefinition;

View File

@ -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&)
{
}

View File

@ -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;

View File

@ -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--;

View File

@ -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;

View File

@ -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); }

View File

@ -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);

View File

@ -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

View File

@ -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];

View File

@ -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();

View File

@ -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) \

View File

@ -39,7 +39,7 @@ namespace
{
static char const* registrarCode = R"DELIMITER(
//sol
pragma solidity ^0.3.6;
contract NameRegister {
function addr(string _name) constant returns (address o_owner);

View File

@ -52,6 +52,8 @@ static char const* registrarCode = R"DELIMITER(
// @authors:
// Gav Wood <g@ethdev.com>
pragma solidity ^0.3.6;
contract Registrar {
event Changed(string indexed name);

View File

@ -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.6;
contract multiowned {
// TYPES

View File

@ -46,7 +46,7 @@ public:
GasMeterTestFramework() { }
void compile(string const& _sourceCode)
{
m_compiler.setSource(_sourceCode);
m_compiler.setSource("pragma solidity >= 0;" + _sourceCode);
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed");
AssemblyItems const* items = m_compiler.runtimeAssemblyItems("");

View File

@ -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;");
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;");
c.addSource("b", "import \"a\"; contract D is C {} pragma solidity >= 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;");
c.addSource("b", "import \"a\"; contract D is C {} pragma solidity >= 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;");
c.addSource("b", "import \"a\"; pragma solidity >= 0;");
c.addSource("c", "import \"b\"; contract D is C {} pragma solidity >= 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;");
c.addSource("b", "import \"a\"; contract D { C c; } pragma solidity >= 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;");
c.addSource("dir/b", "contract B {} pragma solidity >= 0;");
c.addSource("dir/c", "import \"../a\"; contract C is A {} pragma solidity >= 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;");
c.addSource("dir/a/b/c", "import \"../../.././a\"; contract B is A {} pragma solidity >= 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;");
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;");
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;");
c.addSource("b", "library A {} pragma solidity >= 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;");
c.addSource("b", "library A {} pragma solidity >= 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;");
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;");
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;");
c.addSource("b", "import \"a\"; contract A {} pragma solidity >= 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;");
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;");
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;");
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;");
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;");
c.addSource("b", "import \"t/tee.sol\"; contract A is Tee {} pragma solidity >= 0;");
c.addSource("s_1.4.6/s.sol", "contract S {} pragma solidity >= 0;");
c.addSource("Tee/tee.sol", "contract Tee {} pragma solidity >= 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;");
c.addSource("b/b.sol", "import \"s/s.sol\"; contract B is SSeven {} pragma solidity >= 0;");
c.addSource("s_1.4.6/s.sol", "contract SSix {} pragma solidity >= 0;");
c.addSource("s_1.4.7/s.sol", "contract SSeven {} pragma solidity >= 0;");
BOOST_CHECK(c.compile());
}

View File

@ -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

View File

@ -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;\n" + _code), "Parsing contract failed");
std::string generatedInterfaceString = m_compilerStack.metadata("", DocumentationType::ABIInterface);
Json::Value generatedInterface;
m_reader.parse(generatedInterfaceString, generatedInterface);

View File

@ -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;\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())

View File

@ -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;\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()
}

View File

@ -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;\n" + _code), "Parsing failed");
if (_userDocumentation)
generatedDocumentationString = m_compilerStack.metadata("", DocumentationType::NatspecUser);