/*
This file is part of solidity.
solidity 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.
solidity 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 solidity. If not, see .
*/
// SPDX-License-Identifier: GPL-3.0
/**
* @author Alex Beregszaszi
* @date 2017
* Component that translates Solidity code into Yul.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace solidity;
using namespace solidity::frontend;
using namespace solidity::langutil;
using namespace solidity::util;
namespace
{
void verifyCallGraph(
set const& _expectedCallables,
set _generatedFunctions
)
{
for (auto const& expectedCallable: _expectedCallables)
if (auto const* expectedFunction = dynamic_cast(expectedCallable))
{
solAssert(
_generatedFunctions.count(expectedFunction) == 1 || expectedFunction->isConstructor(),
"No code generated for function " + expectedFunction->name() + " even though it is not a constructor."
);
_generatedFunctions.erase(expectedFunction);
}
solAssert(
_generatedFunctions.size() == 0,
"Of the generated functions " + toString(_generatedFunctions.size()) + " are not in the call graph."
);
}
set collectReachableCallables(
CallGraph const& _graph
)
{
set reachableCallables;
for (CallGraph::Node const& reachableNode: _graph.edges | ranges::views::keys)
if (holds_alternative(reachableNode))
reachableCallables.emplace(get(reachableNode));
return reachableCallables;
}
}
pair IRGenerator::run(
ContractDefinition const& _contract,
bytes const& _cborMetadata,
map const& _otherYulSources
)
{
string ir = yul::reindent(generate(_contract, _cborMetadata, _otherYulSources));
yul::YulStack asmStack(
m_evmVersion,
m_eofVersion,
yul::YulStack::Language::StrictAssembly,
m_optimiserSettings,
m_context.debugInfoSelection()
);
if (!asmStack.parseAndAnalyze("", ir))
{
string errorMessage;
for (auto const& error: asmStack.errors())
errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(
*error,
asmStack.charStream("")
);
solAssert(false, ir + "\n\nInvalid IR generated:\n" + errorMessage + "\n");
}
asmStack.optimize();
return {std::move(ir), asmStack.print(m_context.soliditySourceProvider())};
}
string IRGenerator::generate(
ContractDefinition const& _contract,
bytes const& _cborMetadata,
map const& _otherYulSources
)
{
auto subObjectSources = [&_otherYulSources](std::set const& subObjects) -> string
{
std::string subObjectsSources;
for (ContractDefinition const* subObject: subObjects)
subObjectsSources += _otherYulSources.at(subObject);
return subObjectsSources;
};
auto formatUseSrcMap = [](IRGenerationContext const& _context) -> string
{
return joinHumanReadable(
ranges::views::transform(_context.usedSourceNames(), [_context](string const& _sourceName) {
return to_string(_context.sourceIndices().at(_sourceName)) + ":" + escapeAndQuoteString(_sourceName);
}),
", "
);
};
Whiskers t(R"(
/// @use-src
object "" {
code {
let := ()
()
}
/// @use-src
object "" {
code {
let called_via_delegatecall := iszero(eq(loadimmutable(""), address()))
}
data "" hex""
}
}
)");
resetContext(_contract, ExecutionContext::Creation);
for (VariableDeclaration const* var: ContractType(_contract).immutableVariables())
m_context.registerImmutableVariable(*var);
t("CreationObject", IRNames::creationObject(_contract));
t("sourceLocationCommentCreation", dispenseLocationComment(_contract));
t("library", _contract.isLibrary());
FunctionDefinition const* constructor = _contract.constructor();
t("callValueCheck", !constructor || !constructor->isPayable() ? callValueCheck() : "");
vector constructorParams;
if (constructor && !constructor->parameters().empty())
{
for (size_t i = 0; i < CompilerUtils::sizeOnStack(constructor->parameters()); ++i)
constructorParams.emplace_back(m_context.newYulVariable());
t(
"copyConstructorArguments",
m_utils.copyConstructorArgumentsToMemoryFunction(_contract, IRNames::creationObject(_contract))
);
}
t("constructorParams", joinHumanReadable(constructorParams));
t("constructorHasParams", !constructorParams.empty());
t("constructor", IRNames::constructor(_contract));
t("deploy", deployCode(_contract));
generateConstructors(_contract);
set creationFunctionList = generateQueuedFunctions();
InternalDispatchMap internalDispatchMap = generateInternalDispatchFunctions(_contract);
t("functions", m_context.functionCollector().requestedFunctions());
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
// This has to be called only after all other code generation for the creation object is complete.
bool creationInvolvesMemoryUnsafeAssembly = m_context.memoryUnsafeInlineAssemblySeen();
t("memoryInitCreation", memoryInit(!creationInvolvesMemoryUnsafeAssembly));
t("useSrcMapCreation", formatUseSrcMap(m_context));
resetContext(_contract, ExecutionContext::Deployed);
// NOTE: Function pointers can be passed from creation code via storage variables. We need to
// get all the functions they could point to into the dispatch functions even if they're never
// referenced by name in the deployed code.
m_context.initializeInternalDispatch(std::move(internalDispatchMap));
// Do not register immutables to avoid assignment.
t("DeployedObject", IRNames::deployedObject(_contract));
t("sourceLocationCommentDeployed", dispenseLocationComment(_contract));
t("library_address", IRNames::libraryAddressImmutable());
t("dispatch", dispatchRoutine(_contract));
set deployedFunctionList = generateQueuedFunctions();
generateInternalDispatchFunctions(_contract);
t("deployedFunctions", m_context.functionCollector().requestedFunctions());
t("deployedSubObjects", subObjectSources(m_context.subObjectsCreated()));
t("metadataName", yul::Object::metadataName());
t("cborMetadata", util::toHex(_cborMetadata));
t("useSrcMapDeployed", formatUseSrcMap(m_context));
// This has to be called only after all other code generation for the deployed object is complete.
bool deployedInvolvesMemoryUnsafeAssembly = m_context.memoryUnsafeInlineAssemblySeen();
t("memoryInitDeployed", memoryInit(!deployedInvolvesMemoryUnsafeAssembly));
solAssert(_contract.annotation().creationCallGraph->get() != nullptr, "");
solAssert(_contract.annotation().deployedCallGraph->get() != nullptr, "");
verifyCallGraph(collectReachableCallables(**_contract.annotation().creationCallGraph), std::move(creationFunctionList));
verifyCallGraph(collectReachableCallables(**_contract.annotation().deployedCallGraph), std::move(deployedFunctionList));
return t.render();
}
string IRGenerator::generate(Block const& _block)
{
IRGeneratorForStatements generator(m_context, m_utils);
generator.generate(_block);
return generator.code();
}
set IRGenerator::generateQueuedFunctions()
{
set functions;
while (!m_context.functionGenerationQueueEmpty())
{
FunctionDefinition const& functionDefinition = *m_context.dequeueFunctionForCodeGeneration();
functions.emplace(&functionDefinition);
// NOTE: generateFunction() may modify function generation queue
generateFunction(functionDefinition);
}
return functions;
}
InternalDispatchMap IRGenerator::generateInternalDispatchFunctions(ContractDefinition const& _contract)
{
solAssert(
m_context.functionGenerationQueueEmpty(),
"At this point all the enqueued functions should have been generated. "
"Otherwise the dispatch may be incomplete."
);
InternalDispatchMap internalDispatchMap = m_context.consumeInternalDispatchMap();
for (YulArity const& arity: internalDispatchMap | ranges::views::keys)
{
string funName = IRNames::internalDispatch(arity);
m_context.functionCollector().createFunction(funName, [&]() {
Whiskers templ(R"(
function (fun+in>, +in>) +out>-> +out> {
switch fun
<#cases>
case
{
+out> :=+out> ()
}
default { () }
}
)");
templ("sourceLocationComment", dispenseLocationComment(_contract));
templ("functionName", funName);
templ("panic", m_utils.panicFunction(PanicCode::InvalidInternalFunction));
templ("in", suffixedVariableNameList("in_", 0, arity.in));
templ("out", suffixedVariableNameList("out_", 0, arity.out));
vector