Skip to content

Commit

Permalink
Merge pull request ethereum#15505 from ethereum/protect_load_generate…
Browse files Browse the repository at this point in the history
…d_ir

Compiler stack: Protect loadGeneratedIR from contracts that cannot be deployed
  • Loading branch information
clonker authored Oct 16, 2024
2 parents 7264b47 + 7972c51 commit 3994386
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 39 deletions.
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

0 comments on commit 3994386

Please sign in to comment.