From 888d7037cd267daee9ec06e088d978a0d1a2d30c Mon Sep 17 00:00:00 2001 From: Jason Cobb Date: Wed, 8 Apr 2020 13:38:30 -0400 Subject: [PATCH] Make FunctionCallAnnotation::kind a SetOnce --- liblangutil/ErrorReporter.cpp | 1 + libsolidity/analysis/PostTypeChecker.cpp | 2 +- libsolidity/analysis/StaticAnalyzer.cpp | 2 +- libsolidity/analysis/TypeChecker.cpp | 18 ++-- libsolidity/analysis/ViewPureChecker.cpp | 2 +- libsolidity/ast/ASTAnnotations.h | 5 +- libsolidity/ast/ASTJsonConverter.cpp | 9 +- libsolidity/codegen/ExpressionCompiler.cpp | 8 +- .../codegen/ir/IRGeneratorForStatements.cpp | 11 +-- libsolidity/formal/BMC.cpp | 5 +- libsolidity/formal/CHC.cpp | 4 +- libsolidity/formal/SMTEncoder.cpp | 11 +-- libsolutil/CMakeLists.txt | 1 + libsolutil/SetOnce.h | 86 +++++++++++++++++++ 14 files changed, 130 insertions(+), 35 deletions(-) create mode 100644 libsolutil/SetOnce.h diff --git a/liblangutil/ErrorReporter.cpp b/liblangutil/ErrorReporter.cpp index 29a20a8d8..02a983651 100644 --- a/liblangutil/ErrorReporter.cpp +++ b/liblangutil/ErrorReporter.cpp @@ -23,6 +23,7 @@ #include #include +#include #include using namespace std; diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index cb04eec6f..47be22810 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -277,7 +277,7 @@ struct EventOutsideEmitChecker: public PostTypeChecker::Checker bool visit(FunctionCall const& _functionCall) override { - if (_functionCall.annotation().kind != FunctionCallKind::FunctionCall) + if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall) return true; if (FunctionTypePointer const functionType = dynamic_cast(_functionCall.expression().annotation().type)) diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 68f52ac26..cd932cf6d 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -292,7 +292,7 @@ bool StaticAnalyzer::visit(BinaryOperation const& _operation) bool StaticAnalyzer::visit(FunctionCall const& _functionCall) { - if (_functionCall.annotation().kind == FunctionCallKind::FunctionCall) + if (*_functionCall.annotation().kind == FunctionCallKind::FunctionCall) { auto functionType = dynamic_cast(_functionCall.expression().annotation().type); solAssert(functionType, ""); diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index f0b174d19..8ebc76291 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -885,7 +885,7 @@ bool TypeChecker::visit(IfStatement const& _ifStatement) void TypeChecker::endVisit(TryStatement const& _tryStatement) { FunctionCall const* externalCall = dynamic_cast(&_tryStatement.externalCall()); - if (!externalCall || externalCall->annotation().kind != FunctionCallKind::FunctionCall) + if (!externalCall || *externalCall->annotation().kind != FunctionCallKind::FunctionCall) { m_errorReporter.typeError( 5347_error, @@ -1095,7 +1095,7 @@ void TypeChecker::endVisit(Return const& _return) void TypeChecker::endVisit(EmitStatement const& _emit) { if ( - _emit.eventCall().annotation().kind != FunctionCallKind::FunctionCall || + *_emit.eventCall().annotation().kind != FunctionCallKind::FunctionCall || type(_emit.eventCall().expression())->category() != Type::Category::Function || dynamic_cast(*type(_emit.eventCall().expression())).kind() != FunctionType::Kind::Event ) @@ -1591,7 +1591,7 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( FunctionCall const& _functionCall ) { - solAssert(_functionCall.annotation().kind == FunctionCallKind::TypeConversion, ""); + solAssert(*_functionCall.annotation().kind == FunctionCallKind::TypeConversion, ""); TypePointer const& expressionType = type(_functionCall.expression()); vector> const& arguments = _functionCall.arguments(); @@ -1956,8 +1956,10 @@ void TypeChecker::typeCheckFunctionGeneralChecks( bool const isPositionalCall = _functionCall.names().empty(); bool const isVariadic = _functionType->takesArbitraryParameters(); + auto functionCallKind = *_functionCall.annotation().kind; + solAssert( - !isVariadic || _functionCall.annotation().kind == FunctionCallKind::FunctionCall, + !isVariadic || functionCallKind == FunctionCallKind::FunctionCall, "Struct constructor calls cannot be variadic." ); @@ -1972,7 +1974,7 @@ void TypeChecker::typeCheckFunctionGeneralChecks( ) { bool const isStructConstructorCall = - _functionCall.annotation().kind == FunctionCallKind::StructConstructorCall; + functionCallKind == FunctionCallKind::StructConstructorCall; auto [errorId, description] = [&]() -> tuple { string msg = isVariadic ? @@ -2235,13 +2237,14 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) default: m_errorReporter.fatalTypeError(5704_error, _functionCall.location(), "Type is not callable"); - funcCallAnno.kind = FunctionCallKind::Unset; + // Unreachable, because fatalTypeError throws. We don't set kind, but that's okay because the switch below + // is never reached. And, even if it was, SetOnce would trigger an assertion violation and not UB. funcCallAnno.isPure = argumentsArePure; break; } // Determine return types - switch (funcCallAnno.kind) + switch (*funcCallAnno.kind) { case FunctionCallKind::TypeConversion: funcCallAnno.type = typeCheckTypeConversionAndRetrieveReturnType(_functionCall); @@ -2291,7 +2294,6 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) break; } - case FunctionCallKind::Unset: // fall-through default: // for non-callables, ensure error reported and annotate node to void function solAssert(m_errorReporter.hasErrors(), ""); diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 469d399b1..a1bf31d77 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -309,7 +309,7 @@ void ViewPureChecker::reportMutability( void ViewPureChecker::endVisit(FunctionCall const& _functionCall) { - if (_functionCall.annotation().kind != FunctionCallKind::FunctionCall) + if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall) return; StateMutability mutability = dynamic_cast(*_functionCall.expression().annotation().type).stateMutability(); diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 04f661020..1f6e039e3 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -27,6 +27,8 @@ #include #include +#include + #include #include #include @@ -285,7 +287,6 @@ struct BinaryOperationAnnotation: ExpressionAnnotation enum class FunctionCallKind { - Unset, FunctionCall, TypeConversion, StructConstructorCall @@ -293,7 +294,7 @@ enum class FunctionCallKind struct FunctionCallAnnotation: ExpressionAnnotation { - FunctionCallKind kind = FunctionCallKind::Unset; + util::SetOnce kind; /// If true, this is the external call of a try statement. bool tryCall = false; }; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 5403b05c5..064068005 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -726,13 +726,16 @@ bool ASTJsonConverter::visit(FunctionCall const& _node) make_pair("arguments", toJson(_node.arguments())), make_pair("tryCall", _node.annotation().tryCall) }; + + FunctionCallKind nodeKind = *_node.annotation().kind; + if (m_legacy) { - attributes.emplace_back("isStructConstructorCall", _node.annotation().kind == FunctionCallKind::StructConstructorCall); - attributes.emplace_back("type_conversion", _node.annotation().kind == FunctionCallKind::TypeConversion); + attributes.emplace_back("isStructConstructorCall", nodeKind == FunctionCallKind::StructConstructorCall); + attributes.emplace_back("type_conversion", nodeKind == FunctionCallKind::TypeConversion); } else - attributes.emplace_back("kind", functionCallKind(_node.annotation().kind)); + attributes.emplace_back("kind", functionCallKind(nodeKind)); appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "FunctionCall", std::move(attributes)); return false; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index b43e0cf9f..8b834031f 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -492,8 +492,10 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { + auto functionCallKind = *_functionCall.annotation().kind; + CompilerContext::LocationSetter locationSetter(m_context, _functionCall); - if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion) + if (functionCallKind == FunctionCallKind::TypeConversion) { solAssert(_functionCall.arguments().size() == 1, ""); solAssert(_functionCall.names().empty(), ""); @@ -517,7 +519,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } FunctionTypePointer functionType; - if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall) + if (functionCallKind == FunctionCallKind::StructConstructorCall) { auto const& type = dynamic_cast(*_functionCall.expression().annotation().type); auto const& structType = dynamic_cast(*type.actualType()); @@ -548,7 +550,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(found, ""); } - if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall) + if (functionCallKind == FunctionCallKind::StructConstructorCall) { TypeType const& type = dynamic_cast(*_functionCall.expression().annotation().type); auto const& structType = dynamic_cast(*type.actualType()); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index a7ef82784..1004ccf1b 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -624,12 +624,9 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall) void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) { - solUnimplementedAssert( - _functionCall.annotation().kind != FunctionCallKind::Unset, - "This type of function call is not yet implemented" - ); + auto functionCallKind = *_functionCall.annotation().kind; - if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion) + if (functionCallKind == FunctionCallKind::TypeConversion) { solAssert( _functionCall.expression().annotation().type->category() == Type::Category::TypeType, @@ -641,7 +638,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } FunctionTypePointer functionType = nullptr; - if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall) + if (functionCallKind == FunctionCallKind::StructConstructorCall) { auto const& type = dynamic_cast(*_functionCall.expression().annotation().type); auto const& structType = dynamic_cast(*type.actualType()); @@ -672,7 +669,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) arguments.push_back(callArguments[static_cast(std::distance(callArgumentNames.begin(), it))]); } - if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall) + if (functionCallKind == FunctionCallKind::StructConstructorCall) { TypeType const& type = dynamic_cast(*_functionCall.expression().annotation().type); auto const& structType = dynamic_cast(*type.actualType()); diff --git a/libsolidity/formal/BMC.cpp b/libsolidity/formal/BMC.cpp index 69165841b..74f814e00 100644 --- a/libsolidity/formal/BMC.cpp +++ b/libsolidity/formal/BMC.cpp @@ -337,8 +337,9 @@ void BMC::endVisit(UnaryOperation const& _op) void BMC::endVisit(FunctionCall const& _funCall) { - solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); - if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + auto functionCallKind = *_funCall.annotation().kind; + + if (functionCallKind != FunctionCallKind::FunctionCall) { SMTEncoder::endVisit(_funCall); return; diff --git a/libsolidity/formal/CHC.cpp b/libsolidity/formal/CHC.cpp index 304754529..3cf1470bc 100644 --- a/libsolidity/formal/CHC.cpp +++ b/libsolidity/formal/CHC.cpp @@ -424,9 +424,9 @@ bool CHC::visit(ForStatement const& _for) void CHC::endVisit(FunctionCall const& _funCall) { - solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); + auto functionCallKind = *_funCall.annotation().kind; - if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + if (functionCallKind != FunctionCallKind::FunctionCall) { SMTEncoder::endVisit(_funCall); return; diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 50ede6d13..f9ae5794e 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -581,9 +581,10 @@ void SMTEncoder::endVisit(BinaryOperation const& _op) void SMTEncoder::endVisit(FunctionCall const& _funCall) { - solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); + auto functionCallKind = *_funCall.annotation().kind; + createExpr(_funCall); - if (_funCall.annotation().kind == FunctionCallKind::StructConstructorCall) + if (functionCallKind == FunctionCallKind::StructConstructorCall) { m_errorReporter.warning( 4639_error, @@ -593,7 +594,7 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall) return; } - if (_funCall.annotation().kind == FunctionCallKind::TypeConversion) + if (functionCallKind == FunctionCallKind::TypeConversion) { visitTypeConversion(_funCall); return; @@ -753,7 +754,7 @@ void SMTEncoder::endVisit(ElementaryTypeNameExpression const& _typeName) void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall) { - solAssert(_funCall.annotation().kind == FunctionCallKind::TypeConversion, ""); + solAssert(*_funCall.annotation().kind == FunctionCallKind::TypeConversion, ""); solAssert(_funCall.arguments().size() == 1, ""); auto argument = _funCall.arguments().front(); unsigned argSize = argument->annotation().type->storageBytes(); @@ -1948,7 +1949,7 @@ string SMTEncoder::extraComment() FunctionDefinition const* SMTEncoder::functionCallToDefinition(FunctionCall const& _funCall) { - if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + if (*_funCall.annotation().kind != FunctionCallKind::FunctionCall) return nullptr; FunctionDefinition const* funDef = nullptr; diff --git a/libsolutil/CMakeLists.txt b/libsolutil/CMakeLists.txt index 3672657e8..485218999 100644 --- a/libsolutil/CMakeLists.txt +++ b/libsolutil/CMakeLists.txt @@ -22,6 +22,7 @@ set(sources LazyInit.h picosha2.h Result.h + SetOnce.h StringUtils.cpp StringUtils.h SwarmHash.cpp diff --git a/libsolutil/SetOnce.h b/libsolutil/SetOnce.h new file mode 100644 index 000000000..289258e7b --- /dev/null +++ b/libsolutil/SetOnce.h @@ -0,0 +1,86 @@ +/* + 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 . +*/ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace solidity::util +{ + +DEV_SIMPLE_EXCEPTION(BadSetOnceReassignment); +DEV_SIMPLE_EXCEPTION(BadSetOnceAccess); + +/// A class that stores a value that can only be set once +/// \tparam T the type of the stored value +template +class SetOnce +{ +public: + /// Initializes the class to have no stored value. + SetOnce() = default; + + // Not copiable + SetOnce(SetOnce const&) = delete; + SetOnce(SetOnce&&) = delete; + + // Not movable + SetOnce& operator=(SetOnce const&) = delete; + SetOnce& operator=(SetOnce&&) = delete; + + /// @brief Sets the stored value to \p _newValue + /// @throws BadSetOnceReassignment when the stored value has already been set + /// @return `*this` + constexpr SetOnce& operator=(T _newValue) & + { + assertThrow( + !m_value.has_value(), + BadSetOnceReassignment, + "Attempt to reassign to a SetOnce that already has a value." + ); + + m_value.emplace(std::move(_newValue)); + return *this; + } + + /// @return A reference to the stored value. The returned reference has the same lifetime as `*this`. + /// @throws BadSetOnceAccess when the stored value has not yet been set + T const& operator*() const + { + assertThrow( + m_value.has_value(), + BadSetOnceAccess, + "Attempt to access the value of a SetOnce that does not have a value." + ); + + return m_value.value(); + } + + /// @return A reference to the stored value. The referent of the returned pointer has the same lifetime as `*this`. + /// @throws BadSetOnceAccess when the stored value has not yet been set + T const* operator->() const { return std::addressof(**this); } + +private: + std::optional m_value = std::nullopt; +}; + +}