Using for for operators.

This commit is contained in:
chriseth
2022-09-28 11:32:03 +02:00
committed by wechman
parent 2201526a90
commit 3bd047f188
28 changed files with 607 additions and 93 deletions
+34 -2
View File
@@ -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;
+10
View File
@@ -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);
+8 -2
View File
@@ -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))
+2
View File
@@ -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;
+1
View File
@@ -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())
+6
View File
@@ -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,
+154 -19
View File
@@ -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_) +
"."
);
}
}
}
+2
View File
@@ -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)