Syntax for custom errors.

This commit is contained in:
chriseth
2021-02-01 18:26:31 +01:00
parent c7d1e1911e
commit 4d1fd84150
62 changed files with 651 additions and 52 deletions
@@ -124,7 +124,7 @@ bool DeclarationContainer::registerDeclaration(
// Do not warn about shadowing for structs and enums because their members are
// not accessible without prefixes. Also do not warn about event parameters
// because they do not participate in any proper scope.
bool special = _declaration.scope() && (_declaration.isStructMember() || _declaration.isEnumValue() || _declaration.isEventParameter());
bool special = _declaration.scope() && (_declaration.isStructMember() || _declaration.isEnumValue() || _declaration.isEventOrErrorParameter());
if (m_enclosingContainer && !special)
m_homonymCandidates.emplace_back(*_name, _location ? _location : &_declaration.location());
}
@@ -368,7 +368,7 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
}
// Find correct data location.
if (_variable.isEventParameter())
if (_variable.isEventOrErrorParameter())
{
solAssert(varLoc == Location::Unspecified, "");
typeLoc = DataLocation::Memory;
@@ -157,6 +157,13 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
return true;
}
bool DocStringAnalyser::visit(ErrorDefinition const& _error)
{
handleCallable(_error, _error, _error.annotation());
return true;
}
void DocStringAnalyser::handleCallable(
CallableDeclaration const& _callable,
StructurallyDocumented const& _node,
+1
View File
@@ -43,6 +43,7 @@ private:
bool visit(VariableDeclaration const& _variable) override;
bool visit(ModifierDefinition const& _modifier) override;
bool visit(EventDefinition const& _event) override;
bool visit(ErrorDefinition const& _error) override;
CallableDeclaration const* resolveInheritDoc(
std::set<CallableDeclaration const*> const& _baseFunctions,
@@ -85,6 +85,13 @@ bool DocStringTagParser::visit(EventDefinition const& _event)
return true;
}
bool DocStringTagParser::visit(ErrorDefinition const& _error)
{
handleCallable(_error, _error, _error.annotation());
return true;
}
void DocStringTagParser::checkParameters(
CallableDeclaration const& _callable,
StructurallyDocumented const& _node,
@@ -127,11 +134,14 @@ void DocStringTagParser::handleCallable(
)
{
static set<string> const validEventTags = set<string>{"dev", "notice", "return", "param"};
static set<string> const validErrorTags = set<string>{"dev", "notice", "param"};
static set<string> const validModifierTags = set<string>{"dev", "notice", "param", "inheritdoc"};
static set<string> const validTags = set<string>{"dev", "notice", "return", "param", "inheritdoc"};
if (dynamic_cast<EventDefinition const*>(&_callable))
parseDocStrings(_node, _annotation, validEventTags, "events");
else if (dynamic_cast<ErrorDefinition const*>(&_callable))
parseDocStrings(_node, _annotation, validErrorTags, "errors");
else if (dynamic_cast<ModifierDefinition const*>(&_callable))
parseDocStrings(_node, _annotation, validModifierTags, "modifiers");
else
@@ -44,6 +44,7 @@ private:
bool visit(VariableDeclaration const& _variable) override;
bool visit(ModifierDefinition const& _modifier) override;
bool visit(EventDefinition const& _event) override;
bool visit(ErrorDefinition const& _error) override;
void checkParameters(
CallableDeclaration const& _callable,
+2 -4
View File
@@ -83,10 +83,8 @@ inline vector<shared_ptr<MagicVariableDeclaration const>> constructMagicVariable
magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)),
magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)),
magicVarDecl("now", TypeProvider::uint256()),
magicVarDecl("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
magicVarDecl("require", TypeProvider::function(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
magicVarDecl("require", TypeProvider::function(strings{}, strings{}, FunctionType::Kind::Require, true, StateMutability::Pure)),
magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, true, StateMutability::Pure)),
magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)),
magicVarDecl("selfdestruct", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
magicVarDecl("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)),
+67
View File
@@ -85,6 +85,11 @@ bool PostTypeChecker::visit(FunctionCall const& _functionCall)
return callVisit(_functionCall);
}
void PostTypeChecker::endVisit(FunctionCall const& _functionCall)
{
callEndVisit(_functionCall);
}
bool PostTypeChecker::visit(Identifier const& _identifier)
{
return callVisit(_identifier);
@@ -314,6 +319,67 @@ private:
bool m_insideEmitStatement = false;
};
struct ErrorOutsideRequireRevertChecker: public PostTypeChecker::Checker
{
ErrorOutsideRequireRevertChecker(ErrorReporter& _errorReporter):
Checker(_errorReporter) {}
bool visit(FunctionCall const& _functionCall) override
{
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
return true;
auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
solAssert(functionType, "");
switch (functionType->kind())
{
case FunctionType::Kind::Require:
case FunctionType::Kind::Revert:
{
solAssert(!m_insideRequireRevert, "");
m_insideRequireRevert = true;
break;
}
case FunctionType::Kind::Error:
{
// This will not catch situations like
// revert(Error1(Error2())), but as long as we
// do not exit the expression context inside "require" and "revert",
// this will be caught by the type checker since errors
// do not have return values.
if (!m_insideRequireRevert)
m_errorReporter.typeError(
7757_error,
_functionCall.location(),
"Errors can only be created directly inside require or revert calls."
);
break;
}
default:
break;
}
return true;
}
void endVisit(FunctionCall const& _functionCall) override
{
if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
return;
auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
solAssert(functionType, "");
if (
functionType->kind() == FunctionType::Kind::Require ||
functionType->kind() == FunctionType::Kind::Revert
)
{
solAssert(m_insideRequireRevert, "");
m_insideRequireRevert = false;
}
}
private:
bool m_insideRequireRevert = false;
};
struct NoVariablesInInterfaceChecker: public PostTypeChecker::Checker
{
NoVariablesInInterfaceChecker(ErrorReporter& _errorReporter):
@@ -371,5 +437,6 @@ PostTypeChecker::PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_err
m_checkers.push_back(make_shared<OverrideSpecifierChecker>(_errorReporter));
m_checkers.push_back(make_shared<ModifierContextChecker>(_errorReporter));
m_checkers.push_back(make_shared<EventOutsideEmitChecker>(_errorReporter));
m_checkers.push_back(make_shared<ErrorOutsideRequireRevertChecker>(_errorReporter));
m_checkers.push_back(make_shared<NoVariablesInInterfaceChecker>(_errorReporter));
}
+1
View File
@@ -81,6 +81,7 @@ private:
void endVisit(EmitStatement const& _emit) override;
bool visit(FunctionCall const& _functionCall) override;
void endVisit(FunctionCall const& _functionCall) override;
bool visit(Identifier const& _identifier) override;
bool visit(MemberAccess const& _identifier) override;
+119 -25
View File
@@ -42,6 +42,7 @@
#include <range/v3/view/zip.hpp>
#include <range/v3/view/drop_exactly.hpp>
#include <range/v3/algorithm/count_if.hpp>
#include <memory>
#include <vector>
@@ -681,30 +682,12 @@ void TypeChecker::visitManually(
bool TypeChecker::visit(EventDefinition const& _eventDef)
{
solAssert(_eventDef.visibility() > Visibility::Internal, "");
unsigned numIndexed = 0;
for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters())
{
if (var->isIndexed())
numIndexed++;
if (type(*var)->containsNestedMapping())
m_errorReporter.typeError(
3448_error,
var->location(),
"Type containing a (nested) mapping is not allowed as event parameter type."
);
if (!type(*var)->interfaceType(false))
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type.");
if (
!useABICoderV2() &&
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
)
m_errorReporter.typeError(
3061_error,
var->location(),
"This type is only supported in ABI coder v2. "
"Use \"pragma abicoder v2;\" to enable the feature."
);
}
checkErrorAndEventParameters(_eventDef);
auto numIndexed = ranges::count_if(
_eventDef.parameters(),
[](ASTPointer<VariableDeclaration> const& var) { return var->isIndexed(); }
);
if (_eventDef.isAnonymous() && numIndexed > 4)
m_errorReporter.typeError(8598_error, _eventDef.location(), "More than 4 indexed arguments for anonymous event.");
else if (!_eventDef.isAnonymous() && numIndexed > 3)
@@ -712,6 +695,13 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
return true;
}
bool TypeChecker::visit(ErrorDefinition const& _errorDef)
{
solAssert(_errorDef.visibility() > Visibility::Internal, "");
checkErrorAndEventParameters(_errorDef);
return true;
}
void TypeChecker::endVisit(FunctionTypeName const& _funType)
{
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
@@ -2029,6 +2019,79 @@ void TypeChecker::typeCheckABIEncodeFunctions(
}
}
void TypeChecker::typeCheckRequireRevert(FunctionCall const& _functionCall, FunctionType::Kind _kind)
{
solAssert(_kind == FunctionType::Kind::Require || _kind == FunctionType::Kind::Revert, "");
// Check for named arguments
if (!_functionCall.names().empty())
{
m_errorReporter.typeError(
1886_error,
_functionCall.location(),
"Named arguments cannot be used for this function call."
);
return;
}
bool isRequire = _kind == FunctionType::Kind::Require;
size_t argsExpected = isRequire ? 1 : 0;
string name = isRequire ? "require" : "revert";
if (
_functionCall.arguments().size() != argsExpected &&
_functionCall.arguments().size() != argsExpected + 1
)
{
m_errorReporter.typeError(
7445_error,
_functionCall.location(),
"Function \"" +
name +
"\" needs " +
to_string(argsExpected) +
" or " +
to_string(argsExpected + 1) +
" arguments, but provided " +
to_string(_functionCall.arguments().size()) +
"."
);
return;
}
if (isRequire)
{
BoolResult result = type(*_functionCall.arguments().front())->isImplicitlyConvertibleTo(*TypeProvider::boolean());
if (!result)
m_errorReporter.typeError(
2956_error,
_functionCall.arguments().front()->location(),
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
type(*_functionCall.arguments().front())->toString() +
" to " +
TypeProvider::boolean()->toString(false) +
" requested." +
(result.message().empty() ? "" : " " + result.message())
);
}
// Event is omitted, nothing more to check.
if (_functionCall.arguments().size() == argsExpected)
return;
if (type(*_functionCall.arguments().back())->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()))
return;
Declaration const* declaration = nullptr;
if (auto const* errorCall = dynamic_cast<FunctionCall const*>(_functionCall.arguments().back().get()))
if (*errorCall->annotation().kind == FunctionCallKind::FunctionCall)
declaration = referencedDeclaration(errorCall->expression());
if (!dynamic_cast<ErrorDefinition const*>(declaration))
m_errorReporter.typeError(
4423_error,
_functionCall.arguments().back()->location(),
"Expected error or string."
);
}
void TypeChecker::typeCheckFunctionGeneralChecks(
FunctionCall const& _functionCall,
FunctionTypePointer _functionType
@@ -2248,7 +2311,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
_functionType->kind() == FunctionType::Kind::DelegateCall ||
_functionType->kind() == FunctionType::Kind::External ||
_functionType->kind() == FunctionType::Kind::Creation ||
_functionType->kind() == FunctionType::Kind::Event;
_functionType->kind() == FunctionType::Kind::Event ||
_functionType->kind() == FunctionType::Kind::Error;
if (callRequiresABIEncoding && !useABICoderV2())
{
@@ -2431,6 +2495,10 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::MetaType:
returnTypes = typeCheckMetaTypeFunctionAndRetrieveReturnType(_functionCall);
break;
case FunctionType::Kind::Require:
case FunctionType::Kind::Revert:
typeCheckRequireRevert(_functionCall, functionType->kind());
break;
default:
{
typeCheckFunctionCall(_functionCall, functionType);
@@ -3392,6 +3460,32 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
);
}
void TypeChecker::checkErrorAndEventParameters(CallableDeclaration const& _callable)
{
string kind = dynamic_cast<EventDefinition const*>(&_callable) ? "event" : "error";
for (ASTPointer<VariableDeclaration> const& var: _callable.parameters())
{
if (type(*var)->containsNestedMapping())
m_errorReporter.typeError(
3448_error,
var->location(),
"Type containing a (nested) mapping is not allowed as " + kind + " parameter type."
);
if (!type(*var)->interfaceType(false))
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as " + kind + " parameter type.");
if (
!useABICoderV2() &&
!typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */)
)
m_errorReporter.typeError(
3061_error,
var->location(),
"This type is only supported in ABI coder v2. "
"Use \"pragma abicoder v2;\" to enable the feature."
);
}
}
bool TypeChecker::contractDependenciesAreCyclic(
ContractDefinition const& _contract,
std::set<ContractDefinition const*> const& _seenContracts
+8
View File
@@ -111,6 +111,11 @@ private:
FunctionTypePointer _functionType
);
void typeCheckRequireRevert(
FunctionCall const& _functionCall,
FunctionType::Kind _kind
);
void endVisit(InheritanceSpecifier const& _inheritance) override;
void endVisit(ModifierDefinition const& _modifier) override;
bool visit(FunctionDefinition const& _function) override;
@@ -119,6 +124,7 @@ private:
/// case this is a base constructor call.
void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases);
bool visit(EventDefinition const& _eventDef) override;
bool visit(ErrorDefinition const& _errorDef) override;
void endVisit(FunctionTypeName const& _funType) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(IfStatement const& _ifStatement) override;
@@ -147,6 +153,8 @@ private:
void endVisit(Literal const& _literal) override;
void endVisit(UsingForDirective const& _usingForDirective) override;
void checkErrorAndEventParameters(CallableDeclaration const& _callable);
bool contractDependenciesAreCyclic(
ContractDefinition const& _contract,
std::set<ContractDefinition const*> const& _seenContracts = std::set<ContractDefinition const*>()