mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
#include <liblangutil/Common.h>
|
||||
|
||||
#include <range/v3/algorithm/any_of.hpp>
|
||||
#include <range/v3/view/filter.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
@@ -162,6 +163,81 @@ bool DocStringTagParser::visit(ErrorDefinition const& _error)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DocStringTagParser::visit(InlineAssembly const& _assembly)
|
||||
{
|
||||
if (!_assembly.documentation())
|
||||
return true;
|
||||
StructuredDocumentation documentation{-1, _assembly.location(), _assembly.documentation()};
|
||||
ErrorList errors;
|
||||
ErrorReporter errorReporter{errors};
|
||||
auto docTags = DocStringParser{documentation, errorReporter}.parse();
|
||||
|
||||
if (!errors.empty())
|
||||
{
|
||||
SecondarySourceLocation ssl;
|
||||
for (auto const& error: errors)
|
||||
if (error->comment())
|
||||
ssl.append(
|
||||
*error->comment(),
|
||||
_assembly.location()
|
||||
);
|
||||
m_errorReporter.warning(
|
||||
7828_error,
|
||||
_assembly.location(),
|
||||
"Inline assembly has invalid NatSpec documentation.",
|
||||
ssl
|
||||
);
|
||||
}
|
||||
|
||||
for (auto const& [tagName, tagValue]: docTags)
|
||||
{
|
||||
if (tagName == "solidity")
|
||||
{
|
||||
vector<string> values;
|
||||
boost::split(values, tagValue.content, isWhiteSpace);
|
||||
|
||||
set<string> valuesSeen;
|
||||
set<string> duplicates;
|
||||
for (auto const& value: values | ranges::views::filter(not_fn(&string::empty)))
|
||||
if (valuesSeen.insert(value).second)
|
||||
{
|
||||
if (value == "memory-safe-assembly")
|
||||
{
|
||||
if (_assembly.annotation().markedMemorySafe)
|
||||
m_errorReporter.warning(
|
||||
8544_error,
|
||||
_assembly.location(),
|
||||
"Inline assembly marked as memory safe using both a NatSpec tag and an assembly flag. "
|
||||
"If you are not concerned with backwards compatibility, only use the assembly flag, "
|
||||
"otherwise only use the NatSpec tag."
|
||||
);
|
||||
_assembly.annotation().markedMemorySafe = true;
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
8787_error,
|
||||
_assembly.location(),
|
||||
"Unexpected value for @solidity tag in inline assembly: " + value
|
||||
);
|
||||
}
|
||||
else if (duplicates.insert(value).second)
|
||||
m_errorReporter.warning(
|
||||
4377_error,
|
||||
_assembly.location(),
|
||||
"Value for @solidity tag in inline assembly specified multiple times: " + value
|
||||
);
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
6269_error,
|
||||
_assembly.location(),
|
||||
"Unexpected NatSpec tag \"" + tagName + "\" with value \"" + tagValue.content + "\" in inline assembly."
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DocStringTagParser::checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
StructurallyDocumented const& _node,
|
||||
|
||||
@@ -48,6 +48,7 @@ private:
|
||||
bool visit(ModifierDefinition const& _modifier) override;
|
||||
bool visit(EventDefinition const& _event) override;
|
||||
bool visit(ErrorDefinition const& _error) override;
|
||||
bool visit(InlineAssembly const& _assembly) override;
|
||||
|
||||
void checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
|
||||
@@ -334,6 +334,27 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation)
|
||||
|
||||
bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
if (_inlineAssembly.flags())
|
||||
for (auto flag: *_inlineAssembly.flags())
|
||||
{
|
||||
if (*flag == "memory-safe")
|
||||
{
|
||||
if (_inlineAssembly.annotation().markedMemorySafe)
|
||||
m_errorReporter.syntaxError(
|
||||
7026_error,
|
||||
_inlineAssembly.location(),
|
||||
"Inline assembly marked memory-safe multiple times."
|
||||
);
|
||||
_inlineAssembly.annotation().markedMemorySafe = true;
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
4430_error,
|
||||
_inlineAssembly.location(),
|
||||
"Unknown inline assembly flag: \"" + *flag + "\""
|
||||
);
|
||||
}
|
||||
|
||||
if (!m_useYulOptimizer)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -763,6 +763,7 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
|
||||
|
||||
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
bool lvalueAccessToMemoryVariable = false;
|
||||
// External references have already been resolved in a prior stage and stored in the annotation.
|
||||
// We run the resolve step again regardless.
|
||||
yul::ExternalIdentifierAccess::Resolver identifierAccess = [&](
|
||||
@@ -780,6 +781,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
{
|
||||
solAssert(var->type(), "Expected variable type!");
|
||||
if (_context == yul::IdentifierContext::LValue && var->type()->dataStoredIn(DataLocation::Memory))
|
||||
lvalueAccessToMemoryVariable = true;
|
||||
if (var->immutable())
|
||||
{
|
||||
m_errorReporter.typeError(3773_error, nativeLocationOf(_identifier), "Assembly access to immutable variables is not supported.");
|
||||
@@ -967,8 +970,11 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
identifierAccess
|
||||
);
|
||||
if (!analyzer.analyze(_inlineAssembly.operations()))
|
||||
return false;
|
||||
return true;
|
||||
solAssert(m_errorReporter.hasErrors());
|
||||
_inlineAssembly.annotation().hasMemoryEffects =
|
||||
lvalueAccessToMemoryVariable ||
|
||||
(analyzer.sideEffects().memory != yul::SideEffects::None);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(IfStatement const& _ifStatement)
|
||||
|
||||
@@ -256,10 +256,9 @@ void ViewPureChecker::reportMutability(
|
||||
m_errorReporter.typeError(
|
||||
8961_error,
|
||||
_location,
|
||||
"Function declared as " +
|
||||
"Function cannot be declared as " +
|
||||
stateMutabilityToString(m_currentFunction->stateMutability()) +
|
||||
", but this expression (potentially) modifies the state and thus "
|
||||
"requires non-payable (the default) or payable."
|
||||
" because this expression (potentially) modifies the state."
|
||||
);
|
||||
m_errors = true;
|
||||
}
|
||||
|
||||
@@ -1463,19 +1463,26 @@ public:
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _docString,
|
||||
yul::Dialect const& _dialect,
|
||||
ASTPointer<std::vector<ASTPointer<ASTString>>> _flags,
|
||||
std::shared_ptr<yul::Block> _operations
|
||||
):
|
||||
Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(std::move(_operations)) {}
|
||||
Statement(_id, _location, _docString),
|
||||
m_dialect(_dialect),
|
||||
m_flags(move(_flags)),
|
||||
m_operations(std::move(_operations))
|
||||
{}
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
yul::Dialect const& dialect() const { return m_dialect; }
|
||||
yul::Block const& operations() const { return *m_operations; }
|
||||
ASTPointer<std::vector<ASTPointer<ASTString>>> const& flags() const { return m_flags; }
|
||||
|
||||
InlineAssemblyAnnotation& annotation() const override;
|
||||
|
||||
private:
|
||||
yul::Dialect const& m_dialect;
|
||||
ASTPointer<std::vector<ASTPointer<ASTString>>> m_flags;
|
||||
std::shared_ptr<yul::Block> m_operations;
|
||||
};
|
||||
|
||||
|
||||
@@ -220,6 +220,10 @@ struct InlineAssemblyAnnotation: StatementAnnotation
|
||||
std::map<yul::Identifier const*, ExternalIdentifierInfo> externalReferences;
|
||||
/// Information generated during analysis phase.
|
||||
std::shared_ptr<yul::AsmAnalysisInfo> analysisInfo;
|
||||
/// True, if the assembly block was annotated to be memory-safe.
|
||||
bool markedMemorySafe = false;
|
||||
/// True, if the assembly block involves any memory opcode or assigns to variables in memory.
|
||||
SetOnce<bool> hasMemoryEffects;
|
||||
};
|
||||
|
||||
struct BlockAnnotation: StatementAnnotation, ScopableAnnotation
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <libsolutil/JSON.h>
|
||||
#include <libsolutil/UTF8.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
|
||||
@@ -493,24 +494,36 @@ bool ASTJsonConverter::visit(ModifierInvocation const& _node)
|
||||
bool ASTJsonConverter::visit(EventDefinition const& _node)
|
||||
{
|
||||
m_inEvent = true;
|
||||
setJsonNode(_node, "EventDefinition", {
|
||||
std::vector<pair<string, Json::Value>> _attributes = {
|
||||
make_pair("name", _node.name()),
|
||||
make_pair("nameLocation", sourceLocationToString(_node.nameLocation())),
|
||||
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
|
||||
make_pair("parameters", toJson(_node.parameterList())),
|
||||
make_pair("anonymous", _node.isAnonymous())
|
||||
});
|
||||
};
|
||||
if (m_stackState >= CompilerStack::State::AnalysisPerformed)
|
||||
_attributes.emplace_back(
|
||||
make_pair(
|
||||
"eventSelector",
|
||||
toHex(u256(h256::Arith(util::keccak256(_node.functionType(true)->externalSignature()))))
|
||||
));
|
||||
|
||||
setJsonNode(_node, "EventDefinition", std::move(_attributes));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ASTJsonConverter::visit(ErrorDefinition const& _node)
|
||||
{
|
||||
setJsonNode(_node, "ErrorDefinition", {
|
||||
std::vector<pair<string, Json::Value>> _attributes = {
|
||||
make_pair("name", _node.name()),
|
||||
make_pair("nameLocation", sourceLocationToString(_node.nameLocation())),
|
||||
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
|
||||
make_pair("parameters", toJson(_node.parameterList()))
|
||||
});
|
||||
};
|
||||
if (m_stackState >= CompilerStack::State::AnalysisPerformed)
|
||||
_attributes.emplace_back(make_pair("errorSelector", _node.functionType(true)->externalIdentifierHex()));
|
||||
|
||||
setJsonNode(_node, "ErrorDefinition", std::move(_attributes));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -587,11 +600,23 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node)
|
||||
for (Json::Value& it: externalReferences | ranges::views::values)
|
||||
externalReferencesJson.append(std::move(it));
|
||||
|
||||
setJsonNode(_node, "InlineAssembly", {
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))),
|
||||
make_pair("externalReferences", std::move(externalReferencesJson)),
|
||||
make_pair("evmVersion", dynamic_cast<solidity::yul::EVMDialect const&>(_node.dialect()).evmVersion().name())
|
||||
});
|
||||
};
|
||||
|
||||
if (_node.flags())
|
||||
{
|
||||
Json::Value flags(Json::arrayValue);
|
||||
for (auto const& flag: *_node.flags())
|
||||
if (flag)
|
||||
flags.append(*flag);
|
||||
else
|
||||
flags.append(Json::nullValue);
|
||||
attributes.emplace_back(make_pair("flags", move(flags)));
|
||||
}
|
||||
setJsonNode(_node, "InlineAssembly", move(attributes));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -626,11 +626,24 @@ ASTPointer<InlineAssembly> ASTJsonImporter::createInlineAssembly(Json::Value con
|
||||
astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!");
|
||||
|
||||
yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value());
|
||||
ASTPointer<vector<ASTPointer<ASTString>>> flags;
|
||||
if (_node.isMember("flags"))
|
||||
{
|
||||
flags = make_shared<vector<ASTPointer<ASTString>>>();
|
||||
Json::Value const& flagsNode = _node["flags"];
|
||||
astAssert(flagsNode.isArray(), "Assembly flags must be an array.");
|
||||
for (Json::ArrayIndex i = 0; i < flagsNode.size(); ++i)
|
||||
{
|
||||
astAssert(flagsNode[i].isString(), "Assembly flag must be a string.");
|
||||
flags->emplace_back(make_shared<ASTString>(flagsNode[i].asString()));
|
||||
}
|
||||
}
|
||||
shared_ptr<yul::Block> operations = make_shared<yul::Block>(yul::AsmJsonImporter(m_sourceNames).createBlock(member(_node, "AST")));
|
||||
return createASTNode<InlineAssembly>(
|
||||
_node,
|
||||
nullOrASTString(_node, "documentation"),
|
||||
dialect,
|
||||
move(flags),
|
||||
operations
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,4 +54,6 @@ ReturnInfo::ReturnInfo(EVMVersion const& _evmVersion, FunctionType const& _funct
|
||||
estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
|
||||
}
|
||||
}
|
||||
if (dynamicReturnSize)
|
||||
solAssert(estimatedReturnSize == 0);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,14 @@ YulArity YulArity::fromType(FunctionType const& _functionType)
|
||||
};
|
||||
}
|
||||
|
||||
string IRNames::externalFunctionABIWrapper(Declaration const& _functionOrVarDecl)
|
||||
{
|
||||
if (auto const* function = dynamic_cast<FunctionDefinition const*>(&_functionOrVarDecl))
|
||||
solAssert(!function->isConstructor());
|
||||
|
||||
return "external_fun_" + _functionOrVarDecl.name() + "_" + to_string(_functionOrVarDecl.id());
|
||||
}
|
||||
|
||||
string IRNames::function(FunctionDefinition const& _function)
|
||||
{
|
||||
if (_function.isConstructor())
|
||||
|
||||
@@ -49,6 +49,7 @@ struct YulArity
|
||||
|
||||
struct IRNames
|
||||
{
|
||||
static std::string externalFunctionABIWrapper(Declaration const& _functionOrVardecl);
|
||||
static std::string function(FunctionDefinition const& _function);
|
||||
static std::string function(VariableDeclaration const& _varDecl);
|
||||
static std::string modifierInvocation(ModifierInvocation const& _modifierInvocation);
|
||||
|
||||
@@ -160,8 +160,8 @@ public:
|
||||
|
||||
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
|
||||
|
||||
bool inlineAssemblySeen() const { return m_inlineAssemblySeen; }
|
||||
void setInlineAssemblySeen() { m_inlineAssemblySeen = true; }
|
||||
bool memoryUnsafeInlineAssemblySeen() const { return m_memoryUnsafeInlineAssemblySeen; }
|
||||
void setMemoryUnsafeInlineAssemblySeen() { m_memoryUnsafeInlineAssemblySeen = true; }
|
||||
|
||||
/// @returns the runtime ID to be used for the function in the dispatch routine
|
||||
/// and for internal function pointers.
|
||||
@@ -202,8 +202,8 @@ private:
|
||||
/// Whether to use checked or wrapping arithmetic.
|
||||
Arithmetic m_arithmetic = Arithmetic::Checked;
|
||||
|
||||
/// Flag indicating whether any inline assembly block was seen.
|
||||
bool m_inlineAssemblySeen = false;
|
||||
/// Flag indicating whether any memory-unsafe inline assembly block was seen.
|
||||
bool m_memoryUnsafeInlineAssemblySeen = false;
|
||||
|
||||
/// Function definitions queued for code generation. They're the Solidity functions whose calls
|
||||
/// were discovered by the IR generator during AST traversal.
|
||||
|
||||
@@ -213,8 +213,8 @@ string IRGenerator::generate(
|
||||
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||
|
||||
// This has to be called only after all other code generation for the creation object is complete.
|
||||
bool creationInvolvesAssembly = m_context.inlineAssemblySeen();
|
||||
t("memoryInitCreation", memoryInit(!creationInvolvesAssembly));
|
||||
bool creationInvolvesMemoryUnsafeAssembly = m_context.memoryUnsafeInlineAssemblySeen();
|
||||
t("memoryInitCreation", memoryInit(!creationInvolvesMemoryUnsafeAssembly));
|
||||
t("useSrcMapCreation", formatUseSrcMap(m_context));
|
||||
|
||||
resetContext(_contract, ExecutionContext::Deployed);
|
||||
@@ -239,8 +239,8 @@ string IRGenerator::generate(
|
||||
t("useSrcMapDeployed", formatUseSrcMap(m_context));
|
||||
|
||||
// This has to be called only after all other code generation for the deployed object is complete.
|
||||
bool deployedInvolvesAssembly = m_context.inlineAssemblySeen();
|
||||
t("memoryInitDeployed", memoryInit(!deployedInvolvesAssembly));
|
||||
bool deployedInvolvesMemoryUnsafeAssembly = m_context.memoryUnsafeInlineAssemblySeen();
|
||||
t("memoryInitDeployed", memoryInit(!deployedInvolvesMemoryUnsafeAssembly));
|
||||
|
||||
solAssert(_contract.annotation().creationCallGraph->get() != nullptr, "");
|
||||
solAssert(_contract.annotation().deployedCallGraph->get() != nullptr, "");
|
||||
@@ -735,6 +735,44 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
||||
});
|
||||
}
|
||||
|
||||
string IRGenerator::generateExternalFunction(ContractDefinition const& _contract, FunctionType const& _functionType)
|
||||
{
|
||||
string functionName = IRNames::externalFunctionABIWrapper(_functionType.declaration());
|
||||
return m_context.functionCollector().createFunction(functionName, [&](vector<string>&, vector<string>&) -> string {
|
||||
Whiskers t(R"X(
|
||||
<callValueCheck>
|
||||
<?+params>let <params> := </+params> <abiDecode>(4, calldatasize())
|
||||
<?+retParams>let <retParams> := </+retParams> <function>(<params>)
|
||||
let memPos := <allocateUnbounded>()
|
||||
let memEnd := <abiEncode>(memPos <?+retParams>,</+retParams> <retParams>)
|
||||
return(memPos, sub(memEnd, memPos))
|
||||
)X");
|
||||
t("callValueCheck", (_functionType.isPayable() || _contract.isLibrary()) ? "" : callValueCheck());
|
||||
|
||||
unsigned paramVars = make_shared<TupleType>(_functionType.parameterTypes())->sizeOnStack();
|
||||
unsigned retVars = make_shared<TupleType>(_functionType.returnParameterTypes())->sizeOnStack();
|
||||
|
||||
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
|
||||
t("abiDecode", abiFunctions.tupleDecoder(_functionType.parameterTypes()));
|
||||
t("params", suffixedVariableNameList("param_", 0, paramVars));
|
||||
t("retParams", suffixedVariableNameList("ret_", 0, retVars));
|
||||
|
||||
if (FunctionDefinition const* funDef = dynamic_cast<FunctionDefinition const*>(&_functionType.declaration()))
|
||||
{
|
||||
solAssert(!funDef->isConstructor());
|
||||
t("function", m_context.enqueueFunctionForCodeGeneration(*funDef));
|
||||
}
|
||||
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(&_functionType.declaration()))
|
||||
t("function", generateGetter(*varDecl));
|
||||
else
|
||||
solAssert(false, "Unexpected declaration for function!");
|
||||
|
||||
t("allocateUnbounded", m_utils.allocateUnboundedFunction());
|
||||
t("abiEncode", abiFunctions.tupleEncoder(_functionType.returnParameterTypes(), _functionType.returnParameterTypes(), _contract.isLibrary()));
|
||||
return t.render();
|
||||
});
|
||||
}
|
||||
|
||||
string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
IRGeneratorForStatements generator(m_context, m_utils);
|
||||
@@ -976,12 +1014,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
{
|
||||
// <functionName>
|
||||
<delegatecallCheck>
|
||||
<callValueCheck>
|
||||
<?+params>let <params> := </+params> <abiDecode>(4, calldatasize())
|
||||
<?+retParams>let <retParams> := </+retParams> <function>(<params>)
|
||||
let memPos := <allocateUnbounded>()
|
||||
let memEnd := <abiEncode>(memPos <?+retParams>,</+retParams> <retParams>)
|
||||
return(memPos, sub(memEnd, memPos))
|
||||
<externalFunction>()
|
||||
}
|
||||
</cases>
|
||||
default {}
|
||||
@@ -1011,25 +1044,8 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
"() }";
|
||||
}
|
||||
templ["delegatecallCheck"] = delegatecallCheck;
|
||||
templ["callValueCheck"] = (type->isPayable() || _contract.isLibrary()) ? "" : callValueCheck();
|
||||
|
||||
unsigned paramVars = make_shared<TupleType>(type->parameterTypes())->sizeOnStack();
|
||||
unsigned retVars = make_shared<TupleType>(type->returnParameterTypes())->sizeOnStack();
|
||||
|
||||
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
|
||||
templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes());
|
||||
templ["params"] = suffixedVariableNameList("param_", 0, paramVars);
|
||||
templ["retParams"] = suffixedVariableNameList("ret_", 0, retVars);
|
||||
|
||||
if (FunctionDefinition const* funDef = dynamic_cast<FunctionDefinition const*>(&type->declaration()))
|
||||
templ["function"] = m_context.enqueueFunctionForCodeGeneration(*funDef);
|
||||
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(&type->declaration()))
|
||||
templ["function"] = generateGetter(*varDecl);
|
||||
else
|
||||
solAssert(false, "Unexpected declaration for function!");
|
||||
|
||||
templ["allocateUnbounded"] = m_utils.allocateUnboundedFunction();
|
||||
templ["abiEncode"] = abiFunctions.tupleEncoder(type->returnParameterTypes(), type->returnParameterTypes(), _contract.isLibrary());
|
||||
templ["externalFunction"] = generateExternalFunction(_contract, *type);
|
||||
}
|
||||
t("cases", functions);
|
||||
FunctionDefinition const* etherReceiver = _contract.receiveFunction();
|
||||
|
||||
@@ -102,6 +102,9 @@ private:
|
||||
/// Generates a getter for the given declaration and returns its name
|
||||
std::string generateGetter(VariableDeclaration const& _varDecl);
|
||||
|
||||
/// Generates the external part (ABI decoding and encoding) of a function or getter.
|
||||
std::string generateExternalFunction(ContractDefinition const& _contract, FunctionType const& _functionType);
|
||||
|
||||
/// Generates code that assigns the initial value of the respective type.
|
||||
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
|
||||
|
||||
|
||||
@@ -2138,7 +2138,8 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
|
||||
{
|
||||
setLocation(_inlineAsm);
|
||||
m_context.setInlineAssemblySeen();
|
||||
if (*_inlineAsm.annotation().hasMemoryEffects && !_inlineAsm.annotation().markedMemorySafe)
|
||||
m_context.setMemoryUnsafeInlineAssemblySeen();
|
||||
CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences};
|
||||
|
||||
yul::Statement modified = bodyCopier(_inlineAsm.operations());
|
||||
@@ -2505,6 +2506,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
appendCode() << "mstore(add(" << m_utils.allocateUnboundedFunction() << "() , " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n";
|
||||
}
|
||||
|
||||
// NOTE: When the expected size of returndata is static, we pass that in to the call opcode and it gets copied automatically.
|
||||
// When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy().
|
||||
Whiskers templ(R"(
|
||||
<?checkExtcodesize>
|
||||
if iszero(extcodesize(<address>)) { <revertNoCode>() }
|
||||
@@ -2514,22 +2517,29 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
mstore(<pos>, <shl28>(<funSel>))
|
||||
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
|
||||
|
||||
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
||||
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <staticReturndataSize>)
|
||||
<?noTryCall>
|
||||
if iszero(<success>) { <forwardingRevert>() }
|
||||
</noTryCall>
|
||||
<?+retVars> let <retVars> </+retVars>
|
||||
if <success> {
|
||||
<?dynamicReturnSize>
|
||||
// copy dynamic return data out
|
||||
returndatacopy(<pos>, 0, returndatasize())
|
||||
</dynamicReturnSize>
|
||||
<?isReturndataSizeDynamic>
|
||||
let <returnDataSizeVar> := returndatasize()
|
||||
returndatacopy(<pos>, 0, <returnDataSizeVar>)
|
||||
<!isReturndataSizeDynamic>
|
||||
let <returnDataSizeVar> := <staticReturndataSize>
|
||||
<?supportsReturnData>
|
||||
if gt(<returnDataSizeVar>, returndatasize()) {
|
||||
<returnDataSizeVar> := returndatasize()
|
||||
}
|
||||
</supportsReturnData>
|
||||
</isReturndataSizeDynamic>
|
||||
|
||||
// update freeMemoryPointer according to dynamic return size
|
||||
<finalizeAllocation>(<pos>, <returnSize>)
|
||||
<finalizeAllocation>(<pos>, <returnDataSizeVar>)
|
||||
|
||||
// decode return parameters from external try-call into retVars
|
||||
<?+retVars> <retVars> := </+retVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
||||
<?+retVars> <retVars> := </+retVars> <abiDecode>(<pos>, add(<pos>, <returnDataSizeVar>))
|
||||
}
|
||||
)");
|
||||
templ("revertNoCode", m_utils.revertReasonIfDebugFunction("Target contract does not contain code"));
|
||||
@@ -2558,21 +2568,18 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
templ("funSel", IRVariable(_functionCall.expression()).part("functionSelector").name());
|
||||
templ("address", IRVariable(_functionCall.expression()).part("address").name());
|
||||
|
||||
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
|
||||
// This ensures it can catch badly formatted input from external calls.
|
||||
if (m_context.evmVersion().supportsReturndata())
|
||||
templ("returnSize", "returndatasize()");
|
||||
else
|
||||
templ("returnSize", to_string(returnInfo.estimatedReturnSize));
|
||||
|
||||
templ("reservedReturnSize", returnInfo.dynamicReturnSize ? "0" : to_string(returnInfo.estimatedReturnSize));
|
||||
if (returnInfo.dynamicReturnSize)
|
||||
solAssert(m_context.evmVersion().supportsReturndata());
|
||||
templ("returnDataSizeVar", m_context.newYulVariable());
|
||||
templ("staticReturndataSize", to_string(returnInfo.estimatedReturnSize));
|
||||
templ("supportsReturnData", m_context.evmVersion().supportsReturndata());
|
||||
|
||||
string const retVars = IRVariable(_functionCall).commaSeparatedList();
|
||||
templ("retVars", retVars);
|
||||
solAssert(retVars.empty() == returnInfo.returnTypes.empty());
|
||||
|
||||
templ("abiDecode", m_context.abiFunctions().tupleDecoder(returnInfo.returnTypes, true));
|
||||
templ("dynamicReturnSize", returnInfo.dynamicReturnSize);
|
||||
templ("isReturndataSizeDynamic", returnInfo.dynamicReturnSize);
|
||||
|
||||
templ("noTryCall", !_functionCall.annotation().tryCall);
|
||||
|
||||
|
||||
@@ -276,14 +276,8 @@ boost::filesystem::path FileReader::normalizeCLIPathForVFS(
|
||||
// If the path is on the same drive as the working dir, for portability we prefer not to
|
||||
// include the root name. Do this only for non-UNC paths - my experiments show that on Windows
|
||||
// when the working dir is an UNC path, / does not not actually refer to the root of the UNC path.
|
||||
boost::filesystem::path normalizedRootPath = normalizedPath.root_path();
|
||||
if (!isUNCPath(normalizedPath))
|
||||
{
|
||||
boost::filesystem::path workingDirRootPath = canonicalWorkDir.root_path();
|
||||
// Ignore drive letter case on Windows (C:\ <=> c:\).
|
||||
if (boost::filesystem::equivalent(normalizedRootPath, workingDirRootPath))
|
||||
normalizedRootPath = "/";
|
||||
}
|
||||
|
||||
boost::filesystem::path normalizedRootPath = normalizeCLIRootPathForVFS(normalizedPath, canonicalWorkDir);
|
||||
|
||||
// lexically_normal() will not squash paths like "/../../" into "/". We have to do it manually.
|
||||
boost::filesystem::path dotDotPrefix = absoluteDotDotPrefix(normalizedPath);
|
||||
@@ -308,6 +302,27 @@ boost::filesystem::path FileReader::normalizeCLIPathForVFS(
|
||||
return normalizedPathNoDotDot;
|
||||
}
|
||||
|
||||
boost::filesystem::path FileReader::normalizeCLIRootPathForVFS(
|
||||
boost::filesystem::path const& _path,
|
||||
boost::filesystem::path const& _workDir
|
||||
)
|
||||
{
|
||||
solAssert(_workDir.is_absolute(), "");
|
||||
|
||||
boost::filesystem::path absolutePath = boost::filesystem::absolute(_path, _workDir);
|
||||
boost::filesystem::path rootPath = absolutePath.root_path();
|
||||
boost::filesystem::path baseRootPath = _workDir.root_path();
|
||||
|
||||
if (isUNCPath(absolutePath))
|
||||
return rootPath;
|
||||
|
||||
// Ignore drive letter case on Windows (C:\ <=> c:\).
|
||||
if (boost::filesystem::equivalent(rootPath, baseRootPath))
|
||||
return "/";
|
||||
|
||||
return rootPath;
|
||||
}
|
||||
|
||||
bool FileReader::isPathPrefix(boost::filesystem::path const& _prefix, boost::filesystem::path const& _path)
|
||||
{
|
||||
solAssert(!_prefix.empty() && !_path.empty(), "");
|
||||
|
||||
@@ -115,6 +115,17 @@ public:
|
||||
SymlinkResolution _symlinkResolution = SymlinkResolution::Disabled
|
||||
);
|
||||
|
||||
/// Normalizes a root path by excluding, in some cases, its root name.
|
||||
/// The function is used for better portability, and intended to omit root name
|
||||
/// if the path can be used without it.
|
||||
/// @param _path Path to normalize the root path.
|
||||
/// @param _workDir Current working directory path, must be absolute.
|
||||
/// @returns a normalized root path.
|
||||
static boost::filesystem::path normalizeCLIRootPathForVFS(
|
||||
boost::filesystem::path const& _path,
|
||||
boost::filesystem::path const& _workDir = boost::filesystem::current_path()
|
||||
);
|
||||
|
||||
/// @returns true if all the path components of @a _prefix are present at the beginning of @a _path.
|
||||
/// Both paths must be absolute (or have slash as root) and normalized (no . or .. segments, no
|
||||
/// multiple consecutive slashes).
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/optimiser/Suite.h>
|
||||
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libevmasm/Disassemble.h>
|
||||
|
||||
#include <libsmtutil/Exceptions.h>
|
||||
|
||||
|
||||
@@ -1321,13 +1321,28 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con
|
||||
advance();
|
||||
}
|
||||
|
||||
ASTPointer<vector<ASTPointer<ASTString>>> flags;
|
||||
if (m_scanner->currentToken() == Token::LParen)
|
||||
{
|
||||
flags = make_shared<vector<ASTPointer<ASTString>>>();
|
||||
do
|
||||
{
|
||||
advance();
|
||||
expectToken(Token::StringLiteral, false);
|
||||
flags->emplace_back(make_shared<ASTString>(m_scanner->currentLiteral()));
|
||||
advance();
|
||||
}
|
||||
while (m_scanner->currentToken() == Token::Comma);
|
||||
expectToken(Token::RParen);
|
||||
}
|
||||
|
||||
yul::Parser asmParser(m_errorReporter, dialect);
|
||||
shared_ptr<yul::Block> block = asmParser.parseInline(m_scanner);
|
||||
if (block == nullptr)
|
||||
BOOST_THROW_EXCEPTION(FatalError());
|
||||
|
||||
location.end = nativeLocationOf(*block).end;
|
||||
return make_shared<InlineAssembly>(nextID(), location, _docString, dialect, block);
|
||||
return make_shared<InlineAssembly>(nextID(), location, _docString, dialect, move(flags), block);
|
||||
}
|
||||
|
||||
ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _docString)
|
||||
|
||||
Reference in New Issue
Block a user