NoQ created this revision.
NoQ added reviewers: zaks.anna, dcoughlin.
NoQ added a subscriber: cfe-commits.
Herald added a subscriber: aemerson.

I've put together a simple checker that throws no warnings, but models some 
library functions, which has already helped us to suppress some false positives 
in other checkers in our runs.

For pure functions, i chose the old `evalCall()` approach instead of the 
body-farm approach because i wanted to produce less state splits. For example, 
this checker produce a single exploded graph branch for `ispunct()`'s non-zero 
branch, when its argument is in range ['!', '/'] U [':', '@'] U ['[', '`'] U 
['{', '~'] - i'm not sure if there's a way to write this out with if's and 
produce less than 4 branches. (Do we have any plans on merging branches more 
aggressively during analysis?) Because these functions are pure, we'd hardly 
ever want to catch them with`evalCall()` again in another checker.

Additionally, this checker's brace-initializers for function specifications are 
quite short - of course they're limited to very simple cases - the list of 
these cases can be expanded though.

The checker doesn't seem to be noticeably degrading performance. Here's an 
example of a false positve squashed: {F2000003} Here `line` is taken to be "", 
the `line++` statement is executed at least once (by looking at the exploded 
graph; there's lack of "entering loop body" diagnostic piece because loop 
condition has complicated CFG, which is why it fails to highlight - a separate 
issue), and the analyzer fails to realize that isspace('\0') is false.

http://reviews.llvm.org/D20811

Files:
  include/clang/StaticAnalyzer/Checkers/Checkers.td
  lib/StaticAnalyzer/Checkers/CMakeLists.txt
  lib/StaticAnalyzer/Checkers/LibraryFunctionsChecker.cpp
  test/Analysis/library-functions.c

Index: test/Analysis/library-functions.c
===================================================================
--- /dev/null
+++ test/Analysis/library-functions.c
@@ -0,0 +1,109 @@
+// RUN: %clang_cc1 -analyze -analyzer-checker=unix.LibraryFunctions,debug.ExprInspection -verify %s
+
+void clang_analyzer_eval(int);
+int glob;
+
+typedef struct FILE FILE;
+int getc(FILE *);
+#define EOF -1
+void test_getc(FILE *fp) {
+  int x;
+  while ((x = getc(fp)) != EOF) {
+    clang_analyzer_eval(x > 255); // expected-warning{{FALSE}}
+    clang_analyzer_eval(x >= 0); // expected-warning{{TRUE}}
+  }
+}
+
+typedef unsigned long size_t;
+typedef signed long ssize_t;
+ssize_t write(int, const void *, size_t);
+void test_write(int fd, char *buf) {
+  glob = 1;
+  ssize_t x = write(fd, buf, 10);
+  clang_analyzer_eval(glob); // expected-warning{{UNKNOWN}}
+  if (x >= 0)
+    clang_analyzer_eval(x <= 10); // expected-warning{{TRUE}}
+  else
+    clang_analyzer_eval(x == -1); // expected-warning{{TRUE}}
+}
+
+size_t fread(void *, size_t, size_t, FILE *);
+void test_fread(FILE *fp, int *buf) {
+  size_t x = fread(buf, sizeof(int), 10, fp);
+  clang_analyzer_eval(x <= 10); // expected-warning{{TRUE}}
+}
+
+ssize_t getline(char **, size_t *, FILE *);
+void test_getline(FILE *fp) {
+  char *line = 0;
+  size_t n = 0;
+  ssize_t len;
+  while ((len = getline(&line, &n, fp)) != -1) {
+    clang_analyzer_eval(len == 0); // expected-warning{{FALSE}}
+  }
+}
+
+int isascii(int);
+void test_isascii(int x) {
+  clang_analyzer_eval(isascii(123)); // expected-warning{{TRUE}}
+  clang_analyzer_eval(isascii(-1)); // expected-warning{{FALSE}}
+  if (isascii(x)) {
+    clang_analyzer_eval(x < 128); // expected-warning{{TRUE}}
+    clang_analyzer_eval(x >= 0); // expected-warning{{TRUE}}
+  } else {
+    if (x > 42)
+      clang_analyzer_eval(x >= 128); // expected-warning{{TRUE}}
+    else
+      clang_analyzer_eval(x < 0); // expected-warning{{TRUE}}
+  }
+  glob = 1;
+  isascii('a');
+  clang_analyzer_eval(glob); // expected-warning{{TRUE}}
+}
+
+int islower(int);
+void test_islower(int x) {
+  clang_analyzer_eval(islower('x')); // expected-warning{{TRUE}}
+  clang_analyzer_eval(islower('X')); // expected-warning{{FALSE}}
+  if (islower(x))
+    clang_analyzer_eval(x < 'a'); // expected-warning{{FALSE}}
+}
+
+int getchar(void);
+void test_getchar() {
+  int x = getchar();
+  if (x == EOF)
+    return;
+  clang_analyzer_eval(x < 0); // expected-warning{{FALSE}}
+  clang_analyzer_eval(x < 256); // expected-warning{{TRUE}}
+}
+
+int isalpha(int);
+void test_isalpha() {
+  clang_analyzer_eval(isalpha(']')); // expected-warning{{FALSE}}
+  clang_analyzer_eval(isalpha('Q')); // expected-warning{{TRUE}}
+  clang_analyzer_eval(isalpha(128)); // expected-warning{{UNKNOWN}}
+}
+
+int isalnum(int);
+void test_alnum() {
+  clang_analyzer_eval(isalnum('1')); // expected-warning{{TRUE}}
+  clang_analyzer_eval(isalnum(')')); // expected-warning{{FALSE}}
+}
+
+int isblank(int);
+void test_isblank() {
+  clang_analyzer_eval(isblank('\t')); // expected-warning{{TRUE}}
+  clang_analyzer_eval(isblank(' ')); // expected-warning{{TRUE}}
+  clang_analyzer_eval(isblank('\n')); // expected-warning{{FALSE}}
+}
+
+int ispunct(int);
+void test_ispunct(int x) {
+  clang_analyzer_eval(ispunct(' ')); // expected-warning{{FALSE}}
+  clang_analyzer_eval(ispunct(-1)); // expected-warning{{FALSE}}
+  clang_analyzer_eval(ispunct('#')); // expected-warning{{TRUE}}
+  clang_analyzer_eval(ispunct('_')); // expected-warning{{TRUE}}
+  if (ispunct(x))
+    clang_analyzer_eval(x < 127); // expected-warning{{TRUE}}
+}
Index: lib/StaticAnalyzer/Checkers/LibraryFunctionsChecker.cpp
===================================================================
--- /dev/null
+++ lib/StaticAnalyzer/Checkers/LibraryFunctionsChecker.cpp
@@ -0,0 +1,644 @@
+//=== LibraryFunctionsChecker.cpp - integer overflows checker ---*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This checker improves modeling of a few simple library functions.
+// It does not throw warnings.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.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 LibraryFunctionsChecker : public Checker<check::PostCall, eval::Call> {
+  static const uint32_t Ret = std::numeric_limits<uint32_t>::max();
+  enum ValueRangeKindTy { Outside, Inside, ComparesToArgument };
+  enum InvalidationKindTy { Normal, Pure };
+  typedef std::vector<std::pair<int64_t, int64_t>> IntRangeVector;
+
+  class ValueRange {
+  private:
+    // ArgNo in CallExpr and CallEvent is defined is Unsigned, but
+    // obviously uint32_t should be enough for most practical purposes.
+    uint32_t ArgNo; // Argument to which we apply the range.
+    ValueRangeKindTy Kind; // Kind of range definition.
+    IntRangeVector Args; // Polymorphic arguments.
+
+  public:
+    ValueRange(uint32_t ArgNo, ValueRangeKindTy Kind,
+               const IntRangeVector &Args)
+        : ArgNo(ArgNo), Kind(Kind), Args(Args) {}
+
+    uint32_t 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;
+    }
+
+    uint32_t getOtherArgNo() const {
+      assert(Kind == ComparesToArgument);
+      assert(Args.size() == 1);
+      return static_cast<uint32_t>(Args[0].second);
+    }
+
+    const IntRangeVector &getRanges() const {
+      assert(Kind != ComparesToArgument);
+      return Args;
+    }
+  };
+
+  typedef std::vector<ValueRange> ValueRangeSet;
+
+  // This spec is a bit more specific than CallDescription.
+  struct FunctionSpec {
+    const std::vector<QualType> ArgTypes;
+    const QualType RetType;
+    const InvalidationKindTy InvalidationKind;
+    const std::vector<ValueRangeSet> Ranges;
+  };
+
+  typedef std::map<std::string, FunctionSpec> FunctionSpecMapTy;
+  mutable FunctionSpecMapTy FunctionSpecMap;
+
+  static QualType getArgType(const FunctionSpec &Spec, uint32_t ArgNo) {
+    return ArgNo == Ret ? Spec.RetType : Spec.ArgTypes[ArgNo];
+  }
+  static QualType getArgType(const CallEvent &Call, uint32_t ArgNo) {
+    return ArgNo == Ret ? Call.getResultType().getCanonicalType()
+                        : Call.getArgExpr(ArgNo)->getType().getCanonicalType();
+  }
+  static QualType getArgType(const CallExpr *CE, uint32_t ArgNo) {
+    return ArgNo == Ret ? CE->getType().getCanonicalType()
+                        : CE->getArg(ArgNo)->getType().getCanonicalType();
+  }
+  static SVal getArgSVal(const CallEvent &Call, uint32_t ArgNo) {
+    return ArgNo == Ret ? Call.getReturnValue() : Call.getArgSVal(ArgNo);
+  }
+
+public:
+  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+  bool evalCall(const CallExpr *CE, CheckerContext &C) const;
+
+private:
+  Optional<FunctionSpec> findFunctionSpec(const FunctionDecl *FD,
+                                          const CallExpr *CE,
+                                          CheckerContext &C) const;
+
+  void initFunctionSpecs(BasicValueFactory &BVF) const;
+};
+} // end of anonymous namespace
+
+void LibraryFunctionsChecker::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<FunctionSpec> FoundSpec = findFunctionSpec(FD, CE, C);
+  if (!FoundSpec)
+    return;
+
+  const FunctionSpec &Spec = *FoundSpec;
+
+  // Now apply ranges.
+
+  SValBuilder &SVB = C.getSValBuilder();
+  BasicValueFactory &BVF = SVB.getBasicValueFactory();
+  ConstraintManager &CM = C.getConstraintManager();
+  ProgramStateRef State = C.getState();
+  QualType CondT = SVB.getConditionType();
+
+  for (const auto &VRS: Spec.Ranges) {
+
+    ProgramStateRef NewState = State;
+
+    for (const auto &VR: VRS) {
+      QualType T = getArgType(Spec, VR.getArgNo());
+      SVal V = getArgSVal(Call, VR.getArgNo());
+
+      // In case we encounter unknown or undefined values,
+      // we would not be able to add ranges on them anyway,
+      // but other ranges are still worth adding.
+      switch (VR.getKind()) {
+      case ComparesToArgument: {
+        BinaryOperator::Opcode Op = VR.getOpcode();
+        uint32_t OtherArg = VR.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(NewState, Op, V, OtherV, CondT)
+                             .getAs<DefinedOrUnknownSVal>())
+          NewState = NewState->assume(*CompV, true);
+        break;
+      }
+      case Outside:
+        if (auto N = V.getAs<NonLoc>()) {
+          const IntRangeVector &R = VR.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);
+            NewState =
+                CM.assumeWithinInclusiveRange(NewState, *N, Min, Max, false);
+            if (!NewState)
+              break;
+          }
+        }
+        break;
+      case Inside:
+        // "Inside 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 IntRangeVector &R = VR.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 - 1, T);
+          if (Left != PlusInf) {
+            assert(MinusInf <= Left);
+            NewState = CM.assumeWithinInclusiveRange(NewState, *N,
+                                                     MinusInf, Left, false);
+            if (!NewState)
+              break;
+          }
+
+          const llvm::APSInt &Right = BVF.getValue(R[E - 1].second + 1, T);
+          if (Right != MinusInf) {
+            assert(Right <= PlusInf);
+            NewState = CM.assumeWithinInclusiveRange(NewState, *N,
+                                                     Right, PlusInf, false);
+            if (!NewState)
+              break;
+          }
+
+          for (size_t I = 1; I != E; ++I) {
+            const llvm::APSInt &Min = BVF.getValue(R[I - 1].second + 1, T);
+            const llvm::APSInt &Max = BVF.getValue(R[I].first - 1, T);
+            assert(Min <= Max);
+            NewState = CM.assumeWithinInclusiveRange(NewState, *N, Min, Max,
+                                                     false);
+            if (!NewState)
+              break;
+          }
+        }
+        break;
+      }
+
+      if (!NewState)
+        break;
+    }
+
+    if (NewState)
+      C.addTransition(NewState);
+  }
+}
+
+bool LibraryFunctionsChecker::evalCall(const CallExpr *CE,
+                                       CheckerContext &C) const {
+  const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(CE->getCalleeDecl());
+  if (!FD)
+    return false;
+
+  Optional<FunctionSpec> FoundSpec = findFunctionSpec(FD, CE, C);
+  if (!FoundSpec)
+    return false;
+
+  const FunctionSpec &Spec = *FoundSpec;
+  switch (Spec.InvalidationKind) {
+  case Pure: {
+    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 Normal:
+    return false;
+  }
+  llvm_unreachable("Unknown invalidation kind!");
+}
+
+Optional<LibraryFunctionsChecker::FunctionSpec>
+LibraryFunctionsChecker::findFunctionSpec(const FunctionDecl *FD,
+                                          const CallExpr *CE,
+                                          CheckerContext &C) const {
+  // Note: we cannot always obtain FD from CE (eg. virtual call).
+  assert(CE);
+
+  if (!FD)
+    return None;
+
+  SValBuilder &SVB = C.getSValBuilder();
+  BasicValueFactory &BVF = SVB.getBasicValueFactory();
+  initFunctionSpecs(BVF);
+
+  std::string Name = FD->getQualifiedNameAsString();
+  if (Name.empty() || !C.isCLibraryFunction(FD, Name))
+    return None;
+
+  auto FSMI = FunctionSpecMap.find(Name);
+  if (FSMI == FunctionSpecMap.end())
+    return None;
+
+  // Verify that function signature matches the spec in advance,
+  // so that we didn't have to roll back if anything goes wrong.
+
+  // Check number of arguments:
+  const FunctionSpec &Spec = FSMI->second;
+  if (CE->getNumArgs() != Spec.ArgTypes.size())
+    return None;
+
+  // Check return type if relevant:
+  if (!Spec.RetType.isNull() &&
+      Spec.RetType != CE->getType().getCanonicalType())
+    return None;
+
+  // Check argument types when relevant:
+  for (size_t I = 0, E = Spec.ArgTypes.size(); I != E; ++I) {
+    QualType FormalT = Spec.ArgTypes[I];
+    // Null type marks irrelevant arguments.
+    if (FormalT.isNull())
+      continue;
+    assert(!FormalT->isVoidType() &&
+           "We should have had no significant void types in the spec");
+    assert(FormalT.isCanonical() &&
+           "We should only have canonical types in the spec");
+    // FIXME: lift this assert (but not the ones above!)
+    assert(FormalT->isIntegralOrEnumerationType() &&
+           "We only support integral ranges in the spec");
+    QualType ActualT = getArgType(CE, I);
+    assert(ActualT.isCanonical());
+    if (ActualT != FormalT)
+      return None;
+  }
+  return FSMI->second;
+}
+
+void LibraryFunctionsChecker::initFunctionSpecs(BasicValueFactory &BVF) const {
+  if (!FunctionSpecMap.empty())
+    return;
+
+  ASTContext &ACtx = BVF.getContext();
+
+  QualType Irrelevant;
+  QualType IntTy = ACtx.IntTy;
+  QualType SizeTy = ACtx.getSizeType();
+  QualType SSizeTy = ACtx.getIntTypeForBitwidth(ACtx.getTypeSize(SizeTy), true);
+
+  // Don't worry about truncation here, it'd be cast back to SIZE_MAX when used.
+  int64_t SizeMax = BVF.getMaxValue(SizeTy).getLimitedValue();
+  int64_t SSizeMax = BVF.getMaxValue(SSizeTy).getLimitedValue();
+
+  // NOTE: The signature needs to have the correct number of arguments.
+  // NOTE: However, insert Irrelevant when the type is insignificant.
+  // NOTE: Argument ranges should always cover all variants. If return value
+  //       is completely unknown, omit it from the respective range set.
+  // NOTE: Upon comparing to another argument, the other argument is casted
+  //       to the current argument's type. This avoids proper promotion but
+  //       seems useful.
+  // NOTE: All types in the spec need to be canonical.
+  //
+  // The format is as follows:
+  //
+  //{ "function name",
+  //  { spec:
+  //    { argument types list, ... }, return type, purity, { range sets list:
+  //      { ranges list:
+  //        { argument index, inside or outside, {{from, to}, ...} },
+  //        { argument index, compares to argument, {{how, which}} },
+  //        ...
+  //      }
+  //    }
+  //  }
+  //}
+  FunctionSpecMap = {
+    // The isascii() family of functions.
+    { "isalnum",
+      {
+        { IntTy }, IntTy, Pure, {
+          { // isupper() or islower() or isdigit()
+            { 0U, Inside, {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          { // The locale-specific range.
+            { 0U, Inside, {{128, 255}} }
+          },
+          { // Other.
+            { 0U, Outside, {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {128, 255}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "isalpha",
+      {
+        { IntTy }, IntTy, Pure, {
+          { // isupper() or islower(). Note that 'Z' is less than 'a'.
+            { 0U, Inside, {{'A', 'Z'}, {'a', 'z'}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          { // The locale-specific range.
+            { 0U, Inside, {{128, 255}} },
+          },
+          { // Other.
+            { 0U, Outside, {{'A', 'Z'}, {'a', 'z'}, {128, 255}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "isascii",
+      {
+        { IntTy }, IntTy, Pure, {
+          { // Is ASCII.
+            { 0U, Inside, {{0, 127}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          { // Is not ASCII.
+            { 0U, Outside, {{0, 127}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "isblank",
+      {
+        { IntTy }, IntTy, Pure, {
+          { // Is tab or space.
+            { 0U, Inside, {{'\t', '\t'}, {' ', ' '}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          { // Other.
+            { 0U, Outside, {{'\t', '\t'}, {' ', ' '}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "iscntrl",
+      {
+        { IntTy }, IntTy, Pure, {
+          { // 0..31 or 127
+            { 0U, Inside, {{0, 32}, {127, 127}} },
+            { Ret, Outside, {{0, 0}} },
+          },
+          {
+            { 0U, Outside, {{0, 32}, {127, 127}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "isdigit",
+      {
+        { IntTy }, IntTy, Pure, {
+          { // Is a digit.
+            { 0U, Inside, {{'0', '9'}} },
+            { Ret, Outside, {{0, 0}} },
+          },
+          {
+            { 0U, Outside, {{'0', '9'}} },
+            { Ret, Inside, {{0, 0}} },
+          }
+        }
+      }
+    },
+    { "isgraph",
+      {
+        { IntTy }, IntTy, Pure, {
+          {
+            { 0U, Inside, {{33, 126}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          {
+            { 0U, Outside, {{33, 126}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "islower",
+      {
+        { IntTy }, IntTy, Pure, {
+          { // Is certainly uppercase.
+            { 0U, Inside, {{'a', 'z'}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          { // Is ascii but not uppercase.
+            { 0U, Inside, {{0, 127}} },
+            { 0U, Outside, {{'a', 'z'}} },
+            { Ret, Inside, {{0, 0}} }
+          },
+          { // The locale-specific range.
+            { 0U, Inside, {{128, 255}} }
+          },
+          { // Is not an unsigned char.
+            { 0U, Outside, {{0, 255}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "isprint",
+      {
+        { IntTy }, IntTy, Pure, {
+          {
+            { 0U, Inside, {{32, 126}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          {
+            { 0U, Outside, {{32, 126}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "ispunct",
+      {
+        { IntTy }, IntTy, Pure, {
+          {
+            { 0U, Inside, {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          {
+            { 0U, Outside, {{'!', '/'}, {':', '@'}, {'[', '`'}, {'{', '~'}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "isspace",
+      {
+        { IntTy }, IntTy, Pure, {
+          { // Space, '\f', '\n', '\r', '\t', '\v'.
+            { 0U, Inside, {{9, 13}, {' ', ' '}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          { // The locale-specific range.
+            { 0U, Inside, {{128, 255}} }
+          },
+          {
+            { 0U, Outside, {{9, 13}, {' ', ' '}, {128, 255}} },
+            { Ret, Inside, {{0, 0}} }
+          },
+        }
+      }
+    },
+    { "isupper",
+      {
+        { IntTy }, IntTy, Pure, {
+          { // Is certainly uppercase.
+            { 0U, Inside, {{'A', 'Z'}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          { // The locale-specific range.
+            { 0U, Inside, {{128, 255}} }
+          },
+          { // Other.
+            { 0U, Outside, {{'A', 'Z'}, {128, 255}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+    { "isxdigit",
+      {
+        { IntTy }, IntTy, Pure, {
+          {
+            { 0U, Inside, {{'0', '9'}, {'A', 'F'}, {'a', 'f'}} },
+            { Ret, Outside, {{0, 0}} }
+          },
+          {
+            { 0U, Outside, {{'0', '9'}, {'A', 'F'}, {'a', 'f'}} },
+            { Ret, Inside, {{0, 0}} }
+          }
+        }
+      }
+    },
+
+    // The getc() family of functions that returns either a char or an EOF.
+    { "getc",
+      {
+        { Irrelevant }, IntTy, Normal, {
+          { // FIXME: EOF is assumed to be defined as -1.
+            { Ret, Inside, {{-1, 255}} }
+          }
+        }
+      }
+    },
+    { "fgetc",
+      {
+        { Irrelevant }, IntTy, Normal, {
+          { // FIXME: EOF is assumed to be defined as -1.
+            { Ret, Inside, {{-1, 255}} }
+          }
+        }
+      }
+    },
+    { "getchar",
+      {
+        { }, IntTy, Normal, {
+          { // FIXME: EOF is assumed to be defined as -1.
+            { Ret, Inside, {{-1, 255}} }
+          }
+        }
+      }
+    },
+
+    // read()-like functions that never return more than buffer size.
+    { "read",
+      {
+        { Irrelevant, Irrelevant, SizeTy }, SSizeTy, Normal, {
+          {
+            { Ret, ComparesToArgument, {{BO_LE, 2U}} },
+            { Ret, Inside, {{-1, SSizeMax}} }
+          },
+        }
+      }
+    },
+    { "write",
+      {
+        { Irrelevant, Irrelevant, SizeTy }, SSizeTy, Normal, {
+          {
+            { Ret, ComparesToArgument, {{BO_LE, 2U}} },
+            { Ret, Inside, {{-1, SSizeMax}} }
+          },
+        }
+      }
+    },
+    { "fread",
+      {
+        { Irrelevant, Irrelevant, SizeTy, Irrelevant }, SizeTy, Normal, {
+          {
+            { Ret, Inside, {{0, SizeMax}} },
+            { Ret, ComparesToArgument, {{BO_LE, 2U}} }
+          }
+        }
+      }
+    },
+    { "fwrite",
+      {
+        { Irrelevant, Irrelevant, SizeTy, Irrelevant }, SizeTy, Normal, {
+          {
+            { Ret, Inside, {{0, SizeMax}} },
+            { Ret, ComparesToArgument, {{BO_LE, 2U}} }
+          }
+        }
+      }
+    },
+
+    // getline()-like functions either fail or read at least the delimiter.
+    { "getline",
+      {
+        { Irrelevant, Irrelevant, Irrelevant }, SSizeTy, Normal, {
+          {
+            { Ret, Inside, {{-1, -1}, {1, SSizeMax}} }
+          },
+        }
+      }
+    },
+    { "getdelim",
+      {
+        { Irrelevant, Irrelevant, Irrelevant, Irrelevant }, SSizeTy, Normal, {
+          {
+            { Ret, Inside, {{-1, -1}, {1, SSizeMax}} }
+          },
+        }
+      }
+    }
+  };
+}
+
+void ento::registerLibraryFunctionsChecker(CheckerManager &mgr) {
+  mgr.registerChecker<LibraryFunctionsChecker>();
+}
Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -34,6 +34,7 @@
   GenericTaintChecker.cpp
   IdenticalExprChecker.cpp
   IvarInvalidationChecker.cpp
+  LibraryFunctionsChecker.cpp
   LLVMConventionsChecker.cpp
   LocalizationChecker.cpp
   MacOSKeychainAPIChecker.cpp
Index: include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -380,6 +380,10 @@
   HelpText<"Check for proper usage of vfork">,
   DescFile<"VforkChecker.cpp">;
 
+def LibraryFunctionsChecker : Checker<"LibraryFunctions">,
+  HelpText<"Improve modeling of common library functions">,
+  DescFile<"LibraryFunctionsChecker.cpp">;
+
 } // end "unix"
 
 let ParentPackage = UnixAlpha in {
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to