180 lines · cpp
1//===- EnumCastOutOfRangeChecker.cpp ---------------------------*- C++ -*--===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// The EnumCastOutOfRangeChecker is responsible for checking integer to10// enumeration casts that could result in undefined values. This could happen11// if the value that we cast from is out of the value range of the enumeration.12// Reference:13// [ISO/IEC 14882-2014] ISO/IEC 14882-2014.14// Programming Languages — C++, Fourth Edition. 2014.15// C++ Standard, [dcl.enum], in paragraph 8, which defines the range of an enum16// C++ Standard, [expr.static.cast], paragraph 10, which defines the behaviour17// of casting an integer value that is out of range18// SEI CERT C++ Coding Standard, INT50-CPP. Do not cast to an out-of-range19// enumeration value20//===----------------------------------------------------------------------===//21 22#include "clang/AST/Attr.h"23#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"24#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"25#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"26#include "llvm/Support/FormatVariadic.h"27#include <optional>28 29using namespace clang;30using namespace ento;31using llvm::formatv;32 33namespace {34// This evaluator checks two SVals for equality. The first SVal is provided via35// the constructor, the second is the parameter of the overloaded () operator.36// It uses the in-built ConstraintManager to resolve the equlity to possible or37// not possible ProgramStates.38class ConstraintBasedEQEvaluator {39 const DefinedOrUnknownSVal CompareValue;40 const ProgramStateRef PS;41 SValBuilder &SVB;42 43public:44 ConstraintBasedEQEvaluator(CheckerContext &C,45 const DefinedOrUnknownSVal CompareValue)46 : CompareValue(CompareValue), PS(C.getState()), SVB(C.getSValBuilder()) {}47 48 bool operator()(const llvm::APSInt &EnumDeclInitValue) {49 DefinedOrUnknownSVal EnumDeclValue = SVB.makeIntVal(EnumDeclInitValue);50 DefinedOrUnknownSVal ElemEqualsValueToCast =51 SVB.evalEQ(PS, EnumDeclValue, CompareValue);52 53 return static_cast<bool>(PS->assume(ElemEqualsValueToCast, true));54 }55};56 57// This checker checks CastExpr statements.58// If the value provided to the cast is one of the values the enumeration can59// represent, the said value matches the enumeration. If the checker can60// establish the impossibility of matching it gives a warning.61// Being conservative, it does not warn if there is slight possibility the62// value can be matching.63class EnumCastOutOfRangeChecker : public Checker<check::PreStmt<CastExpr>> {64 const BugType EnumValueCastOutOfRange{this, "Enum cast out of range"};65 void reportWarning(CheckerContext &C, const CastExpr *CE,66 const EnumDecl *E) const;67 68public:69 void checkPreStmt(const CastExpr *CE, CheckerContext &C) const;70};71 72using EnumValueVector = llvm::SmallVector<llvm::APSInt, 6>;73 74// Collects all of the values an enum can represent (as SVals).75EnumValueVector getDeclValuesForEnum(const EnumDecl *ED) {76 EnumValueVector DeclValues(77 std::distance(ED->enumerator_begin(), ED->enumerator_end()));78 llvm::transform(ED->enumerators(), DeclValues.begin(),79 [](const EnumConstantDecl *D) { return D->getInitVal(); });80 return DeclValues;81}82} // namespace83 84void EnumCastOutOfRangeChecker::reportWarning(CheckerContext &C,85 const CastExpr *CE,86 const EnumDecl *E) const {87 assert(E && "valid EnumDecl* is expected");88 if (const ExplodedNode *N = C.generateNonFatalErrorNode()) {89 std::string ValueStr = "", NameStr = "the enum";90 91 // Try to add details to the message:92 const auto ConcreteValue =93 C.getSVal(CE->getSubExpr()).getAs<nonloc::ConcreteInt>();94 if (ConcreteValue) {95 ValueStr = formatv(" '{0}'", ConcreteValue->getValue());96 }97 if (StringRef EnumName{E->getName()}; !EnumName.empty()) {98 NameStr = formatv("'{0}'", EnumName);99 }100 101 std::string Msg = formatv("The value{0} provided to the cast expression is "102 "not in the valid range of values for {1}",103 ValueStr, NameStr);104 105 auto BR = std::make_unique<PathSensitiveBugReport>(EnumValueCastOutOfRange,106 Msg, N);107 bugreporter::trackExpressionValue(N, CE->getSubExpr(), *BR);108 BR->addNote("enum declared here",109 PathDiagnosticLocation::create(E, C.getSourceManager()),110 {E->getSourceRange()});111 C.emitReport(std::move(BR));112 }113}114 115void EnumCastOutOfRangeChecker::checkPreStmt(const CastExpr *CE,116 CheckerContext &C) const {117 118 // Only perform enum range check on casts where such checks are valid. For119 // all other cast kinds (where enum range checks are unnecessary or invalid),120 // just return immediately. TODO: The set of casts allowed for enum range121 // checking may be incomplete. Better to add a missing cast kind to enable a122 // missing check than to generate false negatives and have to remove those123 // later.124 switch (CE->getCastKind()) {125 case CK_IntegralCast:126 break;127 128 default:129 return;130 break;131 }132 133 // Get the value of the expression to cast.134 const std::optional<DefinedOrUnknownSVal> ValueToCast =135 C.getSVal(CE->getSubExpr()).getAs<DefinedOrUnknownSVal>();136 137 // If the value cannot be reasoned about (not even a DefinedOrUnknownSVal),138 // don't analyze further.139 if (!ValueToCast)140 return;141 142 // Check whether the cast type is an enum.143 const auto *ED = CE->getType()->getAsEnumDecl();144 if (!ED)145 return;146 147 // [[clang::flag_enum]] annotated enums are by definition should be ignored.148 if (ED->hasAttr<FlagEnumAttr>())149 return;150 151 EnumValueVector DeclValues = getDeclValuesForEnum(ED);152 153 // If the declarator list is empty, bail out.154 // Every initialization an enum with a fixed underlying type but without any155 // enumerators would produce a warning if we were to continue at this point.156 // The most notable example is std::byte in the C++17 standard library.157 // TODO: Create heuristics to bail out when the enum type is intended to be158 // used to store combinations of flag values (to mitigate the limitation159 // described in the docs).160 if (DeclValues.size() == 0)161 return;162 163 // Check if any of the enum values possibly match.164 bool PossibleValueMatch =165 llvm::any_of(DeclValues, ConstraintBasedEQEvaluator(C, *ValueToCast));166 167 // If there is no value that can possibly match any of the enum values, then168 // warn.169 if (!PossibleValueMatch)170 reportWarning(C, CE, ED);171}172 173void ento::registerEnumCastOutOfRangeChecker(CheckerManager &mgr) {174 mgr.registerChecker<EnumCastOutOfRangeChecker>();175}176 177bool ento::shouldRegisterEnumCastOutOfRangeChecker(const CheckerManager &mgr) {178 return true;179}180