Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler stack: Protect loadGeneratedIR from contracts that cannot be deployed #15505

Merged
merged 4 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Compiler Features:


Bugfixes:
* General: Fix internal compiler error when requesting IR AST outputs for interfaces and abstract contracts.


### 0.8.28 (2024-10-09)
Expand Down
48 changes: 33 additions & 15 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -969,46 +969,61 @@ std::string const CompilerStack::filesystemFriendlyName(std::string const& _cont
return matchContract.contract->name();
}

std::string const& CompilerStack::yulIR(std::string const& _contractName) const
std::optional<std::string> const& CompilerStack::yulIR(std::string const& _contractName) const
{
solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful.");
return contract(_contractName).yulIR;
}

Json CompilerStack::yulIRAst(std::string const& _contractName) const
std::optional<Json> CompilerStack::yulIRAst(std::string const& _contractName) const
{
solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful.");
solUnimplementedAssert(!isExperimentalSolidity());

// NOTE: Intentionally not using LazyInit. The artifact can get very large and we don't want to
// keep it around when compiling a large project containing many contracts.
return loadGeneratedIR(contract(_contractName).yulIR).astJson();
Contract const& currentContract = contract(_contractName);
yulAssert(currentContract.contract);
yulAssert(currentContract.yulIR.has_value() == currentContract.contract->canBeDeployed());
if (!currentContract.yulIR)
return std::nullopt;
return loadGeneratedIR(*currentContract.yulIR).astJson();
}

Json CompilerStack::yulCFGJson(std::string const& _contractName) const
std::optional<Json> CompilerStack::yulCFGJson(std::string const& _contractName) const
{
solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful.");
solUnimplementedAssert(!isExperimentalSolidity());

// NOTE: Intentionally not using LazyInit. The artifact can get very large and we don't want to
// keep it around when compiling a large project containing many contracts.
return loadGeneratedIR(contract(_contractName).yulIR).cfgJson();
Contract const& currentContract = contract(_contractName);
yulAssert(currentContract.contract);
yulAssert(currentContract.yulIR.has_value() == currentContract.contract->canBeDeployed());
if (!currentContract.yulIR)
return std::nullopt;
return loadGeneratedIR(*currentContract.yulIR).cfgJson();
}

std::string const& CompilerStack::yulIROptimized(std::string const& _contractName) const
std::optional<std::string> const& CompilerStack::yulIROptimized(std::string const& _contractName) const
{
solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful.");
return contract(_contractName).yulIROptimized;
}

Json CompilerStack::yulIROptimizedAst(std::string const& _contractName) const
std::optional<Json> CompilerStack::yulIROptimizedAst(std::string const& _contractName) const
{
solAssert(m_stackState == CompilationSuccessful, "Compilation was not successful.");
solUnimplementedAssert(!isExperimentalSolidity());

// NOTE: Intentionally not using LazyInit. The artifact can get very large and we don't want to
// keep it around when compiling a large project containing many contracts.
return loadGeneratedIR(contract(_contractName).yulIROptimized).astJson();
Contract const& currentContract = contract(_contractName);
yulAssert(currentContract.contract);
yulAssert(currentContract.yulIROptimized.has_value() == currentContract.contract->canBeDeployed());
if (!currentContract.yulIROptimized)
return std::nullopt;
return loadGeneratedIR(*currentContract.yulIROptimized).astJson();
}

evmasm::LinkerObject const& CompilerStack::object(std::string const& _contractName) const
Expand Down Expand Up @@ -1507,8 +1522,11 @@ void CompilerStack::generateIR(ContractDefinition const& _contract, bool _unopti
solAssert(m_stackState >= AnalysisSuccessful, "");

Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
if (!compiledContract.yulIR.empty())
if (compiledContract.yulIR)
{
solAssert(!compiledContract.yulIR->empty());
return;
}

if (!*_contract.sourceUnit().annotation().useABICoderV2)
m_errorReporter.warning(
Expand All @@ -1527,7 +1545,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract, bool _unopti

std::map<ContractDefinition const*, std::string_view const> otherYulSources;
for (auto const& pair: m_contracts)
otherYulSources.emplace(pair.second.contract, pair.second.yulIR);
otherYulSources.emplace(pair.second.contract, pair.second.yulIR ? *pair.second.yulIR : std::string_view{});

if (m_experimentalAnalysis)
{
Expand Down Expand Up @@ -1564,7 +1582,8 @@ void CompilerStack::generateIR(ContractDefinition const& _contract, bool _unopti
);
}

YulStack stack = loadGeneratedIR(compiledContract.yulIR);
yulAssert(compiledContract.yulIR);
YulStack stack = loadGeneratedIR(*compiledContract.yulIR);
if (!_unoptimizedOnly)
{
stack.optimize();
Expand All @@ -1580,14 +1599,13 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
return;

Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
solAssert(!compiledContract.yulIROptimized.empty(), "");
solAssert(compiledContract.yulIROptimized);
solAssert(!compiledContract.yulIROptimized->empty());
if (!compiledContract.object.bytecode.empty())
return;

// Re-parse the Yul IR in EVM dialect
YulStack stack = loadGeneratedIR(compiledContract.yulIROptimized);

//cout << yul::AsmPrinter{}(*stack.parserResult()->code) << endl;
YulStack stack = loadGeneratedIR(*compiledContract.yulIROptimized);

std::string deployedName = IRNames::deployedObject(_contract);
solAssert(!deployedName.empty(), "");
Expand Down
14 changes: 7 additions & 7 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,18 +321,18 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
virtual std::string const filesystemFriendlyName(std::string const& _contractName) const override;

/// @returns the IR representation of a contract.
std::string const& yulIR(std::string const& _contractName) const;
std::optional<std::string> const& yulIR(std::string const& _contractName) const;

/// @returns the IR representation of a contract AST in format.
Json yulIRAst(std::string const& _contractName) const;
std::optional<Json> yulIRAst(std::string const& _contractName) const;

/// @returns the optimized IR representation of a contract.
std::string const& yulIROptimized(std::string const& _contractName) const;
std::optional<std::string> const& yulIROptimized(std::string const& _contractName) const;

/// @returns the optimized IR representation of a contract AST in JSON format.
Json yulIROptimizedAst(std::string const& _contractName) const;
std::optional<Json> yulIROptimizedAst(std::string const& _contractName) const;

Json yulCFGJson(std::string const& _contractName) const;
std::optional<Json> yulCFGJson(std::string const& _contractName) const;

/// @returns the assembled object for a contract.
virtual evmasm::LinkerObject const& object(std::string const& _contractName) const override;
Expand Down Expand Up @@ -445,8 +445,8 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
std::optional<std::string> runtimeGeneratedYulUtilityCode; ///< Extra Yul utility code that was used when compiling the deployed assembly
evmasm::LinkerObject object; ///< Deployment object (includes the runtime sub-object).
evmasm::LinkerObject runtimeObject; ///< Runtime object.
std::string yulIR; ///< Yul IR code straight from the code generator.
std::string yulIROptimized; ///< Reparsed and possibly optimized Yul IR code.
std::optional<std::string> yulIR; ///< Yul IR code straight from the code generator.
std::optional<std::string> yulIROptimized; ///< Reparsed and possibly optimized Yul IR code.
util::LazyInit<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
util::LazyInit<Json const> abi;
util::LazyInit<Json const> storageLayout;
Expand Down
10 changes: 5 additions & 5 deletions libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1482,15 +1482,15 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu

// IR
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesExperimental))
contractData["ir"] = compilerStack.yulIR(contractName);
contractData["ir"] = compilerStack.yulIR(contractName).value_or("");
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irAst", wildcardMatchesExperimental))
contractData["irAst"] = compilerStack.yulIRAst(contractName);
contractData["irAst"] = compilerStack.yulIRAst(contractName).value_or(Json{});
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesExperimental))
contractData["irOptimized"] = compilerStack.yulIROptimized(contractName);
contractData["irOptimized"] = compilerStack.yulIROptimized(contractName).value_or("");
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimizedAst", wildcardMatchesExperimental))
contractData["irOptimizedAst"] = compilerStack.yulIROptimizedAst(contractName);
contractData["irOptimizedAst"] = compilerStack.yulIROptimizedAst(contractName).value_or(Json{});
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "yulCFGJson", wildcardMatchesExperimental))
contractData["yulCFGJson"] = compilerStack.yulCFGJson(contractName);
contractData["yulCFGJson"] = compilerStack.yulCFGJson(contractName).value_or(Json{});

// EVM
Json evmData;
Expand Down
27 changes: 16 additions & 11 deletions solc/CommandLineInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,13 @@ void CommandLineInterface::handleIR(std::string const& _contractName)
if (!m_options.compiler.outputs.ir)
return;

std::optional<std::string> const& ir = m_compiler->yulIR(_contractName);
if (!m_options.output.dir.empty())
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName));
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", ir.value_or(""));
else
{
sout() << "IR:" << std::endl;
sout() << m_compiler->yulIR(_contractName) << std::endl;
sout() << "IR:\n";
sout() << ir.value_or("") << std::endl;
}
}

Expand All @@ -273,19 +274,20 @@ void CommandLineInterface::handleIRAst(std::string const& _contractName)
if (!m_options.compiler.outputs.irAstJson)
return;

std::optional<Json> const& yulIRAst = m_compiler->yulIRAst(_contractName);
if (!m_options.output.dir.empty())
createFile(
m_compiler->filesystemFriendlyName(_contractName) + "_yul_ast.json",
util::jsonPrint(
m_compiler->yulIRAst(_contractName),
yulIRAst.value_or(Json{}),
m_options.formatting.json
)
);
else
{
sout() << "IR AST:" << std::endl;
sout() << util::jsonPrint(
m_compiler->yulIRAst(_contractName),
yulIRAst.value_or(Json{}),
m_options.formatting.json
) << std::endl;
}
Expand All @@ -298,18 +300,19 @@ void CommandLineInterface::handleYulCFGExport(std::string const& _contractName)
if (!m_options.compiler.outputs.yulCFGJson)
return;

std::optional<Json> const& yulCFGJson = m_compiler->yulCFGJson(_contractName);
if (!m_options.output.dir.empty())
createFile(
m_compiler->filesystemFriendlyName(_contractName) + "_yul_cfg.json",
util::jsonPrint(
m_compiler->yulCFGJson(_contractName),
yulCFGJson.value_or(Json{}),
m_options.formatting.json
)
);
else
{
sout() << util::jsonPrint(
m_compiler->yulCFGJson(_contractName),
yulCFGJson.value_or(Json{}),
m_options.formatting.json
) << std::endl;
}
Expand All @@ -322,15 +325,16 @@ void CommandLineInterface::handleIROptimized(std::string const& _contractName)
if (!m_options.compiler.outputs.irOptimized)
return;

std::optional<std::string> const& irOptimized = m_compiler->yulIROptimized(_contractName);
if (!m_options.output.dir.empty())
createFile(
m_compiler->filesystemFriendlyName(_contractName) + "_opt.yul",
m_compiler->yulIROptimized(_contractName)
irOptimized.value_or("")
);
else
{
sout() << "Optimized IR:" << std::endl;
sout() << m_compiler->yulIROptimized(_contractName) << std::endl;
sout() << irOptimized.value_or("") << std::endl;
}
}

Expand All @@ -341,19 +345,20 @@ void CommandLineInterface::handleIROptimizedAst(std::string const& _contractName
if (!m_options.compiler.outputs.irOptimizedAstJson)
return;

std::optional<Json> const& yulIROptimizedAst = m_compiler->yulIROptimizedAst(_contractName);
if (!m_options.output.dir.empty())
createFile(
m_compiler->filesystemFriendlyName(_contractName) + "_opt_yul_ast.json",
util::jsonPrint(
m_compiler->yulIROptimizedAst(_contractName),
yulIROptimizedAst.value_or(Json{}),
m_options.formatting.json
)
);
else
{
sout() << "Optimized IR AST:" << std::endl;
sout() << util::jsonPrint(
m_compiler->yulIROptimizedAst(_contractName),
yulIROptimizedAst.value_or(Json{}),
m_options.formatting.json
) << std::endl;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity *;

abstract contract C {}
interface I {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"language": "Solidity",
"sources": {
"C": {"urls": ["standard_undeployable_contract_all_outputs/in.sol"]}
},
"settings": {
"outputSelection": {
"*": {
"*": ["*", "ir", "irAst", "irOptimized", "irOptimizedAst"]
}
}
}
}
Loading