mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Using for for operators.
This commit is contained in:
@@ -63,6 +63,7 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation)
|
||||
case Token::Or:
|
||||
case Token::And:
|
||||
{
|
||||
solAssert(!_operation.annotation().userDefinedFunction);
|
||||
visitNode(_operation);
|
||||
appendControlFlow(_operation.leftExpression());
|
||||
|
||||
@@ -73,10 +74,41 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation)
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return ASTConstVisitor::visit(_operation);
|
||||
{
|
||||
ASTConstVisitor::visit(_operation);
|
||||
if (_operation.annotation().userDefinedFunction)
|
||||
{
|
||||
solAssert(!m_currentNode->resolveFunctionCall(nullptr));
|
||||
m_currentNode->functionCall = _operation.annotation().userDefinedFunction;
|
||||
|
||||
auto nextNode = newLabel();
|
||||
|
||||
connect(m_currentNode, nextNode);
|
||||
m_currentNode = nextNode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ControlFlowBuilder::visit(UnaryOperation const& _operation)
|
||||
{
|
||||
solAssert(!!m_currentNode, "");
|
||||
|
||||
ASTConstVisitor::visit(_operation);
|
||||
if (_operation.annotation().userDefinedFunction)
|
||||
{
|
||||
solAssert(!m_currentNode->resolveFunctionCall(nullptr));
|
||||
m_currentNode->functionCall = _operation.annotation().userDefinedFunction;
|
||||
|
||||
auto nextNode = newLabel();
|
||||
|
||||
connect(m_currentNode, nextNode);
|
||||
m_currentNode = nextNode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ControlFlowBuilder::visit(Conditional const& _conditional)
|
||||
{
|
||||
solAssert(!!m_currentNode, "");
|
||||
@@ -300,7 +332,7 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall)
|
||||
_functionCall.expression().accept(*this);
|
||||
ASTNode::listAccept(_functionCall.arguments(), *this);
|
||||
|
||||
solAssert(!m_currentNode->functionCall);
|
||||
solAssert(!m_currentNode->resolveFunctionCall(nullptr));
|
||||
m_currentNode->functionCall = &_functionCall;
|
||||
|
||||
auto nextNode = newLabel();
|
||||
|
||||
@@ -50,6 +50,7 @@ private:
|
||||
|
||||
// Visits for constructing the control flow.
|
||||
bool visit(BinaryOperation const& _operation) override;
|
||||
bool visit(UnaryOperation const& _operation) override;
|
||||
bool visit(Conditional const& _conditional) override;
|
||||
bool visit(TryStatement const& _tryStatement) override;
|
||||
bool visit(IfStatement const& _ifStatement) override;
|
||||
|
||||
@@ -19,11 +19,21 @@
|
||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||
|
||||
#include <libsolidity/analysis/ControlFlowBuilder.h>
|
||||
#include <libsolutil/Visitor.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
FunctionDefinition const* CFGNode::resolveFunctionCall(ContractDefinition const* _mostDerivedContract) const
|
||||
{
|
||||
return std::visit(GenericVisitor{
|
||||
[=](FunctionCall const* _funCall) { return _funCall ? ASTNode::resolveFunctionCall(*_funCall, _mostDerivedContract) : nullptr; },
|
||||
[](FunctionDefinition const* _funDef) { return _funDef; }
|
||||
}, functionCall);
|
||||
}
|
||||
|
||||
bool CFG::constructFlow(ASTNode const& _astRoot)
|
||||
{
|
||||
_astRoot.accept(*this);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <stack>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
@@ -98,8 +99,13 @@ struct CFGNode
|
||||
std::vector<CFGNode*> entries;
|
||||
/// Exit nodes. All CFG nodes to which control flow may continue after this node.
|
||||
std::vector<CFGNode*> exits;
|
||||
/// Function call done by this node
|
||||
FunctionCall const* functionCall = nullptr;
|
||||
/// Function call done by this node, either a proper function call (allows virtual lookup)
|
||||
/// or a direct function definition reference (in case of an operator),
|
||||
/// or nullptr.
|
||||
std::variant<FunctionCall const*, FunctionDefinition const*> functionCall = static_cast<FunctionCall const*>(nullptr);
|
||||
/// @returns the actual function called given a most derived contract. If no function is called
|
||||
/// in this node, returns nullptr.
|
||||
FunctionDefinition const* resolveFunctionCall(ContractDefinition const* _mostDerivedContract) const;
|
||||
|
||||
/// Variable occurrences in the node.
|
||||
std::vector<VariableOccurrence> variableOccurrences;
|
||||
|
||||
@@ -81,27 +81,23 @@ void ControlFlowRevertPruner::findRevertStates()
|
||||
if (_node == functionFlow.exit)
|
||||
foundExit = true;
|
||||
|
||||
if (auto const* functionCall = _node->functionCall)
|
||||
auto const* resolvedFunction = _node->resolveFunctionCall(item.contract);
|
||||
if (resolvedFunction && resolvedFunction->isImplemented())
|
||||
{
|
||||
auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.contract);
|
||||
|
||||
if (resolvedFunction && resolvedFunction->isImplemented())
|
||||
CFG::FunctionContractTuple calledFunctionTuple{
|
||||
findScopeContract(*resolvedFunction, item.contract),
|
||||
resolvedFunction
|
||||
};
|
||||
switch (m_functions.at(calledFunctionTuple))
|
||||
{
|
||||
CFG::FunctionContractTuple calledFunctionTuple{
|
||||
findScopeContract(*resolvedFunction, item.contract),
|
||||
resolvedFunction
|
||||
};
|
||||
switch (m_functions.at(calledFunctionTuple))
|
||||
{
|
||||
case RevertState::Unknown:
|
||||
wakeUp[calledFunctionTuple].insert(item);
|
||||
foundUnknown = true;
|
||||
return;
|
||||
case RevertState::AllPathsRevert:
|
||||
return;
|
||||
case RevertState::HasNonRevertingPath:
|
||||
break;
|
||||
}
|
||||
case RevertState::Unknown:
|
||||
wakeUp[calledFunctionTuple].insert(item);
|
||||
foundUnknown = true;
|
||||
return;
|
||||
case RevertState::AllPathsRevert:
|
||||
return;
|
||||
case RevertState::HasNonRevertingPath:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,30 +131,26 @@ void ControlFlowRevertPruner::modifyFunctionFlows()
|
||||
FunctionFlow const& functionFlow = m_cfg.functionFlow(*item.first.function, item.first.contract);
|
||||
solidity::util::BreadthFirstSearch<CFGNode*>{{functionFlow.entry}}.run(
|
||||
[&](CFGNode* _node, auto&& _addChild) {
|
||||
if (auto const* functionCall = _node->functionCall)
|
||||
{
|
||||
auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.first.contract);
|
||||
auto const* resolvedFunction = _node->resolveFunctionCall(item.first.contract);
|
||||
if (resolvedFunction && resolvedFunction->isImplemented())
|
||||
switch (m_functions.at({findScopeContract(*resolvedFunction, item.first.contract), resolvedFunction}))
|
||||
{
|
||||
case RevertState::Unknown:
|
||||
[[fallthrough]];
|
||||
case RevertState::AllPathsRevert:
|
||||
// If the revert states of the functions do not
|
||||
// change anymore, we treat all "unknown" states as
|
||||
// "reverting", since they can only be caused by
|
||||
// recursion.
|
||||
for (CFGNode * node: _node->exits)
|
||||
ranges::remove(node->entries, _node);
|
||||
|
||||
if (resolvedFunction && resolvedFunction->isImplemented())
|
||||
switch (m_functions.at({findScopeContract(*resolvedFunction, item.first.contract), resolvedFunction}))
|
||||
{
|
||||
case RevertState::Unknown:
|
||||
[[fallthrough]];
|
||||
case RevertState::AllPathsRevert:
|
||||
// If the revert states of the functions do not
|
||||
// change anymore, we treat all "unknown" states as
|
||||
// "reverting", since they can only be caused by
|
||||
// recursion.
|
||||
for (CFGNode * node: _node->exits)
|
||||
ranges::remove(node->entries, _node);
|
||||
|
||||
_node->exits = {functionFlow.revert};
|
||||
functionFlow.revert->entries.push_back(_node);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
_node->exits = {functionFlow.revert};
|
||||
functionFlow.revert->entries.push_back(_node);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (CFGNode* exit: _node->exits)
|
||||
_addChild(exit);
|
||||
|
||||
@@ -204,6 +204,20 @@ bool FunctionCallGraphBuilder::visit(MemberAccess const& _memberAccess)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::visit(BinaryOperation const& _binaryOperation)
|
||||
{
|
||||
if (FunctionDefinition const* function = _binaryOperation.annotation().userDefinedFunction)
|
||||
functionReferenced(*function, true /* called directly */);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::visit(UnaryOperation const& _unaryOperation)
|
||||
{
|
||||
if (FunctionDefinition const* function = _unaryOperation.annotation().userDefinedFunction)
|
||||
functionReferenced(*function, true /* called directly */);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionCallGraphBuilder::visit(ModifierInvocation const& _modifierInvocation)
|
||||
{
|
||||
if (auto const* modifier = dynamic_cast<ModifierDefinition const*>(_modifierInvocation.name().annotation().referencedDeclaration))
|
||||
|
||||
@@ -72,6 +72,8 @@ private:
|
||||
bool visit(EmitStatement const& _emitStatement) override;
|
||||
bool visit(Identifier const& _identifier) override;
|
||||
bool visit(MemberAccess const& _memberAccess) override;
|
||||
bool visit(BinaryOperation const& _binaryOperation) override;
|
||||
bool visit(UnaryOperation const& _unaryOperation) override;
|
||||
bool visit(ModifierInvocation const& _modifierInvocation) override;
|
||||
bool visit(NewExpression const& _newExpression) override;
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
|
||||
|
||||
bool visit(Identifier const& _identifier) override
|
||||
{
|
||||
// TODO add user defined operators?
|
||||
if (m_currentConstVariable)
|
||||
if (auto var = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
|
||||
if (var->isConstant())
|
||||
|
||||
@@ -405,6 +405,12 @@ void SyntaxChecker::endVisit(ContractDefinition const&)
|
||||
|
||||
bool SyntaxChecker::visit(UsingForDirective const& _usingFor)
|
||||
{
|
||||
if (!_usingFor.usesBraces())
|
||||
solAssert(
|
||||
_usingFor.functionsAndOperators().size() == 1 &&
|
||||
!std::get<1>(_usingFor.functionsAndOperators().front())
|
||||
);
|
||||
|
||||
if (!m_currentContractKind && !_usingFor.typeName())
|
||||
m_errorReporter.syntaxError(
|
||||
8118_error,
|
||||
|
||||
@@ -1728,10 +1728,40 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
|
||||
else
|
||||
_operation.subExpression().accept(*this);
|
||||
Type const* subExprType = type(_operation.subExpression());
|
||||
TypeResult result = type(_operation.subExpression())->unaryOperatorResult(op);
|
||||
if (!result)
|
||||
|
||||
|
||||
// Check if the operator is built-in or user-defined.
|
||||
FunctionDefinition const* userDefinedOperator = subExprType->userDefinedOperator(
|
||||
_operation.getOperator(),
|
||||
*currentDefinitionScope()
|
||||
);
|
||||
_operation.annotation().userDefinedFunction = userDefinedOperator;
|
||||
FunctionType const* userDefinedFunctionType = nullptr;
|
||||
if (userDefinedOperator)
|
||||
userDefinedFunctionType = &dynamic_cast<FunctionType const&>(
|
||||
userDefinedOperator->libraryFunction() ?
|
||||
*userDefinedOperator->typeViaContractName() :
|
||||
*userDefinedOperator->type()
|
||||
);
|
||||
|
||||
TypeResult builtinResult = subExprType->unaryOperatorResult(op);
|
||||
|
||||
solAssert(!builtinResult || !userDefinedOperator);
|
||||
if (userDefinedOperator)
|
||||
{
|
||||
string description = "Unary operator " + string(TokenTraits::toString(op)) + " cannot be applied to type " + subExprType->humanReadableName() + "." + (!result.message().empty() ? " " + result.message() : "");
|
||||
solAssert(userDefinedFunctionType->parameterTypes().size() == 1);
|
||||
solAssert(userDefinedFunctionType->returnParameterTypes().size() == 1);
|
||||
solAssert(
|
||||
*userDefinedFunctionType->parameterTypes().at(0) ==
|
||||
*userDefinedFunctionType->returnParameterTypes().at(0)
|
||||
);
|
||||
_operation.annotation().type = userDefinedFunctionType->returnParameterTypes().at(0);
|
||||
}
|
||||
else if (builtinResult)
|
||||
_operation.annotation().type = builtinResult;
|
||||
else
|
||||
{
|
||||
string description = "Unary operator " + string(TokenTraits::toString(op)) + " cannot be applied to type " + subExprType->humanReadableName() + "." + (!builtinResult.message().empty() ? " " + builtinResult.message() : "");
|
||||
if (modifying)
|
||||
// Cannot just report the error, ignore the unary operator, and continue,
|
||||
// because the sub-expression was already processed with requireLValue()
|
||||
@@ -1740,10 +1770,12 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
|
||||
m_errorReporter.typeError(4907_error, _operation.location(), description);
|
||||
_operation.annotation().type = subExprType;
|
||||
}
|
||||
else
|
||||
_operation.annotation().type = result.get();
|
||||
|
||||
_operation.annotation().isConstant = false;
|
||||
_operation.annotation().isPure = !modifying && *_operation.subExpression().annotation().isPure;
|
||||
_operation.annotation().isPure =
|
||||
!modifying &&
|
||||
*_operation.subExpression().annotation().isPure &&
|
||||
(!userDefinedFunctionType || userDefinedFunctionType->isPure());
|
||||
_operation.annotation().isLValue = false;
|
||||
|
||||
return false;
|
||||
@@ -1753,10 +1785,35 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
|
||||
{
|
||||
Type const* leftType = type(_operation.leftExpression());
|
||||
Type const* rightType = type(_operation.rightExpression());
|
||||
TypeResult result = leftType->binaryOperatorResult(_operation.getOperator(), rightType);
|
||||
Type const* commonType = result.get();
|
||||
if (!commonType)
|
||||
{
|
||||
_operation.annotation().isLValue = false;
|
||||
_operation.annotation().isConstant = false;
|
||||
|
||||
// Check if the operator is built-in or user-defined.
|
||||
FunctionDefinition const* userDefinedOperator = leftType->userDefinedOperator(
|
||||
_operation.getOperator(),
|
||||
*currentDefinitionScope()
|
||||
);
|
||||
_operation.annotation().userDefinedFunction = userDefinedOperator;
|
||||
FunctionType const* userDefinedFunctionType = nullptr;
|
||||
if (userDefinedOperator)
|
||||
userDefinedFunctionType = &dynamic_cast<FunctionType const&>(
|
||||
userDefinedOperator->libraryFunction() ?
|
||||
*userDefinedOperator->typeViaContractName() :
|
||||
*userDefinedOperator->type()
|
||||
);
|
||||
_operation.annotation().isPure =
|
||||
*_operation.leftExpression().annotation().isPure &&
|
||||
*_operation.rightExpression().annotation().isPure &&
|
||||
(!userDefinedFunctionType || userDefinedFunctionType->isPure());
|
||||
|
||||
TypeResult builtinResult = leftType->binaryOperatorResult(_operation.getOperator(), rightType);
|
||||
Type const* commonType = leftType;
|
||||
|
||||
// Either the operator is user-defined or built-in.
|
||||
// TODO For enums, we have compare operators. Should we disallow overriding them?
|
||||
solAssert(!userDefinedOperator || !builtinResult);
|
||||
|
||||
if (!builtinResult && !userDefinedOperator)
|
||||
m_errorReporter.typeError(
|
||||
2271_error,
|
||||
_operation.location(),
|
||||
@@ -1766,22 +1823,33 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
|
||||
leftType->humanReadableName() +
|
||||
" and " +
|
||||
rightType->humanReadableName() + "." +
|
||||
(!result.message().empty() ? " " + result.message() : "")
|
||||
(!builtinResult.message().empty() ? " " + builtinResult.message() : "")
|
||||
);
|
||||
commonType = leftType;
|
||||
|
||||
if (builtinResult)
|
||||
commonType = builtinResult.get();
|
||||
else if (userDefinedOperator)
|
||||
{
|
||||
solAssert(
|
||||
userDefinedFunctionType->parameterTypes().size() == 2 &&
|
||||
*userDefinedFunctionType->parameterTypes().at(0) ==
|
||||
*userDefinedFunctionType->parameterTypes().at(1)
|
||||
);
|
||||
commonType = userDefinedFunctionType->parameterTypes().at(0);
|
||||
}
|
||||
|
||||
_operation.annotation().commonType = commonType;
|
||||
_operation.annotation().type =
|
||||
TokenTraits::isCompareOp(_operation.getOperator()) ?
|
||||
TypeProvider::boolean() :
|
||||
commonType;
|
||||
_operation.annotation().isPure =
|
||||
*_operation.leftExpression().annotation().isPure &&
|
||||
*_operation.rightExpression().annotation().isPure;
|
||||
_operation.annotation().isLValue = false;
|
||||
_operation.annotation().isConstant = false;
|
||||
|
||||
if (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL)
|
||||
if (userDefinedOperator)
|
||||
solAssert(
|
||||
userDefinedFunctionType->returnParameterTypes().size() == 1 &&
|
||||
*userDefinedFunctionType->returnParameterTypes().front() == *_operation.annotation().type
|
||||
);
|
||||
else if (builtinResult && (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL))
|
||||
{
|
||||
string operation = _operation.getOperator() == Token::Exp ? "exponentiation" : "shift";
|
||||
if (
|
||||
@@ -3784,7 +3852,7 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
||||
);
|
||||
solAssert(normalizedType);
|
||||
|
||||
for (ASTPointer<IdentifierPath> const& path: _usingFor.functionsOrLibrary())
|
||||
for (auto const& [path, operator_]: _usingFor.functionsAndOperators())
|
||||
{
|
||||
solAssert(path->annotation().referencedDeclaration);
|
||||
FunctionDefinition const& functionDefinition =
|
||||
@@ -3820,6 +3888,73 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
||||
": " + result.message()
|
||||
)
|
||||
);
|
||||
else if (operator_)
|
||||
{
|
||||
if (!_usingFor.typeName()->annotation().type->typeDefinition())
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
5332_error,
|
||||
path->location(),
|
||||
"Operators can only be implemented for user-defined types and not for contracts."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// "-" can be used as unary and binary operator.
|
||||
bool isUnaryNegation = (
|
||||
operator_ == Token::Sub &&
|
||||
functionType->parameterTypesIncludingSelf().size() == 1
|
||||
);
|
||||
if (
|
||||
(
|
||||
(TokenTraits::isBinaryOp(*operator_) && !isUnaryNegation) ||
|
||||
TokenTraits::isCompareOp(*operator_)
|
||||
) &&
|
||||
(
|
||||
functionType->parameterTypesIncludingSelf().size() != 2 ||
|
||||
*functionType->parameterTypesIncludingSelf().at(0) !=
|
||||
*functionType->parameterTypesIncludingSelf().at(1)
|
||||
)
|
||||
)
|
||||
m_errorReporter.typeError(
|
||||
1884_error,
|
||||
path->location(),
|
||||
"The function \"" + joinHumanReadable(path->path(), ".") + "\" "+
|
||||
"needs to have two parameters of equal type to be used for the operator " +
|
||||
TokenTraits::friendlyName(*operator_) +
|
||||
"."
|
||||
);
|
||||
if (
|
||||
(isUnaryNegation || (TokenTraits::isUnaryOp(*operator_) && *operator_ != Token::Add)) &&
|
||||
functionType->parameterTypesIncludingSelf().size() != 1
|
||||
)
|
||||
m_errorReporter.typeError(
|
||||
8112_error,
|
||||
path->location(),
|
||||
"The function \"" + joinHumanReadable(path->path(), ".") + "\" "+
|
||||
"needs to have exactly one parameter to be used for the operator " +
|
||||
TokenTraits::friendlyName(*operator_) +
|
||||
"."
|
||||
);
|
||||
Type const* expectedType =
|
||||
TokenTraits::isCompareOp(*operator_) ?
|
||||
dynamic_cast<Type const*>(TypeProvider::boolean()) :
|
||||
functionType->parameterTypesIncludingSelf().at(0);
|
||||
|
||||
if (
|
||||
functionType->returnParameterTypes().size() != 1 ||
|
||||
*functionType->returnParameterTypes().front() != *expectedType
|
||||
)
|
||||
m_errorReporter.typeError(
|
||||
7743_error,
|
||||
path->location(),
|
||||
"The function \"" + joinHumanReadable(path->path(), ".") + "\" "+
|
||||
"needs to return exactly one value of type " +
|
||||
expectedType->toString(true) +
|
||||
" to be used for the operator " +
|
||||
TokenTraits::friendlyName(*operator_) +
|
||||
"."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -323,6 +323,8 @@ ViewPureChecker::MutabilityAndLocation const& ViewPureChecker::modifierMutabilit
|
||||
return m_inferredMutability.at(&_modifier);
|
||||
}
|
||||
|
||||
// TODO needs to visit binaryoperation as well
|
||||
|
||||
void ViewPureChecker::endVisit(FunctionCall const& _functionCall)
|
||||
{
|
||||
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
|
||||
|
||||
Reference in New Issue
Block a user