Add support for SPDX license identifiers.

This commit is contained in:
chriseth 2020-05-12 11:56:28 +02:00
parent e9446475bb
commit 3872a1f000
19 changed files with 119 additions and 29 deletions

View File

@ -148,6 +148,11 @@ void ParserBase::parserWarning(ErrorId _error, string const& _description)
m_errorReporter.warning(_error, currentLocation(), _description);
}
void ParserBase::parserWarning(ErrorId _error, SourceLocation const& _location, string const& _description)
{
m_errorReporter.warning(_error, _location, _description);
}
void ParserBase::parserError(ErrorId _error, SourceLocation const& _location, string const& _description)
{
m_errorReporter.parserError(_error, _location, _description);

View File

@ -95,6 +95,7 @@ protected:
/// Creates a @ref ParserWarning and annotates it with the current position and the
/// given @a _description.
void parserWarning(ErrorId _error, std::string const& _description);
void parserWarning(ErrorId _error, SourceLocation const& _location, std::string const& _description);
/// Creates a @ref ParserError and annotates it with the current position and the
/// given @a _description. Throws the FatalError.

View File

@ -156,19 +156,26 @@ std::vector<T const*> ASTNode::filteredNodes(std::vector<ASTPointer<ASTNode>> co
class SourceUnit: public ASTNode
{
public:
SourceUnit(int64_t _id, SourceLocation const& _location, std::vector<ASTPointer<ASTNode>> _nodes):
ASTNode(_id, _location), m_nodes(std::move(_nodes)) {}
SourceUnit(
int64_t _id,
SourceLocation const& _location,
std::optional<std::string> _licenseString,
std::vector<ASTPointer<ASTNode>> _nodes
):
ASTNode(_id, _location), m_licenseString(std::move(_licenseString)), m_nodes(std::move(_nodes)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
SourceUnitAnnotation& annotation() const override;
std::optional<std::string> const& licenseString() const { return m_licenseString; }
std::vector<ASTPointer<ASTNode>> nodes() const { return m_nodes; }
/// @returns a set of referenced SourceUnits. Recursively if @a _recurse is true.
std::set<SourceUnit const*> referencedSourceUnits(bool _recurse = false, std::set<SourceUnit const*> _skipList = std::set<SourceUnit const*>()) const;
private:
std::optional<std::string> m_licenseString;
std::vector<ASTPointer<ASTNode>> m_nodes;
};

View File

@ -225,6 +225,7 @@ bool ASTJsonConverter::visit(SourceUnit const& _node)
{
make_pair("absolutePath", _node.annotation().path),
make_pair("exportedSymbols", move(exportedSymbols)),
make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue),
make_pair("nodes", toJson(_node.nodes()))
}
);

View File

@ -218,10 +218,15 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
ASTPointer<SourceUnit> ASTJsonImporter::createSourceUnit(Json::Value const& _node, string const& _srcName)
{
optional<string> license;
if (_node.isMember("license") && !_node["license"].isNull())
license = _node["license"].asString();
vector<ASTPointer<ASTNode>> nodes;
for (auto& child: member(_node, "nodes"))
nodes.emplace_back(convertJsonToASTNode(child));
ASTPointer<SourceUnit> tmp = createASTNode<SourceUnit>(_node, nodes);
ASTPointer<SourceUnit> tmp = createASTNode<SourceUnit>(_node, license, nodes);
tmp->annotation().path = _srcName;
return tmp;
}

View File

@ -1236,6 +1236,8 @@ string CompilerStack::createMetadata(Contract const& _contract) const
solAssert(s.second.scanner, "Scanner not available");
meta["sources"][s.first]["keccak256"] = "0x" + toHex(s.second.keccak256().asBytes());
if (optional<string> licenseString = s.second.ast->licenseString())
meta["sources"][s.first]["license"] = *licenseString;
if (m_metadataLiteralSources)
meta["sources"][s.first]["content"] = s.second.scanner->source();
else

View File

@ -30,8 +30,11 @@
#include <liblangutil/SemVerHandler.h>
#include <liblangutil/SourceLocation.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <cctype>
#include <vector>
#include <regex>
using namespace std;
using namespace solidity::langutil;
@ -79,6 +82,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
m_recursionDepth = 0;
m_scanner = _scanner;
ASTNodeFactory nodeFactory(*this);
vector<ASTPointer<ASTNode>> nodes;
while (m_scanner->currentToken() != Token::EOS)
{
@ -107,7 +111,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
}
}
solAssert(m_recursionDepth == 0, "");
return nodeFactory.createNode<SourceUnit>(nodes);
return nodeFactory.createNode<SourceUnit>(findLicenseString(nodes), nodes);
}
catch (FatalError const&)
{
@ -1981,6 +1985,60 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
return ret;
}
optional<string> Parser::findLicenseString(std::vector<ASTPointer<ASTNode>> const& _nodes)
{
// We circumvent the scanner here, because it skips non-docstring comments.
static regex const licenseRegex("SPDX-License-Identifier:\\s*([a-zA-Z0-9 ()+.-]+)");
// Search inside all parts of the source not covered by parsed nodes.
// This will leave e.g. "global comments".
string const& source = m_scanner->source();
using iter = decltype(source.begin());
vector<pair<iter, iter>> sequencesToSearch;
sequencesToSearch.emplace_back(source.begin(), source.end());
for (ASTPointer<ASTNode> const& node: _nodes)
if (node->location().hasText())
{
sequencesToSearch.back().second = source.begin() + node->location().start;
sequencesToSearch.emplace_back(source.begin() + node->location().end, source.end());
}
vector<string> matches;
for (auto const& [start, end]: sequencesToSearch)
{
smatch match;
if (regex_search(start, end, match, licenseRegex))
{
string license{boost::trim_copy(string(match[1]))};
if (!license.empty())
matches.emplace_back(std::move(license));
}
}
if (matches.size() == 1)
return matches.front();
else if (matches.empty())
parserWarning(
1878_error,
{-1, -1, m_scanner->charStream()},
"SPDX license identifier not provided in source file. "
"Before publishing, consider adding a comment containing "
"\"SPDX-License-Identifier: <SPDX-License>\" to each source file. "
"Use \"SPDX-License-Identifier: UNLICENSED\" for non-open-source code. "
"Please see https://spdx.org for more information."
);
else
parserError(
3716_error,
{-1, -1, m_scanner->charStream()},
"Multiple SPDX license identifiers found in source file. "
"Use \"AND\" or \"OR\" to combine multiple licenses. "
"Please see https://spdx.org for more information."
);
return {};
}
Parser::LookAheadInfo Parser::peekStatementType() const
{
// Distinguish between variable declaration (and potentially assignment) and expression statement

View File

@ -175,6 +175,8 @@ private:
bool empty() const;
};
std::optional<std::string> findLicenseString(std::vector<ASTPointer<ASTNode>> const& _nodes);
/// Returns the next AST node ID
int64_t nextID() { return ++m_currentNodeID; }

View File

@ -45,7 +45,10 @@ TestCase::TestResult ABIJsonTest::run(ostream& _stream, string const& _linePrefi
{
CompilerStack compiler;
compiler.setSources({{"", "pragma solidity >=0.0;\n" + m_source}});
compiler.setSources({{
"",
"pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n" + m_source
}});
compiler.setEVMVersion(solidity::test::CommonOptions::get().evmVersion());
compiler.setOptimiserSettings(solidity::test::CommonOptions::get().optimize);
if (!compiler.parseAndAnalyze())

View File

@ -44,13 +44,17 @@ pair<SourceUnit const*, ErrorList>
AnalysisFramework::parseAnalyseAndReturnError(
string const& _source,
bool _reportWarnings,
bool _insertVersionPragma,
bool _insertLicenseAndVersionPragma,
bool _allowMultipleErrors,
bool _allowRecoveryErrors
)
{
compiler().reset();
compiler().setSources({{"", _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source}});
compiler().setSources({{"",
_insertLicenseAndVersionPragma ?
"pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n" + _source :
_source
}});
compiler().setEVMVersion(solidity::test::CommonOptions::get().evmVersion());
compiler().setParserErrorRecovery(_allowRecoveryErrors);
_allowMultipleErrors = _allowMultipleErrors || _allowRecoveryErrors;

View File

@ -47,7 +47,7 @@ protected:
parseAnalyseAndReturnError(
std::string const& _source,
bool _reportWarnings = false,
bool _insertVersionPragma = true,
bool _insertLicenseAndVersionPragma = true,
bool _allowMultipleErrors = false,
bool _allowRecoveryErrors = false
);

View File

@ -43,7 +43,8 @@ public:
void compile(string const& _sourceCode)
{
m_compiler.reset();
m_compiler.setSources({{"", "pragma solidity >=0.0;\n" + _sourceCode}});
m_compiler.setSources({{"", "pragma solidity >=0.0;\n"
"// SPDX-License-Identifier: GPL-3.0\n" + _sourceCode}});
m_compiler.setOptimiserSettings(solidity::test::CommonOptions::get().optimize);
m_compiler.setEVMVersion(m_evmVersion);
BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed");

View File

@ -100,7 +100,7 @@ void GasTest::printUpdatedExpectations(ostream& _stream, string const& _linePref
TestCase::TestResult GasTest::run(ostream& _stream, string const& _linePrefix, bool _formatted)
{
string const versionPragma = "pragma solidity >=0.0;\n";
string const preamble = "pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n";
compiler().reset();
// Prerelease CBOR metadata varies in size due to changing version numbers and build dates.
// This leads to volatile creation cost estimates. Therefore we force the compiler to
@ -114,7 +114,7 @@ TestCase::TestResult GasTest::run(ostream& _stream, string const& _linePrefix, b
}
settings.expectedExecutionsPerDeployment = m_optimiseRuns;
compiler().setOptimiserSettings(settings);
compiler().setSources({{"", versionPragma + m_source}});
compiler().setSources({{"", preamble + m_source}});
if (!compiler().parseAndAnalyze() || !compiler().compile())
{

View File

@ -38,7 +38,7 @@ protected:
parseAnalyseAndReturnError(
std::string const& _source,
bool _reportWarnings = false,
bool _insertVersionPragma = true,
bool _insertLicenseAndVersionPragma = true,
bool _allowMultipleErrors = false,
bool _allowRecoveryErrors = false
) override
@ -46,7 +46,7 @@ protected:
return AnalysisFramework::parseAnalyseAndReturnError(
"pragma experimental SMTChecker;\n" + _source,
_reportWarnings,
_insertVersionPragma,
_insertLicenseAndVersionPragma,
_allowMultipleErrors,
_allowRecoveryErrors
);

View File

@ -64,8 +64,8 @@ TestCase::TestResult SMTCheckerJSONTest::run(ostream& _stream, string const& _li
StandardCompiler compiler;
// Run the compiler and retrieve the smtlib2queries (1st run)
string versionPragma = "pragma solidity >=0.0;\n";
Json::Value input = buildJson(versionPragma);
string preamble = "pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n";
Json::Value input = buildJson(preamble);
Json::Value result = compiler.compile(input);
// This is the list of query hashes requested by the 1st run
@ -121,10 +121,10 @@ TestCase::TestResult SMTCheckerJSONTest::run(ostream& _stream, string const& _li
std::string sourceName;
if (location.isMember("source") && location["source"].isString())
sourceName = location["source"].asString();
if (start >= static_cast<int>(versionPragma.size()))
start -= versionPragma.size();
if (end >= static_cast<int>(versionPragma.size()))
end -= versionPragma.size();
if (start >= static_cast<int>(preamble.size()))
start -= preamble.size();
if (end >= static_cast<int>(preamble.size()))
end -= preamble.size();
m_errorList.emplace_back(SyntaxTestError{
error["type"].asString(),
error["message"].asString(),

View File

@ -370,6 +370,7 @@ BOOST_AUTO_TEST_CASE(dynamic_return_types_not_possible)
BOOST_AUTO_TEST_CASE(warn_nonpresent_pragma)
{
char const* text = R"(
// SPDX-License-Identifier: GPL-3.0
contract C {}
)";
auto sourceAndError = parseAnalyseAndReturnError(text, true, false);

View File

@ -209,7 +209,7 @@ BOOST_AUTO_TEST_CASE(type_identifiers)
ModifierDefinition mod(++id, SourceLocation{}, make_shared<string>("modif"), {}, emptyParams, {}, {}, {});
BOOST_CHECK_EQUAL(ModifierType(mod).identifier(), "t_modifier$__$");
SourceUnit su(++id, {}, {});
SourceUnit su(++id, {}, {}, {});
BOOST_CHECK_EQUAL(ModuleType(su).identifier(), "t_module_7");
BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Block).identifier(), "t_magic_block");
BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Message).identifier(), "t_magic_message");

View File

@ -427,7 +427,7 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
BOOST_CHECK(result["sources"]["fileA"]["legacyAST"].isObject());
BOOST_CHECK_EQUAL(
util::jsonCompactPrint(result["sources"]["fileA"]["legacyAST"]),
"{\"attributes\":{\"absolutePath\":\"fileA\",\"exportedSymbols\":{\"A\":[1]}},\"children\":"
"{\"attributes\":{\"absolutePath\":\"fileA\",\"exportedSymbols\":{\"A\":[1]},\"license\":null},\"children\":"
"[{\"attributes\":{\"abstract\":false,\"baseContracts\":[null],\"contractDependencies\":[null],\"contractKind\":\"contract\","
"\"documentation\":null,\"fullyImplemented\":true,\"linearizedBaseContracts\":[1],\"name\":\"A\",\"nodes\":[null],\"scope\":2},"
"\"id\":1,\"name\":\"ContractDefinition\",\"src\":\"0:14:0\"}],\"id\":2,\"name\":\"SourceUnit\",\"src\":\"0:14:0\"}"

View File

@ -52,11 +52,11 @@ TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix
void SyntaxTest::setupCompiler()
{
string const versionPragma = "pragma solidity >=0.0;\n";
string const preamble = "pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n";
compiler().reset();
auto sourcesWithPragma = m_sources;
for (auto& source: sourcesWithPragma)
source.second = versionPragma + source.second;
source.second = preamble + source.second;
compiler().setSources(sourcesWithPragma);
compiler().setEVMVersion(m_evmVersion);
compiler().setParserErrorRecovery(m_parserErrorRecovery);
@ -89,18 +89,18 @@ void SyntaxTest::parseAndAnalyze()
void SyntaxTest::filterObtainedErrors()
{
string const versionPragma = "pragma solidity >=0.0;\n";
string const preamble = "pragma solidity >=0.0;\n// SPDX-License-Identifier: GPL-3.0\n";
for (auto const& currentError: filterErrors(compiler().errors(), true))
{
int locationStart = -1, locationEnd = -1;
string sourceName;
if (auto location = boost::get_error_info<errinfo_sourceLocation>(*currentError))
{
// ignore the version pragma inserted by the testing tool when calculating locations.
if (location->start >= static_cast<int>(versionPragma.size()))
locationStart = location->start - versionPragma.size();
if (location->end >= static_cast<int>(versionPragma.size()))
locationEnd = location->end - versionPragma.size();
// ignore the version & license pragma inserted by the testing tool when calculating locations.
if (location->start >= static_cast<int>(preamble.size()))
locationStart = location->start - (preamble.size());
if (location->end >= static_cast<int>(preamble.size()))
locationEnd = location->end - (preamble.size());
if (location->source)
sourceName = location->source->name();
}