brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.0 KiB · 76a1470 Raw
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