//=== StdLibraryFunctionsChecker.cpp - Model standard functions -*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This checker improves modeling of a few simple library functions.
// It does not generate warnings.
//
// This checker provides a specification format - `FunctionSummaryTy' - and
// contains descriptions of some library functions in this format. Each
// specification contains a list of branches for splitting the program state
// upon call, and range constraints on argument and return-value symbols that
// are satisfied on each branch. This spec can be expanded to include more
// items, like external effects of the function.
//
// The main difference between this approach and the body farms technique is
// in more explicit control over how many branches are produced. For example,
// consider standard C function `ispunct(int x)', which returns a non-zero value
// iff `x' is a punctuation character, that is, when `x' is in range
//   ['!', '/']   [':', '@']  U  ['[', '\`']  U  ['{', '~'].
// `FunctionSummaryTy' provides only two branches for this function. However,
// any attempt to describe this range with if-statements in the body farm
// would result in many more branches. Because each branch needs to be analyzed
// independently, this significantly reduces performance. Additionally,
// once we consider a branch on which `x' is in range, say, ['!', '/'],
// we assume that such branch is an important separate path through the program,
// which may lead to false positives because considering this particular path
// was not consciously intended, and therefore it might have been unreachable.
//
// This checker uses eval::Call for modeling "pure" functions, for which
// their `FunctionSummaryTy' is a precise model. This avoids unnecessary
// invalidation passes. Conflicts with other checkers are unlikely because
// if the function has no other effects, other checkers would probably never
// want to improve upon the modeling done by this checker.
//
// Non-"pure" functions, for which only partial improvement over the default
// behavior is expected, are modeled via check::PostCall, non-intrusively.
//
// The following standard C functions are currently supported:
//
//   fgetc      getline   isdigit   isupper
//   fread      isalnum   isgraph   isxdigit
//   fwrite     isalpha   islower   read
//   getc       isascii   isprint   write
//   getchar    isblank   ispunct
//   getdelim   iscntrl   isspace
//
//===----------------------------------------------------------------------===//

#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"

using namespace clang;
using namespace clang::ento;

namespace {
class StdLibraryFunctionsChecker : public Checker<check::PostCall, eval::Call> {
  /// Below is a series of typedefs necessary to define function specs.
  /// We avoid nesting types here because each additional qualifier
  /// would need to be repeated in every function spec.
  struct FunctionSummaryTy;

  /// Specify how much the analyzer engine should entrust modeling this function
  /// to us. If he doesn't, he performs additional invalidations.
  enum InvalidationKindTy { NoEvalCall, EvalCallAsPure };

  /// A pair of ValueRangeKindTy and IntRangeVectorTy would describe a range
  /// imposed on a particular argument or return value symbol.
  ///
  /// Given a range, should the argument stay inside or outside this range?
  /// The special `ComparesToArgument' value indicates that we should
  /// impose a constraint that involves other argument or return value symbols.
  enum ValueRangeKindTy { OutOfRange, WithinRange, ComparesToArgument };

  // The universal integral type to use in value range descriptions.
  // Unsigned to make sure overflows are well-defined.
  typedef uint64_t RangeIntTy;

  /// Normally, describes a single range constraint, eg. {{0, 1}, {3, 4}} is
  /// a non-negative integer, which less than 5 and not equal to 2. For
  /// `ComparesToArgument', holds information about how exactly to compare to
  /// the argument.
  typedef std::vector<std::pair<RangeIntTy, RangeIntTy>> IntRangeVectorTy;

  /// A reference to an argument or return value by its number.
  /// ArgNo in CallExpr and CallEvent is defined as Unsigned, but
  /// obviously uint32_t should be enough for all practical purposes.
  typedef uint32_t ArgNoTy;
  static const ArgNoTy Ret = std::numeric_limits<ArgNoTy>::max();

  /// Incapsulates a single range on a single symbol within a branch.
  class ValueRange {
    ArgNoTy ArgNo; // Argument to which we apply the range.
    ValueRangeKindTy Kind; // Kind of range definition.
    IntRangeVectorTy Args; // Polymorphic arguments.

  public:
    ValueRange(ArgNoTy ArgNo, ValueRangeKindTy Kind,
               const IntRangeVectorTy &Args)
        : ArgNo(ArgNo), Kind(Kind), Args(Args) {}

    ArgNoTy getArgNo() const { return ArgNo; }
    ValueRangeKindTy getKind() const { return Kind; }

    BinaryOperator::Opcode getOpcode() const {
      assert(Kind == ComparesToArgument);
      assert(Args.size() == 1);
      BinaryOperator::Opcode Op =
          static_cast<BinaryOperator::Opcode>(Args[0].first);
      assert(BinaryOperator::isComparisonOp(Op) &&
             "Only comparison ops are supported for ComparesToArgument");
      return Op;
    }

    ArgNoTy getOtherArgNo() const {
      assert(Kind == ComparesToArgument);
      assert(Args.size() == 1);
      return static_cast<ArgNoTy>(Args[0].second);
    }

    const IntRangeVectorTy &getRanges() const {
      assert(Kind != ComparesToArgument);
      return Args;
    }

    // We avoid creating a virtual apply() method because
    // it makes initializer lists harder to write.
  private:
    ProgramStateRef
    applyAsOutOfRange(ProgramStateRef State, const CallEvent &Call,
                      const FunctionSummaryTy &Summary) const;
    ProgramStateRef
    applyAsWithinRange(ProgramStateRef State, const CallEvent &Call,
                       const FunctionSummaryTy &Summary) const;
    ProgramStateRef
    applyAsComparesToArgument(ProgramStateRef State, const CallEvent &Call,
                              const FunctionSummaryTy &Summary) const;

  public:
    ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call,
                          const FunctionSummaryTy &Summary) const {
      switch (Kind) {
      case OutOfRange:
        return applyAsOutOfRange(State, Call, Summary);
      case WithinRange:
        return applyAsWithinRange(State, Call, Summary);
      case ComparesToArgument:
        return applyAsComparesToArgument(State, Call, Summary);
      }
      llvm_unreachable("Unknown ValueRange kind!");
    }
  };

  /// The complete list of ranges that defines a single branch.
  typedef std::vector<ValueRange> ValueRangeSet;

  /// Includes information about function prototype (which is necessary to
  /// ensure we're modeling the right function and casting values properly),
  /// approach to invalidation, and a list of branches - essentially, a list
  /// of list of ranges - essentially, a list of lists of lists of segments.
  struct FunctionSummaryTy {
    const std::vector<QualType> ArgTypes;
    const QualType RetType;
    const InvalidationKindTy InvalidationKind;
    const std::vector<ValueRangeSet> Ranges;

  private:
    static void assertTypeSuitableForSummary(QualType T) {
      assert(!T->isVoidType() &&
             "We should have had no significant void types in the spec");
      assert(T.isCanonical() &&
             "We should only have canonical types in the spec");
      // FIXME: lift this assert (but not the ones above!)
      assert(T->isIntegralOrEnumerationType() &&
             "We only support integral ranges in the spec");
    }

  public:
    QualType getArgType(ArgNoTy ArgNo) const {
      QualType T = (ArgNo == Ret) ? RetType : ArgTypes[ArgNo];
      assertTypeSuitableForSummary(T);
      return T;
    }

    /// Try our best to figure out if the call expression is the call of
    /// *the* library function to which this specification applies.
    bool matchesCall(const CallExpr *CE) const;
  };

  // The same function (as in, function identifier) may have different
  // summaries assigned to it, with different argument and return value types.
  // We call these "variants" of the function. This can be useful for handling
  // C++ function overloads, and also it can be used when the same function
  // may have different definitions on different platforms.
  typedef std::vector<FunctionSummaryTy> FunctionVariantsTy;

  // The map of all functions supported by the checker. It is initialized
  // lazily, and it doesn't change after initialization.
  typedef llvm::StringMap<FunctionVariantsTy> FunctionSummaryMapTy;
  mutable FunctionSummaryMapTy FunctionSummaryMap;

  // Auxiliary functions to support ArgNoTy within all structures
  // in a unified manner.
  static QualType getArgType(const FunctionSummaryTy &Summary, ArgNoTy ArgNo) {
    return Summary.getArgType(ArgNo);
  }
  static QualType getArgType(const CallEvent &Call, ArgNoTy ArgNo) {
    return ArgNo == Ret ? Call.getResultType().getCanonicalType()
                        : Call.getArgExpr(ArgNo)->getType().getCanonicalType();
  }
  static QualType getArgType(const CallExpr *CE, ArgNoTy ArgNo) {
    return ArgNo == Ret ? CE->getType().getCanonicalType()
                        : CE->getArg(ArgNo)->getType().getCanonicalType();
  }
  static SVal getArgSVal(const CallEvent &Call, ArgNoTy ArgNo) {
    return ArgNo == Ret ? Call.getReturnValue() : Call.getArgSVal(ArgNo);
  }

public:
  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
  bool evalCall(const CallEvent &Call, CheckerContext &C) const;

private:
  Optional<FunctionSummaryTy> findFunctionSummary(const FunctionDecl *FD,
                                          const CallExpr *CE,
                                          CheckerContext &C) const;

  void initFunctionSummaries(BasicValueFactory &BVF) const;
};
} // end of anonymous namespace

ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsOutOfRange(
    ProgramStateRef State, const CallEvent &Call,
    const FunctionSummaryTy &Summary) const {

  ProgramStateManager &Mgr = State->getStateManager();
  SValBuilder &SVB = Mgr.getSValBuilder();
  BasicValueFactory &BVF = SVB.getBasicValueFactory();
  ConstraintManager &CM = Mgr.getConstraintManager();
  QualType T = getArgType(Summary, getArgNo());
  SVal V = getArgSVal(Call, getArgNo());

  if (auto N = V.getAs<NonLoc>()) {
    const IntRangeVectorTy &R = getRanges();
    size_t E = R.size();
    for (size_t I = 0; I != E; ++I) {
      const llvm::APSInt &Min = BVF.getValue(R[I].first, T);
      const llvm::APSInt &Max = BVF.getValue(R[I].second, T);
      assert(Min <= Max);
      State = CM.assumeInclusiveRange(State, *N, Min, Max, false);
      if (!State)
        break;
    }
  }

  return State;
}

ProgramStateRef
StdLibraryFunctionsChecker::ValueRange::applyAsWithinRange(
    ProgramStateRef State, const CallEvent &Call,
    const FunctionSummaryTy &Summary) const {

  ProgramStateManager &Mgr = State->getStateManager();
  SValBuilder &SVB = Mgr.getSValBuilder();
  BasicValueFactory &BVF = SVB.getBasicValueFactory();
  ConstraintManager &CM = Mgr.getConstraintManager();
  QualType T = getArgType(Summary, getArgNo());
  SVal V = getArgSVal(Call, getArgNo());

  // "WithinRange R" is treated as "outside [T_MIN, T_MAX] \ R".
  // We cut off [T_MIN, min(R) - 1] and [max(R) + 1, T_MAX] if necessary,
  // and then cut away all holes in R one by one.
  if (auto N = V.getAs<NonLoc>()) {
    const IntRangeVectorTy &R = getRanges();
    size_t E = R.size();

    const llvm::APSInt &MinusInf = BVF.getMinValue(T);
    const llvm::APSInt &PlusInf = BVF.getMaxValue(T);

    const llvm::APSInt &Left = BVF.getValue(R[0].first - 1ULL, T);
    if (Left != PlusInf) {
      assert(MinusInf <= Left);
      State = CM.assumeInclusiveRange(State, *N, MinusInf, Left, false);
      if (!State)
        return nullptr;
    }

    const llvm::APSInt &Right = BVF.getValue(R[E - 1].second + 1ULL, T);
    if (Right != MinusInf) {
      assert(Right <= PlusInf);
      State = CM.assumeInclusiveRange(State, *N, Right, PlusInf, false);
      if (!State)
        return nullptr;
    }

    for (size_t I = 1; I != E; ++I) {
      const llvm::APSInt &Min = BVF.getValue(R[I - 1].second + 1ULL, T);
      const llvm::APSInt &Max = BVF.getValue(R[I].first - 1ULL, T);
      assert(Min <= Max);
      State = CM.assumeInclusiveRange(State, *N, Min, Max, false);
      if (!State)
        return nullptr;
    }
  }

  return State;
}

ProgramStateRef
StdLibraryFunctionsChecker::ValueRange::applyAsComparesToArgument(
    ProgramStateRef State, const CallEvent &Call,
    const FunctionSummaryTy &Summary) const {

  ProgramStateManager &Mgr = State->getStateManager();
  SValBuilder &SVB = Mgr.getSValBuilder();
  QualType CondT = SVB.getConditionType();
  QualType T = getArgType(Summary, getArgNo());
  SVal V = getArgSVal(Call, getArgNo());

  BinaryOperator::Opcode Op = getOpcode();
  ArgNoTy OtherArg = getOtherArgNo();
  SVal OtherV = getArgSVal(Call, OtherArg);
  QualType OtherT = getArgType(Call, OtherArg);
  // Note: we avoid integral promotion for comparison.
  OtherV = SVB.evalCast(OtherV, T, OtherT);
  if (auto CompV = SVB.evalBinOp(State, Op, V, OtherV, CondT)
                       .getAs<DefinedOrUnknownSVal>())
    State = State->assume(*CompV, true);
  return State;
}

void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call,
                                               CheckerContext &C) const {
  const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
  if (!FD)
    return;

  const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
  if (!CE)
    return;

  Optional<FunctionSummaryTy> FoundSummary = findFunctionSummary(FD, CE, C);
  if (!FoundSummary)
    return;

  // Now apply ranges.
  const FunctionSummaryTy &Summary = *FoundSummary;
  ProgramStateRef State = C.getState();

  for (const auto &VRS: Summary.Ranges) {
    ProgramStateRef NewState = State;
    for (const auto &VR: VRS) {
      NewState = VR.apply(NewState, Call, Summary);
      if (!NewState)
        break;
    }

    if (NewState && NewState != State)
      C.addTransition(NewState);
  }
}

bool StdLibraryFunctionsChecker::evalCall(const CallEvent &Call,
                                          CheckerContext &C) const {
  const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
  if (!FD)
    return false;

  const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
  if (!CE)
    return false;

  Optional<FunctionSummaryTy> FoundSummary = findFunctionSummary(FD, CE, C);
  if (!FoundSummary)
    return false;

  const FunctionSummaryTy &Summary = *FoundSummary;
  switch (Summary.InvalidationKind) {
  case EvalCallAsPure: {
    ProgramStateRef State = C.getState();
    const LocationContext *LC = C.getLocationContext();
    SVal V = C.getSValBuilder().conjureSymbolVal(
        CE, LC, CE->getType().getCanonicalType(), C.blockCount());
    State = State->BindExpr(CE, LC, V);
    C.addTransition(State);
    return true;
  }
  case NoEvalCall:
    // Summary tells us to avoid performing eval::Call. The function is possibly
    // evaluated by another checker, or evaluated conservatively.
    return false;
  }
  llvm_unreachable("Unknown invalidation kind!");
}

bool StdLibraryFunctionsChecker::FunctionSummaryTy::matchesCall(
    const CallExpr *CE) const {
  // Check number of arguments:
  if (CE->getNumArgs() != ArgTypes.size())
    return false;

  // Check return type if relevant:
  if (!RetType.isNull() && RetType != CE->getType().getCanonicalType())
    return false;

  // Check argument types when relevant:
  for (size_t I = 0, E = ArgTypes.size(); I != E; ++I) {
    QualType FormalT = ArgTypes[I];
    // Null type marks irrelevant arguments.
    if (FormalT.isNull())
      continue;

    assertTypeSuitableForSummary(FormalT);

    QualType ActualT = StdLibraryFunctionsChecker::getArgType(CE, I);
    assert(ActualT.isCanonical());
    if (ActualT != FormalT)
      return false;
  }

  return true;
}

Optional<StdLibraryFunctionsChecker::FunctionSummaryTy>
StdLibraryFunctionsChecker::findFunctionSummary(const FunctionDecl *FD,
                                                const CallExpr *CE,
                                                CheckerContext &C) const {
  // Note: we cannot always obtain FD from CE
  // (eg. virtual call, or call by pointer).
  assert(CE);

  if (!FD)
    return None;

  SValBuilder &SVB = C.getSValBuilder();
  BasicValueFactory &BVF = SVB.getBasicValueFactory();
  initFunctionSummaries(BVF);

  IdentifierInfo *II = FD->getIdentifier();
  if (!II)
    return None;
  StringRef Name = II->getName();
  if (Name.empty() || !C.isCLibraryFunction(FD, Name))
    return None;

  auto FSMI = FunctionSummaryMap.find(Name);
  if (FSMI == FunctionSummaryMap.end())
    return None;

  // Verify that function signature matches the spec in advance.
  // Otherwise we might be modeling the wrong function.
  // Strict checking is important because we will be conducting
  // very integral-type-sensitive operations on arguments and
  // return values.
  const FunctionVariantsTy &SpecVariants = FSMI->second;
  for (const FunctionSummaryTy &Spec : SpecVariants)
    if (Spec.matchesCall(CE))
      return Spec;

  return None;
}

void StdLibraryFunctionsChecker::initFunctionSummaries(
    BasicValueFactory &BVF) const {
  if (!FunctionSummaryMap.empty())
    return;

  ASTContext &ACtx = BVF.getContext();

  // These types are useful for writing specifications quickly,
  // New specifications should probably introduce more types.
  // Some types are hard to obtain from the AST, eg. "ssize_t".
  // In such cases it should be possible to provide multiple variants
  // of function summary for common cases (eg. ssize_t could be int or long
  // or long long, so three summary variants would be enough).
  // Of course, function variants are also useful for C++ overloads.
  QualType Irrelevant; // A placeholder, whenever we do not care about the type.
  QualType IntTy = ACtx.IntTy;
  QualType LongTy = ACtx.LongTy;
  QualType LongLongTy = ACtx.LongLongTy;
  QualType SizeTy = ACtx.getSizeType();

  RangeIntTy IntMax = BVF.getMaxValue(IntTy).getLimitedValue();
  RangeIntTy LongMax = BVF.getMaxValue(LongTy).getLimitedValue();
  RangeIntTy LongLongMax = BVF.getMaxValue(LongLongTy).getLimitedValue();

  // We are finally ready to define specifications for all supported functions.
  //
  // The signature needs to have the correct number of arguments.
  // However, we insert `Irrelevant' when the type is insignificant.
  //
  // Argument ranges should always cover all variants. If return value
  // is completely unknown, omit it from the respective range set.
  //
  // All types in the spec need to be canonical.
  //
  // Every item in the list of range sets represents a particular
  // execution path the analyzer would need to explore once
  // the call is modeled - a new program state is constructed
  // for every range set, and each range line in the range set
  // corresponds to a specific constraint within this state.
  //
  // Upon comparing to another argument, the other argument is casted
  // to the current argument's type. This avoids proper promotion but
  // seems useful. For example, read() receives size_t argument,
  // and its return value, which is of type ssize_t, cannot be greater
  // than this argument. If we made a promotion, and the size argument
  // is equal to, say, 10, then we'd impose a range of [0, 10] on the
  // return value, however the correct range is [-1, 10].
  //
  // Please update the list of functions in the header after editing!
  //
  // The format is as follows:
  //
  //{ "function name",
  //  { spec:
  //    { argument types list, ... },
  //    return type, purity, { range set list:
  //      { range list:
  //        { argument index, within or out of, {{from, to}, ...} },
  //        { argument index, compares to argument, {{how, which}} },
  //        ...
  //      }
  //    }
  //  }
  //}

#define SUMMARY_WITH_VARIANTS(identifier) {#identifier, {
#define END_SUMMARY_WITH_VARIANTS }},
#define VARIANT(argument_types, return_type, invalidation_approach)            \
  { argument_types, return_type, invalidation_approach, {
#define END_VARIANT } },
#define SUMMARY(identifier, argument_types, return_type,                       \
                invalidation_approach)                                         \
  { #identifier, { { argument_types, return_type, invalidation_approach, {
#define END_SUMMARY } } } },
#define ARGUMENT_TYPES(...) { __VA_ARGS__ }
#define RETURN_TYPE(x) x
#define INVALIDATION_APPROACH(x) x
#define CASE {
#define END_CASE },
#define ARGUMENT_CONDITION(argument_number, condition_kind)                    \
  { argument_number, condition_kind, {
#define END_ARGUMENT_CONDITION }},
#define RETURN_VALUE_CONDITION(condition_kind)                                 \
  { Ret, condition_kind, {
#define END_RETURN_VALUE_CONDITION }},
#define ARG_NO(x) x##U
#define RANGE(x, y) { x, y },
#define SINGLE_VALUE(x) RANGE(x, x)
#define IS_LESS_THAN(arg) { BO_LE, arg }

  FunctionSummaryMap = {
    // The isascii() family of functions.
    SUMMARY(isalnum, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE // Boils down to isupper() or islower() or isdigit()
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE('0', '9')
          RANGE('A', 'Z')
          RANGE('a', 'z')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE // The locale-specific range.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(128, 255)
        END_ARGUMENT_CONDITION
        // No post-condition. We are completely unaware of
        // locale-specific return values.
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE('0', '9')
          RANGE('A', 'Z')
          RANGE('a', 'z')
          RANGE(128, 255)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(isalpha, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE // isupper() or islower(). Note that 'Z' is less than 'a'.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE('A', 'Z')
          RANGE('a', 'z')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE // The locale-specific range.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(128, 255)
        END_ARGUMENT_CONDITION
      END_CASE
      CASE // Other.
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE('A', 'Z')
          RANGE('a', 'z')
          RANGE(128, 255)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(isascii, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE // Is ASCII.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(0, 127)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE(0, 127)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(isblank, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          SINGLE_VALUE('\t')
          SINGLE_VALUE(' ')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          SINGLE_VALUE('\t')
          SINGLE_VALUE(' ')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(iscntrl, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE // 0..31 or 127
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(0, 32)
          SINGLE_VALUE(127)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE(0, 32)
          SINGLE_VALUE(127)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(isdigit, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE // Is a digit.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE('0', '9')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE('0', '9')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(isgraph, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(33, 126)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE(33, 126)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(islower, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE // Is certainly lowercase.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE('a', 'z')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE // Is ascii but not lowercase.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(0, 127)
        END_ARGUMENT_CONDITION
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE('a', 'z')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE // The locale-specific range.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(128, 255)
        END_ARGUMENT_CONDITION
      END_CASE
      CASE // Is not an unsigned char.
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE(0, 255)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(isprint, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(32, 126)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE(32, 126)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(ispunct, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE('!', '/')
          RANGE(':', '@')
          RANGE('[', '`')
          RANGE('{', '~')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE('!', '/')
          RANGE(':', '@')
          RANGE('[', '`')
          RANGE('{', '~')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(isspace, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE // Space, '\f', '\n', '\r', '\t', '\v'.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(9, 13)
          SINGLE_VALUE(' ')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE // The locale-specific range.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(128, 255)
        END_ARGUMENT_CONDITION
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE(9, 13)
          SINGLE_VALUE(' ')
          RANGE(128, 255)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(isupper, ARGUMENT_TYPES(IntTy), RETURN_TYPE (IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE // Is certainly uppercase.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE('A', 'Z')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE // The locale-specific range.
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE(128, 255)
        END_ARGUMENT_CONDITION
      END_CASE
      CASE // Other.
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE('A', 'Z') RANGE(128, 255)
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(isxdigit, ARGUMENT_TYPES(IntTy), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(EvalCallAsPure))
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), WithinRange)
          RANGE('0', '9')
          RANGE('A', 'F')
          RANGE('a', 'f')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(OutOfRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
      CASE
        ARGUMENT_CONDITION(ARG_NO(0), OutOfRange)
          RANGE('0', '9')
          RANGE('A', 'F')
          RANGE('a', 'f')
        END_ARGUMENT_CONDITION
        RETURN_VALUE_CONDITION(WithinRange)
          SINGLE_VALUE(0)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY

    // The getc() family of functions that returns either a char or an EOF.
    SUMMARY(getc, ARGUMENT_TYPES(Irrelevant), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(NoEvalCall))
      CASE // FIXME: EOF is assumed to be defined as -1.
        RETURN_VALUE_CONDITION(WithinRange)
          RANGE(-1, 255)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(fgetc, ARGUMENT_TYPES(Irrelevant), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(NoEvalCall))
      CASE // FIXME: EOF is assumed to be defined as -1.
        RETURN_VALUE_CONDITION(WithinRange)
          RANGE(-1, 255)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(getchar, ARGUMENT_TYPES(), RETURN_TYPE(IntTy),
            INVALIDATION_APPROACH(NoEvalCall))
      CASE // FIXME: EOF is assumed to be defined as -1.
        RETURN_VALUE_CONDITION(WithinRange)
          RANGE(-1, 255)
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY

    // read()-like functions that never return more than buffer size.
    // We are not sure how ssize_t is defined on every platform, so we provide
    // three variants that should cover common cases.
    SUMMARY_WITH_VARIANTS(read)
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy),
              RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(ComparesToArgument)
            IS_LESS_THAN(ARG_NO(2))
          END_RETURN_VALUE_CONDITION
          RETURN_VALUE_CONDITION(WithinRange)
            RANGE(-1, IntMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy),
              RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(ComparesToArgument)
            IS_LESS_THAN(ARG_NO(2))
          END_RETURN_VALUE_CONDITION
          RETURN_VALUE_CONDITION(WithinRange)
            RANGE(-1, LongMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy),
              RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(ComparesToArgument)
            IS_LESS_THAN(ARG_NO(2))
          END_RETURN_VALUE_CONDITION
          RETURN_VALUE_CONDITION(WithinRange)
            RANGE(-1, LongLongMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
    END_SUMMARY_WITH_VARIANTS
    SUMMARY_WITH_VARIANTS(write)
      // Again, due to elusive nature of ssize_t, we have duplicate
      // our summaries to cover different variants.
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy),
              RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(ComparesToArgument)
            IS_LESS_THAN(ARG_NO(2))
          END_RETURN_VALUE_CONDITION
          RETURN_VALUE_CONDITION(WithinRange)
            RANGE(-1, IntMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy),
              RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(ComparesToArgument)
            IS_LESS_THAN(ARG_NO(2))
          END_RETURN_VALUE_CONDITION
          RETURN_VALUE_CONDITION(WithinRange)
            RANGE(-1, LongMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy),
              RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(ComparesToArgument)
            IS_LESS_THAN(ARG_NO(2))
          END_RETURN_VALUE_CONDITION
          RETURN_VALUE_CONDITION(WithinRange)
            RANGE(-1, LongLongMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
    END_SUMMARY_WITH_VARIANTS
    SUMMARY(fread,
            ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy, Irrelevant),
            RETURN_TYPE(SizeTy), INVALIDATION_APPROACH(NoEvalCall))
      CASE
        RETURN_VALUE_CONDITION(ComparesToArgument)
          IS_LESS_THAN(ARG_NO(2))
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY
    SUMMARY(fwrite,
            ARGUMENT_TYPES(Irrelevant, Irrelevant, SizeTy, Irrelevant),
            RETURN_TYPE(SizeTy), INVALIDATION_APPROACH(NoEvalCall))
      CASE
        RETURN_VALUE_CONDITION(ComparesToArgument)
          IS_LESS_THAN(ARG_NO(2))
        END_RETURN_VALUE_CONDITION
      END_CASE
    END_SUMMARY

    // getline()-like functions either fail or read at least the delimiter.
    SUMMARY_WITH_VARIANTS(getline)
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant),
              RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(WithinRange)
            SINGLE_VALUE(-1)
            RANGE(1, IntMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant),
              RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(WithinRange)
            SINGLE_VALUE(-1)
            RANGE(1, LongMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant),
              RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(WithinRange)
            SINGLE_VALUE(-1)
            RANGE(1, LongLongMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
    END_SUMMARY_WITH_VARIANTS
    SUMMARY_WITH_VARIANTS(getdelim)
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant, Irrelevant),
            RETURN_TYPE(IntTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(WithinRange)
            SINGLE_VALUE(-1)
            RANGE(1, IntMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant, Irrelevant),
            RETURN_TYPE(LongTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(WithinRange)
            SINGLE_VALUE(-1)
            RANGE(1, LongMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
      VARIANT(ARGUMENT_TYPES(Irrelevant, Irrelevant, Irrelevant, Irrelevant),
            RETURN_TYPE(LongLongTy), INVALIDATION_APPROACH(NoEvalCall))
        CASE
          RETURN_VALUE_CONDITION(WithinRange)
            SINGLE_VALUE(-1)
            RANGE(1, LongLongMax)
          END_RETURN_VALUE_CONDITION
        END_CASE
      END_VARIANT
    END_SUMMARY_WITH_VARIANTS
  };
}

void ento::registerStdCLibraryFunctionsChecker(CheckerManager &mgr) {
  // If this checker grows large enough to support C++, Objective-C, or other
  // standard libraries, we could use multiple register...Checker() functions,
  // which would register various checkers with the help of the same Checker
  // class, turning on different function summaries.
  mgr.registerChecker<StdLibraryFunctionsChecker>();
}

bool ento::shouldRegisterStdCLibraryFunctionsChecker(const LangOptions &LO) {
  return true;
}
