//===------------------------ 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) { for (auto semantics : decl->getAttrs().getAttributes()) { if (semantics->Value.equals(attrName)) return true; } return false; } /// 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 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::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); } /// 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 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(expr)) { expressionsToCheck.push_back(identityExpr->getSubExpr()); continue; } if (TupleExpr *tupleExpr = dyn_cast(expr)) { for (Expr *element : tupleExpr->getElements()) expressionsToCheck.push_back(element); continue; } if (InjectIntoOptionalExpr *optionalExpr = dyn_cast(expr)) { expressionsToCheck.push_back(optionalExpr->getSubExpr()); continue; } // Literal expressions also includes InterpolatedStringLiteralExpr. if (isa(expr)) continue; if (isa(expr)) continue; // Closure expressions are always treated as constants. They are // constants of function types. if (isa(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(expr)) continue; // If this is a member-ref, it has to be annotated constant evaluable. if (MemberRefExpr *memberRef = dyn_cast(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(expr)) { ValueDecl *decl = declRef->getDecl(); if (!decl) return expr; ParamDecl *paramDecl = dyn_cast(decl); if (!paramDecl) return expr; Decl *declContext = paramDecl->getDeclContext()->getAsDecl(); if (!declContext) return expr; FuncDecl *funcDecl = dyn_cast(declContext); if (!funcDecl || !isParamRequiredToBeConstant(funcDecl, paramDecl)) return expr; continue; } if (!isa(expr)) return expr; ApplyExpr *apply = cast(expr); ValueDecl *calledValue = apply->getCalledValue(); if (!calledValue) return expr; // If this is an enum case, check whether the arguments are constants. if (isa(calledValue)) { expressionsToCheck.push_back(apply->getArg()); continue; } // If this is a constant_evaluable function, check whether the arguments are // constants. AbstractFunctionDecl *callee = dyn_cast(calledValue); if (!callee || !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()) { diags.diagnose(errorLoc, diag::oslog_arg_must_be_metatype_literal); return; } if (exprType->is()) { 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(calledDecl)) return; FuncDecl *callee = cast(calledDecl); // Collect argument indices that are required to be constants. SmallVector 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 arguments; if (TupleExpr *tupleExpr = dyn_cast(argumentExpr)) { auto elements = tupleExpr->getElements(); arguments.append(elements.begin(), elements.end()); } else if (ParenExpr *parenExpr = dyn_cast(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 walkToExprPre(Expr *expr) override { if (!expr || isa(expr) || !expr->getType()) return {false, expr}; if (auto *callExpr = dyn_cast(expr)) { diagnoseConstantArgumentRequirementOfCall(callExpr, astContext); return {false, expr}; } return {true, expr}; } }; ConstantReqCallWalker walker(declContext->getASTContext()); const_cast(expr)->walk(walker); }