Skip to content

Commit

Permalink
Stop after parsing.
Browse files Browse the repository at this point in the history
  • Loading branch information
chriseth authored and Marenz committed Aug 24, 2020
1 parent bff0f9b commit c3a9d06
Show file tree
Hide file tree
Showing 13 changed files with 220 additions and 42 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Compiler Features:
* Yul: Report error when using non-string literals for ``datasize()``, ``dataoffset()``, ``linkersymbol()``, ``loadimmutable()``, ``setimmutable()``.
* Yul Optimizer: LoopInvariantCodeMotion can move reading operations outside for-loops as long as the affected area is not modified inside the loop.
* SMTChecker: Support conditional operator.
* New feature to stop compilation after parsing stage. Can be used with ``solc --stop-after parsing``

Bugfixes:
* Optimizer: Keep side-effects of ``x`` in ``byte(a, shr(b, x))`` even if the constants ``a`` and ``b`` would make the expression zero unconditionally. This optimizer rule is very hard if not impossible to trigger in a way that it can result in invalid code, though.
Expand Down
2 changes: 2 additions & 0 deletions docs/using-the-compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ Input Description
// Optional
"settings":
{
// Optional: Stop compilation after this stage. Currently only "parsing" is valid here
"stopAfter": "parsing",
// Optional: Sorted list of remappings
"remappings": [ ":g=/dir" ],
// Optional: Optimizer settings
Expand Down
8 changes: 7 additions & 1 deletion libsolidity/ast/ASTJsonConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,14 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
std::vector<pair<string, Json::Value>> attributes = {
make_pair("file", _node.path()),
make_pair("absolutePath", _node.annotation().absolutePath),
make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit)),
make_pair("scope", idOrNull(_node.scope()))
};

if (_node.annotation().sourceUnit)
attributes.emplace_back(
make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit))
);

attributes.emplace_back("unitAlias", _node.name());
Json::Value symbolAliases(Json::arrayValue);
for (auto const& symbolAlias: _node.symbolAliases())
Expand Down Expand Up @@ -735,6 +740,7 @@ bool ASTJsonConverter::visit(FunctionCall const& _node)
}
else
attributes.emplace_back("kind", functionCallKind(nodeKind));

appendExpressionAttributes(attributes, _node.annotation());
setJsonNode(_node, "FunctionCall", std::move(attributes));
return false;
Expand Down
5 changes: 4 additions & 1 deletion libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ util::Result<TypePointers> transformParametersToExternal(TypePointers const& _pa

for (auto const& type: _parameters)
{
if (!type)
continue;

if (TypePointer ext = type->interfaceType(_inLibrary).get())
transformed.push_back(ext);
else
Expand Down Expand Up @@ -3571,7 +3574,7 @@ string FunctionType::externalSignature() const

// "inLibrary" is only relevant if this is not an event.
bool inLibrary = false;
if (kind() != Kind::Event)
if (kind() != Kind::Event && m_declaration->scope())
if (auto const* contract = dynamic_cast<ContractDefinition const*>(m_declaration->scope()))
inLibrary = contract->isLibrary();

Expand Down
75 changes: 46 additions & 29 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ std::optional<CompilerStack::Remapping> CompilerStack::parseRemapping(string con

void CompilerStack::setRemappings(vector<Remapping> const& _remappings)
{
if (m_stackState >= ParsingPerformed)
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set remappings before parsing."));
for (auto const& remapping: _remappings)
solAssert(!remapping.prefix.empty(), "");
Expand All @@ -130,21 +130,21 @@ void CompilerStack::setRemappings(vector<Remapping> const& _remappings)

void CompilerStack::setEVMVersion(langutil::EVMVersion _version)
{
if (m_stackState >= ParsingPerformed)
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set EVM version before parsing."));
m_evmVersion = _version;
}

void CompilerStack::setSMTSolverChoice(smtutil::SMTSolverChoice _enabledSMTSolvers)
{
if (m_stackState >= ParsingPerformed)
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set enabled SMT solvers before parsing."));
m_enabledSMTSolvers = _enabledSMTSolvers;
}

void CompilerStack::setLibraries(std::map<std::string, util::h160> const& _libraries)
{
if (m_stackState >= ParsingPerformed)
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set libraries before parsing."));
m_libraries = _libraries;
}
Expand All @@ -158,36 +158,36 @@ void CompilerStack::setOptimiserSettings(bool _optimize, unsigned _runs)

void CompilerStack::setOptimiserSettings(OptimiserSettings _settings)
{
if (m_stackState >= ParsingPerformed)
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set optimiser settings before parsing."));
m_optimiserSettings = std::move(_settings);
}

void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings)
{
if (m_stackState >= ParsingPerformed)
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set revert string settings before parsing."));
solUnimplementedAssert(_revertStrings != RevertStrings::VerboseDebug, "");
m_revertStrings = _revertStrings;
}

void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources)
{
if (m_stackState >= ParsingPerformed)
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set use literal sources before parsing."));
m_metadataLiteralSources = _metadataLiteralSources;
}

void CompilerStack::setMetadataHash(MetadataHash _metadataHash)
{
if (m_stackState >= ParsingPerformed)
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set metadata hash before parsing."));
m_metadataHash = _metadataHash;
}

void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _response)
{
if (m_stackState >= ParsingPerformed)
if (m_stackState >= ParsedAndImported)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must add SMTLib2 responses before parsing."));
m_smtlib2Responses[_hash] = _response;
}
Expand Down Expand Up @@ -244,6 +244,7 @@ bool CompilerStack::parse()
vector<string> sourcesToParse;
for (auto const& s: m_sources)
sourcesToParse.push_back(s.first);

for (size_t i = 0; i < sourcesToParse.size(); ++i)
{
string const& path = sourcesToParse[i];
Expand All @@ -255,17 +256,21 @@ bool CompilerStack::parse()
else
{
source.ast->annotation().path = path;
for (auto const& newSource: loadMissingSources(*source.ast, path))
{
string const& newPath = newSource.first;
string const& newContents = newSource.second;
m_sources[newPath].scanner = make_shared<Scanner>(CharStream(newContents, newPath));
sourcesToParse.push_back(newPath);
}
if (m_stopAfter >= ParsedAndImported)
for (auto const& newSource: loadMissingSources(*source.ast, path))
{
string const& newPath = newSource.first;
string const& newContents = newSource.second;
m_sources[newPath].scanner = make_shared<Scanner>(CharStream(newContents, newPath));
sourcesToParse.push_back(newPath);
}
}
}

m_stackState = ParsingPerformed;
if (m_stopAfter <= Parsed)
m_stackState = Parsed;
else
m_stackState = ParsedAndImported;
if (!Error::containsOnlyWarnings(m_errorReporter.errors()))
m_hasError = true;
return !m_hasError;
Expand All @@ -287,13 +292,13 @@ void CompilerStack::importASTs(map<string, Json::Value> const& _sources)
source.scanner = scanner;
m_sources[path] = source;
}
m_stackState = ParsingPerformed;
m_stackState = ParsedAndImported;
m_importedSources = true;
}

bool CompilerStack::analyze()
{
if (m_stackState != ParsingPerformed || m_stackState >= AnalysisPerformed)
if (m_stackState != ParsedAndImported || m_stackState >= AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call analyze only after parsing was performed."));
resolveImports();

Expand Down Expand Up @@ -464,9 +469,13 @@ bool CompilerStack::analyze()
return !m_hasError;
}

bool CompilerStack::parseAndAnalyze()
bool CompilerStack::parseAndAnalyze(State _stopAfter)
{
m_stopAfter = _stopAfter;

bool success = parse();
if (m_stackState >= m_stopAfter)
return success;
if (success || m_parserErrorRecovery)
success = analyze();
return success;
Expand Down Expand Up @@ -497,12 +506,16 @@ bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) con
return false;
}

bool CompilerStack::compile()
bool CompilerStack::compile(State _stopAfter)
{
m_stopAfter = _stopAfter;
if (m_stackState < AnalysisPerformed)
if (!parseAndAnalyze())
return false;

if (m_stackState >= m_stopAfter)
return true;

if (m_hasError)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors."));

Expand Down Expand Up @@ -547,11 +560,15 @@ void CompilerStack::link()

vector<string> CompilerStack::contractNames() const
{
if (m_stackState < AnalysisPerformed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
vector<string> contractNames;
for (auto const& contract: m_contracts)
contractNames.push_back(contract.first);

for (Source const* source: m_sourceOrder)
if (source->ast)
for (
ContractDefinition const* contract:
ASTNode::filteredNodes<ContractDefinition>(source->ast->nodes())
)
contractNames.emplace_back(contract->fullyQualifiedName());
return contractNames;
}

Expand Down Expand Up @@ -850,7 +867,7 @@ Scanner const& CompilerStack::scanner(string const& _sourceName) const

SourceUnit const& CompilerStack::ast(string const& _sourceName) const
{
if (m_stackState < ParsingPerformed)
if (m_stackState < Parsed)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing not yet performed."));
if (!source(_sourceName).ast && !m_parserErrorRecovery)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
Expand Down Expand Up @@ -923,7 +940,7 @@ string const& CompilerStack::Source::ipfsUrl() const

StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _sourcePath)
{
solAssert(m_stackState < ParsingPerformed, "");
solAssert(m_stackState < ParsedAndImported, "");
StringMap newSources;
try
{
Expand Down Expand Up @@ -967,7 +984,7 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string

string CompilerStack::applyRemapping(string const& _path, string const& _context)
{
solAssert(m_stackState < ParsingPerformed, "");
solAssert(m_stackState < ParsedAndImported, "");
// Try to find the longest prefix match in all remappings that are active in the current context.
auto isPrefixOf = [](string const& _a, string const& _b)
{
Expand Down Expand Up @@ -1009,7 +1026,7 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context

void CompilerStack::resolveImports()
{
solAssert(m_stackState == ParsingPerformed, "");
solAssert(m_stackState == ParsedAndImported, "");

// topological sorting (depth first search) of the import graph, cutting potential cycles
vector<Source const*> sourceOrder;
Expand Down
8 changes: 5 additions & 3 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ class CompilerStack: boost::noncopyable
enum State {
Empty,
SourcesSet,
ParsingPerformed,
Parsed,
ParsedAndImported,
AnalysisPerformed,
CompilationSuccessful
};
Expand Down Expand Up @@ -216,11 +217,11 @@ class CompilerStack: boost::noncopyable

/// Parses and analyzes all source units that were added
/// @returns false on error.
bool parseAndAnalyze();
bool parseAndAnalyze(State _stopAfter = State::CompilationSuccessful);

/// Compiles the source units that were previously added and parsed.
/// @returns false on error.
bool compile();
bool compile(State _stopAfter = State::CompilationSuccessful);

/// @returns the list of sources (paths) used
std::vector<std::string> sourceNames() const;
Expand Down Expand Up @@ -440,6 +441,7 @@ class CompilerStack: boost::noncopyable
ReadCallback::Callback m_readFile;
OptimiserSettings m_optimiserSettings;
RevertStrings m_revertStrings = RevertStrings::Default;
State m_stopAfter = State::CompilationSuccessful;
langutil::EVMVersion m_evmVersion;
smtutil::SMTSolverChoice m_enabledSMTSolvers;
std::map<std::string, std::set<std::string>> m_requestedContractNames;
Expand Down
21 changes: 19 additions & 2 deletions libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ std::optional<Json::Value> checkAuxiliaryInputKeys(Json::Value const& _input)

std::optional<Json::Value> checkSettingsKeys(Json::Value const& _input)
{
static set<string> keys{"parserErrorRecovery", "debug", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"};
static set<string> keys{"parserErrorRecovery", "debug", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings", "stopAfter"};
return checkKeys(_input, keys, "settings");
}

Expand Down Expand Up @@ -718,6 +718,17 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:
if (auto result = checkSettingsKeys(settings))
return *result;

if (settings.isMember("stopAfter"))
{
if (!settings["stopAfter"].isString())
return formatFatalError("JSONError", "\"settings.stopAfter\" must be a string.");

if (settings["stopAfter"].asString() != "parsing")
return formatFatalError("JSONError", "Invalid value for \"settings.stopAfter\". Only valid value is \"parsing\".");

ret.stopAfter = CompilerStack::State::Parsed;
}

if (settings.isMember("parserErrorRecovery"))
{
if (!settings["parserErrorRecovery"].isBool())
Expand Down Expand Up @@ -843,6 +854,12 @@ std::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler:

ret.outputSelection = std::move(outputSelection);

if (ret.stopAfter != CompilerStack::State::CompilationSuccessful && isBinaryRequested(ret.outputSelection))
return formatFatalError(
"JSONError",
"Requested output selection conflicts with \"settings.stopAfter\"."
);

return { std::move(ret) };
}

Expand Down Expand Up @@ -877,7 +894,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
if (binariesRequested)
compilerStack.compile();
else
compilerStack.parseAndAnalyze();
compilerStack.parseAndAnalyze(_inputsAndSettings.stopAfter);

for (auto const& error: compilerStack.errors())
{
Expand Down
1 change: 1 addition & 0 deletions libsolidity/interface/StandardCompiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class StandardCompiler: boost::noncopyable
std::string language;
Json::Value errors;
bool parserErrorRecovery = false;
CompilerStack::State stopAfter = CompilerStack::State::CompilationSuccessful;
std::map<std::string, std::string> sources;
std::map<util::h256, std::string> smtLib2Responses;
langutil::EVMVersion evmVersion;
Expand Down
Loading

0 comments on commit c3a9d06

Please sign in to comment.