| //===------------------------ ConstantnessSemaDiagnostics.cpp -------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file implements checks for checking whether certain arguments to some |
| // specific APIs are compile-time constants (see below for the definition of |
| // constants). In particular, this code checks whether the new os_log APIs are |
| // invoked with constant arguments, and whether the primitive atomic operations |
| // are invoked with constant "orderings". These APIs are identified through |
| // @_semantics attributes. |
| // |
| // A "compile-time constant" is either a literal (including |
| // string/integer/float/boolean/string-interpolation literal) or a call to a |
| // "constant_evaluable" function (or property) with compile-time constant |
| // arguments. A closure expression is also considered a compile-time constant |
| // (it is a constant of a function type). |
| //===----------------------------------------------------------------------===// |
| |
| #include "MiscDiagnostics.h" |
| #include "TypeChecker.h" |
| #include "swift/AST/ASTContext.h" |
| #include "swift/AST/ASTWalker.h" |
| #include "swift/AST/ParameterList.h" |
| #include "swift/AST/SemanticAttrs.h" |
| using namespace swift; |
| |
| /// Check whether a given \p decl has a @_semantics attribute with the given |
| /// attribute name \c attrName. |
| static bool hasSemanticsAttr(ValueDecl *decl, StringRef attrName) { |
| return decl->getAttrs().hasSemanticsAttr(attrName); |
| } |
| |
| /// Return true iff the given \p structDecl has a name that matches one of the |
| /// known atomic orderings structs. |
| static bool isAtomicOrderingDecl(StructDecl *structDecl) { |
| ASTContext &astContext = structDecl->getASTContext(); |
| Identifier structName = structDecl->getName(); |
| return (structName == astContext.Id_AtomicLoadOrdering || |
| structName == astContext.Id_AtomicStoreOrdering || |
| structName == astContext.Id_AtomicUpdateOrdering); |
| } |
| |
| /// Return true iff the given nominal type decl \p nominal has a name that |
| /// matches one of the known OSLog types that need not be a constant in the new |
| /// os_log APIs. |
| static bool isOSLogDynamicObject(NominalTypeDecl *nominal) { |
| ASTContext &astContext = nominal->getASTContext(); |
| Identifier name = nominal->getName(); |
| return (name == astContext.Id_OSLog || name == astContext.Id_OSLogType); |
| } |
| |
| /// Return true iff the parameter \p param of function \c funDecl is required to |
| /// be a constant. This is true if either the function is an os_log function or |
| /// it is an atomics operation and the parameter represents the ordering. |
| static bool isParamRequiredToBeConstant(FuncDecl *funcDecl, ParamDecl *param) { |
| assert(funcDecl && param && "funcDecl and param must not be null"); |
| if (hasSemanticsAttr(funcDecl, semantics::OSLOG_REQUIRES_CONSTANT_ARGUMENTS)) |
| return true; |
| if (hasSemanticsAttr(funcDecl, semantics::OSLOG_LOG_WITH_LEVEL)) { |
| // We are looking at a top-level os_log function that accepts level and |
| // possibly custom log object. Those need not be constants, but every other |
| // parameter must be. |
| Type paramType = param->getType(); |
| NominalTypeDecl *nominal = paramType->getNominalOrBoundGenericNominal(); |
| return !nominal || !isOSLogDynamicObject(nominal); |
| } |
| if (!hasSemanticsAttr(funcDecl, |
| semantics::ATOMICS_REQUIRES_CONSTANT_ORDERINGS)) |
| return false; |
| Type paramType = param->getType(); |
| StructDecl *structDecl = paramType->getStructOrBoundGenericStruct(); |
| if (!structDecl) |
| return false; |
| return isAtomicOrderingDecl(structDecl); |
| } |
| |
| /// Return true iff the \c decl is annotated as |
| /// @_semantics("constant_evaluable"). |
| static bool hasConstantEvaluableAttr(ValueDecl *decl) { |
| return hasSemanticsAttr(decl, semantics::CONSTANT_EVALUABLE); |
| } |
| |
| /// Return true iff the \p decl is annotated with oslog.message.init semantics |
| /// attribute. |
| static bool isOSLogMessageInitializer(ValueDecl *decl) { |
| return hasSemanticsAttr(decl, semantics::OSLOG_MESSAGE_INIT_STRING_LITERAL) || |
| hasSemanticsAttr(decl, semantics::OSLOG_MESSAGE_INIT_INTERPOLATION); |
| } |
| |
| /// Check whether \p expr is a compile-time constant. It must either be a |
| /// literal_expr, which does not include array and dictionary literal, or a |
| /// closure expression, which is considered a compile-time constant of a |
| /// function type, or a call to a "constant_evaluable" function (or property) |
| /// whose arguments are themselves compile-time constants. |
| static Expr *checkConstantness(Expr *expr) { |
| SmallVector<Expr *, 4> expressionsToCheck; |
| expressionsToCheck.push_back(expr); |
| while (!expressionsToCheck.empty()) { |
| Expr *expr = expressionsToCheck.pop_back_val(); |
| // Lookthrough identity_expr, tuple and inject_into_optional expressions. |
| if (IdentityExpr *identityExpr = dyn_cast<IdentityExpr>(expr)) { |
| expressionsToCheck.push_back(identityExpr->getSubExpr()); |
| continue; |
| } |
| if (TupleExpr *tupleExpr = dyn_cast<TupleExpr>(expr)) { |
| for (Expr *element : tupleExpr->getElements()) |
| expressionsToCheck.push_back(element); |
| continue; |
| } |
| if (InjectIntoOptionalExpr *optionalExpr = |
| dyn_cast<InjectIntoOptionalExpr>(expr)) { |
| expressionsToCheck.push_back(optionalExpr->getSubExpr()); |
| continue; |
| } |
| // Literal expressions also includes InterpolatedStringLiteralExpr. |
| if (isa<LiteralExpr>(expr)) |
| continue; |
| if (isa<TypeExpr>(expr)) |
| continue; |
| // Closure expressions are always treated as constants. They are |
| // constants of function types. |
| if (isa<AbstractClosureExpr>(expr)) |
| continue; |
| // Default argument expressions of a constant_evaluable or a |
| // requires_constant function must be ensured to be a constant by the |
| // definition of the function. |
| if (isa<DefaultArgumentExpr>(expr)) |
| continue; |
| |
| // If this is a member-ref, it has to be annotated constant evaluable. |
| if (MemberRefExpr *memberRef = dyn_cast<MemberRefExpr>(expr)) { |
| if (ValueDecl *memberDecl = memberRef->getMember().getDecl()) { |
| if (hasConstantEvaluableAttr(memberDecl)) |
| continue; |
| } |
| return expr; |
| } |
| |
| // If this is a variable, it has to be a known constant parameter of the |
| // enclosing function. |
| if (DeclRefExpr *declRef = dyn_cast<DeclRefExpr>(expr)) { |
| ValueDecl *decl = declRef->getDecl(); |
| if (!decl) |
| return expr; |
| ParamDecl *paramDecl = dyn_cast<ParamDecl>(decl); |
| if (!paramDecl) |
| return expr; |
| Decl *declContext = paramDecl->getDeclContext()->getAsDecl(); |
| if (!declContext) |
| return expr; |
| FuncDecl *funcDecl = dyn_cast<FuncDecl>(declContext); |
| if (!funcDecl || !isParamRequiredToBeConstant(funcDecl, paramDecl)) |
| return expr; |
| continue; |
| } |
| |
| if (!isa<ApplyExpr>(expr)) |
| return expr; |
| |
| ApplyExpr *apply = cast<ApplyExpr>(expr); |
| ValueDecl *calledValue = apply->getCalledValue(); |
| if (!calledValue) |
| return expr; |
| |
| // If this is an enum case, check whether the arguments are constants. |
| if (isa<EnumElementDecl>(calledValue)) { |
| expressionsToCheck.push_back(apply->getArg()); |
| continue; |
| } |
| |
| AbstractFunctionDecl *callee = dyn_cast<AbstractFunctionDecl>(calledValue); |
| if (!callee) |
| return expr; |
| |
| // If this is an application of OSLogMessage initializer, fail the check |
| // as this type must be created from string interpolations. |
| if (isOSLogMessageInitializer(callee)) |
| return expr; |
| |
| // If this is a constant_evaluable function, check whether the arguments are |
| // constants. |
| if (!hasConstantEvaluableAttr(callee)) |
| return expr; |
| expressionsToCheck.push_back(apply->getArg()); |
| } |
| return nullptr; |
| } |
| |
| /// Return true iff the norminal type decl \c numberDecl is a known stdlib |
| /// integer decl. |
| static bool isStdlibInteger(NominalTypeDecl *numberDecl) { |
| ASTContext &astCtx = numberDecl->getASTContext(); |
| return (numberDecl == astCtx.getIntDecl() || |
| numberDecl == astCtx.getInt8Decl() || |
| numberDecl == astCtx.getInt16Decl() || |
| numberDecl == astCtx.getInt32Decl() || |
| numberDecl == astCtx.getInt64Decl() || |
| numberDecl == astCtx.getUIntDecl() || |
| numberDecl == astCtx.getUInt8Decl() || |
| numberDecl == astCtx.getUInt16Decl() || |
| numberDecl == astCtx.getUInt32Decl() || |
| numberDecl == astCtx.getUInt64Decl()); |
| } |
| |
| /// Return true iff the given \p type is a Stdlib integer type. |
| static bool isIntegerType(Type type) { |
| NominalTypeDecl *nominalDecl = type->getNominalOrBoundGenericNominal(); |
| return nominalDecl && isStdlibInteger(nominalDecl); |
| } |
| |
| /// Return true iff the norminal type decl \c numberDecl is a known stdlib float |
| /// decl. |
| static bool isStdlibFloat(NominalTypeDecl *numberDecl) { |
| ASTContext &astCtx = numberDecl->getASTContext(); |
| return (numberDecl == astCtx.getFloatDecl() || |
| numberDecl == astCtx.getFloat80Decl() || |
| numberDecl == astCtx.getDoubleDecl()); |
| } |
| |
| /// Return true iff the given \p type is a Bool type. |
| static bool isFloatType(Type type) { |
| NominalTypeDecl *nominalDecl = type->getNominalOrBoundGenericNominal(); |
| return nominalDecl && isStdlibFloat(nominalDecl); |
| } |
| |
| /// Return true iff the given \p type is a String type. |
| static bool isStringType(Type type) { |
| NominalTypeDecl *nominalDecl = type->getNominalOrBoundGenericNominal(); |
| return nominalDecl && nominalDecl == type->getASTContext().getStringDecl(); |
| } |
| |
| /// Given an error expression \p errorExpr, diagnose the error based on the type |
| /// of the expression. For instance, if the expression's type is expressible by |
| /// a literal e.g. integer, boolean etc. report that it must be a literal. |
| /// Otherwise, if the expression is a nominal type, report that it must be |
| /// static member of the type. |
| static void diagnoseError(Expr *errorExpr, const ASTContext &astContext, |
| FuncDecl *funcDecl) { |
| DiagnosticEngine &diags = astContext.Diags; |
| Type exprType = errorExpr->getType(); |
| SourceLoc errorLoc = errorExpr->getLoc(); |
| |
| // Diagnose atomics ordering related error here. |
| if (hasSemanticsAttr(funcDecl, |
| semantics::ATOMICS_REQUIRES_CONSTANT_ORDERINGS)) { |
| NominalTypeDecl *nominalDecl = exprType->getNominalOrBoundGenericNominal(); |
| if (!nominalDecl) { |
| // This case should normally not happen. This is a safe guard against |
| // possible mismatch between the atomics library and the compiler. |
| diags.diagnose(errorLoc, diag::argument_must_be_constant); |
| } |
| diags.diagnose(errorLoc, diag::atomics_ordering_must_be_constant, |
| nominalDecl->getName()); |
| return; |
| } |
| |
| // Diagnose os_log specific errors here. |
| |
| // Diagnose primitive stdlib types. |
| if (exprType->isBool()) { |
| diags.diagnose(errorLoc, diag::oslog_arg_must_be_bool_literal); |
| return; |
| } |
| if (isStringType(exprType)) { |
| diags.diagnose(errorLoc, diag::oslog_arg_must_be_string_literal); |
| return; |
| } |
| if (isIntegerType(exprType)) { |
| diags.diagnose(errorLoc, diag::oslog_arg_must_be_integer_literal); |
| return; |
| } |
| if (isFloatType(exprType)) { |
| diags.diagnose(errorLoc, diag::oslog_arg_must_be_float_literal); |
| return; |
| } |
| if (exprType->is<MetatypeType>()) { |
| diags.diagnose(errorLoc, diag::oslog_arg_must_be_metatype_literal); |
| return; |
| } |
| if (exprType->is<AnyFunctionType>()) { |
| diags.diagnose(errorLoc, diag::oslog_arg_must_be_closure); |
| return; |
| } |
| if (EnumDecl *enumDecl = exprType->getEnumOrBoundGenericEnum()) { |
| diags.diagnose(errorLoc, diag::oslog_arg_must_be_enum_case, |
| enumDecl->getName()); |
| return; |
| } |
| NominalTypeDecl *nominalDecl = exprType->getNominalOrBoundGenericNominal(); |
| if (!nominalDecl) { |
| // This case should normally not happen. This is a safe guard against |
| // possible mismatch between the os overlay and the compiler. |
| diags.diagnose(errorLoc, diag::argument_must_be_constant); |
| return; |
| } |
| // If this is OSLogMessage, it should be a string-interpolation literal. |
| Identifier declName = nominalDecl->getName(); |
| if (declName == astContext.Id_OSLogMessage) { |
| diags.diagnose(errorLoc, diag::oslog_message_must_be_string_interpolation); |
| return; |
| } |
| diags.diagnose(errorLoc, diag::oslog_arg_must_be_type_member_access, |
| declName); |
| } |
| |
| /// Given a call \c callExpr, if some or all of its arguments are required to be |
| /// constants, check that property on the arguments. |
| static void diagnoseConstantArgumentRequirementOfCall(const CallExpr *callExpr, |
| const ASTContext &ctx) { |
| assert(callExpr && callExpr->getType() && |
| "callExpr should have a valid type"); |
| ValueDecl *calledDecl = callExpr->getCalledValue(); |
| if (!calledDecl || !isa<FuncDecl>(calledDecl)) |
| return; |
| FuncDecl *callee = cast<FuncDecl>(calledDecl); |
| |
| // Collect argument indices that are required to be constants. |
| SmallVector<unsigned, 4> constantArgumentIndices; |
| auto paramList = callee->getParameters(); |
| for (unsigned i = 0; i < paramList->size(); ++i) { |
| ParamDecl *param = paramList->get(i); |
| if (isParamRequiredToBeConstant(callee, param)) |
| constantArgumentIndices.push_back(i); |
| } |
| if (constantArgumentIndices.empty()) |
| return; |
| |
| // Check that the arguments at the constantArgumentIndices are constants. |
| Expr *argumentExpr = callExpr->getArg(); |
| SmallVector<Expr *, 4> arguments; |
| if (TupleExpr *tupleExpr = dyn_cast<TupleExpr>(argumentExpr)) { |
| auto elements = tupleExpr->getElements(); |
| arguments.append(elements.begin(), elements.end()); |
| } else if (ParenExpr *parenExpr = dyn_cast<ParenExpr>(argumentExpr)) { |
| arguments.push_back(parenExpr->getSubExpr()); |
| } else { |
| arguments.push_back(argumentExpr); |
| } |
| |
| for (unsigned constantIndex : constantArgumentIndices) { |
| assert(constantIndex < arguments.size() && |
| "constantIndex exceeds the number of arguments to the function"); |
| Expr *argument = arguments[constantIndex]; |
| Expr *errorExpr = checkConstantness(argument); |
| if (errorExpr) |
| diagnoseError(errorExpr, ctx, callee); |
| } |
| } |
| |
| void swift::diagnoseConstantArgumentRequirement( |
| const Expr *expr, const DeclContext *declContext) { |
| class ConstantReqCallWalker : public ASTWalker { |
| const ASTContext &astContext; |
| |
| public: |
| ConstantReqCallWalker(ASTContext &ctx) : astContext(ctx) {} |
| |
| // Descend until we find a call expressions. Note that the input expression |
| // could be an assign expression or another expression that contains the |
| // call. |
| std::pair<bool, Expr *> walkToExprPre(Expr *expr) override { |
| if (!expr || isa<ErrorExpr>(expr) || !expr->getType()) |
| return {false, expr}; |
| if (auto *callExpr = dyn_cast<CallExpr>(expr)) { |
| diagnoseConstantArgumentRequirementOfCall(callExpr, astContext); |
| return {false, expr}; |
| } |
| return {true, expr}; |
| } |
| }; |
| |
| ConstantReqCallWalker walker(declContext->getASTContext()); |
| const_cast<Expr *>(expr)->walk(walker); |
| } |