haowei created this revision.
Herald added subscribers: xazax.hun, mgorny.

This patch adds a new Static Analyzer checker for the correct use of handle in 
Magenta kernel.  The concept of handle is very similar to file descriptor in 
Unix.  This checker checks leaks, use after release and double release issues 
in Magenta source code. We have tested this checker internally and it has 
detects several issues in our code. We are still improving and adding new 
features to this checker so any comments or suggestions are appreciated.


https://reviews.llvm.org/D34724

Files:
  include/clang/StaticAnalyzer/Checkers/Checkers.td
  lib/StaticAnalyzer/Checkers/CMakeLists.txt
  lib/StaticAnalyzer/Checkers/MagentaHandleChecker.cpp
  test/Analysis/mxhandle.c

Index: test/Analysis/mxhandle.c
===================================================================
--- /dev/null
+++ test/Analysis/mxhandle.c
@@ -0,0 +1,217 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.magenta.MagentaHandleChecker -analyzer-store=region -verify %s
+
+typedef __typeof__(sizeof(int)) size_t;
+typedef int mx_status_t;
+typedef __typeof__(sizeof(int)) mx_handle_t;
+typedef unsigned int uint32_t;
+#define NULL ((void *)0)
+
+mx_status_t mx_channel_create(
+   uint32_t options,
+   mx_handle_t* out0,
+   mx_handle_t* out1);
+
+mx_status_t mx_handle_close(mx_handle_t handle);
+
+mx_status_t mx_channel_read(mx_handle_t handle, uint32_t options,
+                            void* bytes, mx_handle_t* handles,
+                            uint32_t num_bytes, uint32_t num_handles,
+                            uint32_t* actual_bytes, uint32_t* actual_handles);
+
+mx_status_t mx_channel_write(mx_handle_t handle, uint32_t options,
+                             void* bytes, uint32_t num_bytes,
+                             mx_handle_t* handles, uint32_t num_handles);
+
+void escapeMethod(mx_handle_t *in);
+void useHandle(mx_handle_t handle);
+
+// End of declaration
+
+void checkNoLeak01() {
+  mx_handle_t sa, sb;
+  mx_channel_create(0, &sa, &sb);
+  mx_handle_close(sa);
+  mx_handle_close(sb);
+}
+
+void checkNoLeak02() {
+  mx_handle_t ay[2];
+  mx_channel_create(0, &ay[0], &ay[1]);
+  mx_handle_close(ay[0]);
+  mx_handle_close(ay[1]);
+}
+
+void checkNoLeak03() {
+  mx_handle_t ay[2];
+  mx_channel_create(0, &ay[0], &ay[1]);
+  for (int i = 0; i < 2; i++) {
+    mx_handle_close(ay[i]);
+  }
+}
+
+mx_handle_t checkNoLeak04() {
+  mx_handle_t sa, sb;
+  mx_channel_create(0, &sa, &sb);
+  mx_handle_close(sa);
+  return sb; // no warning
+}
+
+mx_handle_t checkNoLeak05(mx_handle_t *out1) {
+  mx_handle_t sa, sb;
+  mx_channel_create(0, &sa, &sb);
+  *out1 = sa;
+  return sb; // no warning
+}
+
+void checkNoLeak06(mx_handle_t handle) {
+  mx_handle_t handlebuf[4];
+  uint32_t hcount;
+  mx_channel_read(handle, 0, NULL, handlebuf, 0, 4, 0, &hcount);
+  for (int i = 0; i < hcount; i++) {
+    mx_handle_close(handlebuf[i]);
+  }
+}
+
+void checkNoLeak07(mx_handle_t handle, uint32_t hcount) {
+  mx_handle_t handlebuf[6];
+  mx_channel_read(handle, 0, NULL, handlebuf, 0, hcount, 0, &hcount);
+  for (int i = 0; i < hcount; i++) {
+    mx_handle_close(handlebuf[i]);
+  }
+}
+
+void checkNoLeak08(mx_handle_t handle) {
+  mx_handle_t handlebuf[4];
+  uint32_t hcount;
+  mx_channel_read(handle, 0, NULL, handlebuf, 0, 4, 0, &hcount);
+  if (mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount) < 0) {
+    for (int i = 0; i < hcount; i++) {
+      mx_handle_close(handlebuf[i]);
+    }
+  }
+}
+
+void checkNoLeak09(mx_handle_t handle, uint32_t hcount) {
+  mx_handle_t handlebuf[6];
+  mx_channel_read(handle, 0, NULL, handlebuf, 0, hcount, 0, &hcount);
+  if (mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount) < 0) {
+    for (int i = 0; i < hcount; i++) {
+      mx_handle_close(handlebuf[i]);
+    }
+  }
+}
+
+void checkNoLeak10() {
+  mx_handle_t sa, sb;
+  if (mx_channel_create(0, &sa, &sb) < 0) {
+    return;
+  }
+  mx_handle_close(sa);
+  mx_handle_close(sb);
+}
+
+void checkNoLeak11(mx_handle_t handle, uint32_t hcount) {
+  mx_handle_t handlebuf[6];
+  mx_status_t r = mx_channel_read(handle, 0, NULL,
+                                  handlebuf, 0, hcount, 0, &hcount);
+  if (r < 0) {
+    return;
+  }
+  for (int i = 0; i < hcount; i++) {
+    mx_handle_close(handlebuf[i]);
+  }
+}
+
+void checkNoLeak12(int tag) {
+  mx_handle_t sa, sb;
+  if (mx_channel_create(0, &sa, &sb) < 0) {
+    return;
+  }
+  if (tag) {
+    escapeMethod(&sa);
+    escapeMethod(&sb);
+  }
+  mx_handle_close(sa);
+  mx_handle_close(sb);
+}
+
+void checkLeak01() {
+  mx_handle_t sa, sb;
+  mx_channel_create(0, &sa, &sb);
+} // expected-warning {{Allocated handle is never released; potential resource leak}}
+
+void checkLeak02(int tag) {
+  mx_handle_t sa, sb;
+  mx_channel_create(0, &sa, &sb);
+  if (tag) {
+    mx_handle_close(sa);
+  }
+  mx_handle_close(sb); // expected-warning {{Allocated handle is never released; potential resource leak}}
+}
+
+void checkLeak03(mx_handle_t handle) {
+  mx_handle_t handlebuf[4];
+  uint32_t hcount;
+  mx_status_t r = mx_channel_read(handle, 0, NULL, handlebuf, 0, 4, 0, &hcount);
+  if (r < 0) {
+    return;
+  }
+  for (int i = 0; i < hcount -1; i++) {
+    mx_handle_close(handlebuf[i]);
+  }
+} // expected-warning {{Allocated handle is never released; potential resource leak}}
+
+void checkLeak04(mx_handle_t handle) {
+  mx_handle_t handlebuf[3];
+  uint32_t hcount;
+  mx_status_t r = mx_channel_read(handle, 0, NULL, handlebuf, 0, 3, 0, &hcount);
+  if (r < 0) {
+    return;
+  }
+  if (mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount - 1) < 0) {
+    for (int i = 0; i < hcount; i++) {
+      mx_handle_close(handlebuf[i]);
+    }
+  }
+} // expected-warning {{Allocated handle is never released; potential resource leak}}
+
+void checkLeak05(mx_handle_t handle, uint32_t hcount) {
+  mx_handle_t handlebuf[6];
+  mx_status_t r = mx_channel_read(
+                  handle, 0, NULL, handlebuf, 0, hcount, 0, &hcount);
+  if (r < 0) {
+    return;
+  }
+  if (mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount - 1) < 0) {
+    for (int i = 0; i < hcount; i++) {
+      mx_handle_close(handlebuf[i]);
+    }
+  }
+} // expected-warning {{Allocated handle is never released; potential resource leak}}
+
+void checkLeak06(mx_handle_t handle, uint32_t hcount) {
+  mx_handle_t handlebuf[6];
+  mx_channel_read(handle, 0, NULL, handlebuf, 0, hcount, 0, &hcount);
+  mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount); // It may fail and handles are not released.
+} // expected-warning {{Allocated handle is never released; potential resource leak}}
+
+void checkDoubleRelease01(int tag) {
+  mx_handle_t sa, sb;
+  mx_channel_create(0, &sa, &sb);
+  if (tag) {
+    mx_handle_close(sa);
+  }
+  mx_handle_close(sa); // expected-warning {{Releasing a previously released handle}}
+  mx_handle_close(sb);
+}
+
+void checkUseAfterFree01(int tag) {
+  mx_handle_t sa, sb;
+  mx_channel_create(0, &sa, &sb);
+  if (tag) {
+    mx_handle_close(sa);
+  }
+  useHandle(sa); // expected-warning {{Using a previously released handle}}
+  mx_handle_close(sa);
+  mx_handle_close(sb);
+}
Index: lib/StaticAnalyzer/Checkers/MagentaHandleChecker.cpp
===================================================================
--- /dev/null
+++ lib/StaticAnalyzer/Checkers/MagentaHandleChecker.cpp
@@ -0,0 +1,876 @@
+//== MagentaHandleChecker.cpp - Magenta Handle 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 checks if the handle of magenta kernel is properly used
+// according to following rules.
+//   - If a handle is allocated, it should be closed/released before execution
+//        ends.
+//   - If a handle is closed/released, it should not be closed/released again.
+//   - If a handle is closed/released, it should not be used for other purposes
+//        such as I/O.
+//
+// In this checker, each tracked handle is associated with a state. When the
+// handle variable is passed to different function calls or syscalls, its state
+// changes. As illustrated in the following ASCII Art:
+//
+//                          mx_channel_write failed
+//                                +---------+
+//                                |         |
+//                                |         |
+//                                |         |      As argument
+//  mx_channel_create succeeded +-+---------v-+    in uninlined   +---------+
+//  mx_channel_read succeeded   |             |    calls          |         |
+//            +----------------->  Allocated  +-------------------> Escaped |
+//            |                 |             |     As return     |         |
+//            |                 +-----+------++     value or      +---------+
+//            |                       |      |      assigned
+//            |       mx_handle_close |      |      back to argument.
+//            |       mx_channel_write|      +--+
+//            |        succeeded      |         |handle out+--------+
+//            |                  +----v-----+   |of scope  |        |
+//            |                  |          |   +----------> Leaked |
+// +----------+--+               | Released |              |(REPORT)|
+// |             |               |          |              +--------+
+// | Not tracked <--+            +----+---+-+
+// |             |  |                 |   |       As argument
+// +------+------+  |  mx_handle_close|   +------+in function
+//        |         |                 |          |call
+//        |         |            +----v-----+    |     +-----------+
+//        +---------+            |          |    |     |           |
+//    mx_channel_create failed   | Double   |    +-----> Use after |
+//    mx_channel_read failed     | released |          | released  |
+//                               | (REPORT) |          | (REPORT)  |
+//                               +----------+          +-----------+
+//
+//
+// If a tracked handle ends up in "Released" or "Escaped" state, we assume it
+// is properly used. Otherwise a bug will be reported.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/ParentMap.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Basic/TargetInfo.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.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"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+#include <climits>
+#include <stdio.h>
+#include <string>
+#include <utility>
+#include <limits.h>
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+
+enum CallKind {
+  // Will add more calls in the future. For now only worried about the ones
+  // related to channel creation.
+  MX_CHANNEL_CREATE,
+  MX_CHANNEL_READ,
+  MX_CHANNEL_WRITE,
+  MX_HANDLE_CLOSE,
+  UNRELATED_CALL
+};
+
+struct HandleState {
+private:
+  enum Kind {
+    // Handle is allocated
+    Allocated,
+    // Handle is released
+    Released,
+    // Handle is no longer trackable
+    Escaped
+  } K;
+
+  HandleState(Kind k) : K(k) {}
+
+public:
+  bool operator==(const HandleState &X) const { return K == X.K; }
+  bool isAllocated() const { return K == Allocated; }
+  bool isReleased() const { return K == Released; }
+  bool isEscaped() const { return K == Escaped; }
+
+  static HandleState getAllocated() { return HandleState(Allocated); }
+
+  static HandleState getReleased() { return HandleState(Released); }
+
+  static HandleState getEscaped() { return HandleState(Escaped); }
+
+  void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
+
+  void dump(raw_ostream &OS) const {
+    switch (static_cast<Kind>(K)) {
+#define CASE(ID) case ID: OS << #ID; break;
+      CASE(Allocated)
+      CASE(Released)
+      CASE(Escaped)
+    }
+  }
+  LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); }
+};
+
+class MagentaHandleChecker
+    : public Checker<check::DeadSymbols, check::PreCall,
+                     check::PreStmt<ReturnStmt>, check::PointerEscape,
+                     eval::Call, check::PostCall> {
+  std::unique_ptr<BugType> LeakBugType;
+  std::unique_ptr<BugType> DoubleReleaseBugType;
+  std::unique_ptr<BugType> UseAfterFreeBugType;
+  mutable llvm::StringMap<CallKind> FunctionNameMap;
+
+public:
+  MagentaHandleChecker();
+  // Checker callbacks
+  void checkPreCall(const CallEvent &Call, CheckerContext &Ctx) const;
+
+  void checkPostCall(const CallEvent &Call, CheckerContext &Ctx) const;
+
+  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &Ctx) const;
+
+  void checkPreStmt(const ReturnStmt *RS, CheckerContext &Ctx) const;
+
+  bool evalCall(const CallExpr *CE, CheckerContext &Ctx) const;
+
+  ProgramStateRef checkPointerEscape(ProgramStateRef State,
+                                     const InvalidatedSymbols &Escaped,
+                                     const CallEvent *Call,
+                                     PointerEscapeKind Kind) const;
+
+private:
+  bool processMxChannelRead(const CallExpr *CE, CheckerContext &C) const;
+
+  bool processMxChannelCreate(const CallExpr *CE, CheckerContext &C) const;
+
+  bool processMxChannelWrite(const CallExpr *CE, CheckerContext &C) const;
+
+  void processMxHandleClose(ProgramStateRef State, const CallEvent &Call,
+                            CheckerContext &Ctx) const;
+
+  void processUninlinedCalls(ProgramStateRef State, const CallEvent &Call,
+                             CheckerContext &Ctx) const;
+
+  // Bug report functions
+  void reportLeaks(ArrayRef<SymbolRef> LeakedHandles, CheckerContext &Ctx,
+                   ExplodedNode *ErrorNode) const;
+
+  void reportDoubleRelease(SymbolRef HandleSym, const SourceRange &Range,
+                           CheckerContext &Ctx) const;
+
+  void reportUseAfterFree(SymbolRef HandleSym, const SourceRange &Range,
+                          CheckerContext &Ctx) const;
+  // Utility functions
+  CallKind CheckCallSignature(const CallEvent &Call) const;
+
+  CallKind CheckCallSignature(const FunctionDecl *FuncDecl) const;
+
+  bool CheckExprIsZero(const Expr *ArgExpr, CheckerContext &Ctx) const;
+
+  // Return true only if it is certain that Sym will be Zero
+  static bool CheckSymbolConstraintToZero(SymbolRef Sym, ProgramStateRef State);
+
+  // Return true only if it is certain that Sym will not be Zero
+  static bool CheckSymbolConstraintToNotZero(SymbolRef Sym,
+                                             ProgramStateRef State);
+
+  bool isLeaked(SymbolRef SymHandle, const HandleState &CurHandleState,
+                bool isSymDead, ProgramStateRef State) const;
+};
+} // end anonymous namespace
+
+// Handle -> HandleState map
+REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState)
+
+MagentaHandleChecker::MagentaHandleChecker() {
+  // Initialize the bug types.
+  LeakBugType.reset(
+      new BugType(this, "MX handle leak", "Magenta Handle Error"));
+  DoubleReleaseBugType.reset(
+      new BugType(this, "MX handle double release", "Magenta Handle Error"));
+  UseAfterFreeBugType.reset(
+      new BugType(this, "MX handle use after free", "Magenta Handle Error"));
+
+  FunctionNameMap = {{"mx_channel_create", MX_CHANNEL_CREATE},
+                        {"mx_channel_read", MX_CHANNEL_READ},
+                        {"mx_handle_close", MX_HANDLE_CLOSE},
+                        {"mx_channel_write", MX_CHANNEL_WRITE}};
+}
+
+CallKind
+MagentaHandleChecker::CheckCallSignature(const FunctionDecl *FuncDecl) const {
+  if (!FuncDecl)
+    return UNRELATED_CALL;
+
+  DeclarationName DeclName = FuncDecl->getDeclName();
+  if (!DeclName.isIdentifier())
+    return UNRELATED_CALL;
+
+  // Use function name to match target function calls
+  // Type signature check is not necessary because C function
+  // cannot be overridden
+  StringRef FuncName = FuncDecl->getName();
+  if (FunctionNameMap.count(FuncName) != 1)
+    return UNRELATED_CALL;
+  return FunctionNameMap[FuncName];
+}
+
+CallKind MagentaHandleChecker::CheckCallSignature(const CallEvent &Call) const {
+  if (const AnyFunctionCall *FuncCall = dyn_cast<AnyFunctionCall>(&Call)) {
+    const FunctionDecl *FuncDecl = FuncCall->getDecl();
+    if (!FuncDecl)
+      return UNRELATED_CALL;
+
+    return CheckCallSignature(FuncDecl);
+  }
+  return UNRELATED_CALL;
+}
+
+bool MagentaHandleChecker::isLeaked(SymbolRef SymHandle,
+                                    const HandleState &CurHandleState,
+                                    bool isSymDead,
+                                    ProgramStateRef State) const {
+  if (isSymDead && CurHandleState.isAllocated()) {
+    // Check if handle value is MX_HANDLE_INVALID. If so, not a leak.
+    if (CheckSymbolConstraintToZero(SymHandle, State))
+      return false;
+
+    // Handle is allocated and dead. Leaked
+    return true;
+  }
+  // Handle not dead. Not a leak.
+  return false;
+}
+
+bool MagentaHandleChecker::CheckSymbolConstraintToZero(SymbolRef Sym,
+                                                       ProgramStateRef State) {
+  ConstraintManager &CMgr = State->getConstraintManager();
+  ConditionTruthVal IsZero = CMgr.isNull(State, Sym);
+  if (IsZero.isConstrained()) {
+    // The value can be compared to zero
+    if (IsZero.isConstrainedTrue())
+      return true;
+  }
+  return false;
+}
+
+bool MagentaHandleChecker::CheckSymbolConstraintToNotZero(
+    SymbolRef Sym, ProgramStateRef State) {
+  ConstraintManager &CMgr = State->getConstraintManager();
+  ConditionTruthVal IsZero = CMgr.isNull(State, Sym);
+  if (IsZero.isConstrained()) {
+    // The value can be compared to zero
+    if (IsZero.isConstrainedFalse())
+      return true;
+  }
+  return false;
+}
+
+ProgramStateRef MagentaHandleChecker::checkPointerEscape(
+    ProgramStateRef State, const InvalidatedSymbols &Escaped,
+    const CallEvent *Call, PointerEscapeKind Kind) const {
+  HStateMapTy TrackedHandle = State->get<HStateMap>();
+  SmallVector<SymbolRef, 2> EscapedSymbolRef;
+  for (auto &CurItem : TrackedHandle) {
+    SymbolRef Sym = CurItem.first;
+    if (Escaped.count(Sym) == 1)
+      EscapedSymbolRef.push_back(Sym);
+
+    const SymbolDerived *ElementSym = dyn_cast<SymbolDerived>(Sym);
+    if (!ElementSym)
+      continue;
+
+    const SymbolRef ParentSymbol = ElementSym->getParentSymbol();
+    if (Escaped.count(ParentSymbol) == 1)
+      EscapedSymbolRef.push_back(Sym);
+
+  }
+
+  for (SymbolRef EscapedHandle : EscapedSymbolRef) {
+    State = State->remove<HStateMap>(EscapedHandle);
+    State = State->set<HStateMap>(EscapedHandle, HandleState::getEscaped());
+  }
+
+  // Currently only handles passed through pointer/arrays are considered in
+  // this callback. There are some cases that the values of handles are wrapped
+  // in multiple layers of struct. Current implementation may failed to treat
+  // these handles as escaped, which causes false positives.
+  // TODO: Improve handle escape detection in the future.
+
+  return State;
+}
+
+bool MagentaHandleChecker::CheckExprIsZero(const Expr *ArgExpr,
+                                           CheckerContext &Ctx) const {
+  ProgramStateRef State = Ctx.getState();
+  const LocationContext *LCtx = Ctx.getLocationContext();
+  SVal ExprSVal = State->getSVal(ArgExpr, LCtx);
+  int64_t ExtVal;
+  if (ExprSVal.getBaseKind() == SVal::NonLocKind &&
+      ExprSVal.getSubKind() == nonloc::ConcreteIntKind) {
+    ExtVal = ExprSVal.castAs<nonloc::ConcreteInt>().getValue().getExtValue();
+  } else if (ExprSVal.getBaseKind() == SVal::LocKind &&
+             ExprSVal.getSubKind() == loc::ConcreteIntKind) {
+    ExtVal = ExprSVal.castAs<loc::ConcreteInt>().getValue().getExtValue();
+  } else {
+    return false;
+  }
+  if (ExtVal == 0)
+    return true;
+
+  return false;
+}
+
+void MagentaHandleChecker::checkPreCall(const CallEvent &Call,
+                                        CheckerContext &Ctx) const {
+  ProgramStateRef State = Ctx.getState();
+
+  switch (CheckCallSignature(Call)) {
+  case MX_HANDLE_CLOSE:
+    processMxHandleClose(State, Call, Ctx);
+    break;
+  default:
+    break;
+  }
+}
+
+void MagentaHandleChecker::checkPostCall(const CallEvent &Call,
+                                         CheckerContext &Ctx) const {
+  ProgramStateRef State = Ctx.getState();
+
+  switch (CheckCallSignature(Call)) {
+  case UNRELATED_CALL:
+    processUninlinedCalls(State, Call, Ctx);
+    break;
+  default:
+    break;
+  }
+}
+
+void MagentaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper,
+                                            CheckerContext &Ctx) const {
+  ProgramStateRef State = Ctx.getState();
+  SmallVector<SymbolRef, 2> LeakVector;
+  HStateMapTy TrackedHandle = State->get<HStateMap>();
+  for (auto &CurItem : TrackedHandle) {
+    SymbolRef Sym = CurItem.first;
+    // Workaround for zombie symbol issue.
+    bool IsSymDead = SymReaper.maybeDead(Sym);
+    const HandleState &CurHandleState = CurItem.second;
+    if (isLeaked(Sym, CurHandleState, IsSymDead, State))
+      LeakVector.push_back(Sym);
+
+    if (IsSymDead)
+      State = State->remove<HStateMap>(Sym);
+  }
+
+  if (!LeakVector.empty()) {
+    ExplodedNode *N = Ctx.generateNonFatalErrorNode(State);
+    if (!N)
+      return;
+
+    reportLeaks(LeakVector, Ctx, N);
+  } else {
+    Ctx.addTransition(State);
+  }
+}
+
+void MagentaHandleChecker::checkPreStmt(const ReturnStmt *RS,
+                                        CheckerContext &Ctx) const {
+  ProgramStateRef State = Ctx.getState();
+  const auto *LCtx = Ctx.getLocationContext();
+  if (!LCtx)
+    return;
+
+  const auto *SFCtx = LCtx->getCurrentStackFrame();
+  if (!SFCtx)
+    return;
+
+  const Expr *RetE = RS->getRetValue();
+  if (!RetE)
+    return;
+
+  SVal SValRet = State->getSVal(RetE, LCtx);
+  SymbolRef SymRet = SValRet.getAsSymbol();
+  if (!SymRet)
+    return;
+
+  const HandleState *SymState = State->get<HStateMap>(SymRet);
+  // Check if value returned in this ReturnStmt is tracked by analyzer
+  if (!SymState)
+    return;
+
+  if (SymState->isReleased()) {
+    // Use after release bug.
+    ExplodedNode *N = Ctx.generateErrorNode(State);
+    if (!N)
+      return;
+
+    reportUseAfterFree(SymRet, RS->getSourceRange(), Ctx);
+    return;
+  }
+  State = State->set<HStateMap>(SymRet, HandleState::getEscaped());
+  Ctx.addTransition(State);
+}
+
+bool MagentaHandleChecker::evalCall(const CallExpr *CE,
+                                    CheckerContext &C) const {
+  const FunctionDecl *FuncDecl = C.getCalleeDecl(CE);
+  switch (CheckCallSignature(FuncDecl)) {
+  case MX_CHANNEL_READ:
+    return processMxChannelRead(CE, C);
+  case MX_CHANNEL_CREATE:
+    return processMxChannelCreate(CE, C);
+  case MX_CHANNEL_WRITE:
+    return processMxChannelWrite(CE, C);
+  default:
+    break;
+  }
+  return false;
+}
+
+void MagentaHandleChecker::processUninlinedCalls(ProgramStateRef State,
+                                                 const CallEvent &Call,
+                                                 CheckerContext &Ctx) const {
+  // Process the handle escaped situation.
+  // When a function is not inlined for any reasons, if one of its
+  // arguments is an acquired handle, treat it as an escaped handle.
+  // This is just a naive approach to reduce the false positive rate, should
+  // be refined later, e.g. through annotation
+  // TODO: Use annotation to make it more accurate.
+  const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
+  if (!FD)
+    return;
+
+  if (Ctx.wasInlined)
+    return;
+
+  unsigned ArgsCount = Call.getNumArgs();
+  bool StateChanged = false;
+  if (!ArgsCount)
+    // No argument passed to this call, ignore it
+    return;
+  for (unsigned i = 0; i < ArgsCount; i++) {
+    SVal ArgSVal = Call.getArgSVal(i);
+    SymbolRef ArgSym = ArgSVal.getAsSymbol();
+    // Check if the argument is a pointer. If so, it should have been handled
+    // by checkPointerEscape, can be ignored here.
+    if (!ArgSym)
+      continue;
+
+    const HandleState *CurHandleState = State->get<HStateMap>(ArgSym);
+    // Check if the handle is tracked by analyzer. If not, we ignore it here.
+    if (!CurHandleState)
+      continue;
+
+    if (CurHandleState->isReleased())
+      // Use after free
+      reportUseAfterFree(ArgSym, Call.getSourceRange(), Ctx);
+    State = State->set<HStateMap>(ArgSym, HandleState::getEscaped());
+    StateChanged = true;
+  }
+  if (StateChanged)
+    Ctx.addTransition(State);
+}
+
+bool MagentaHandleChecker::processMxChannelRead(const CallExpr *CE,
+                                                CheckerContext &Ctx) const {
+  ProgramStateRef State = Ctx.getState();
+  const LocationContext *LCtx = Ctx.getLocationContext();
+  ProgramStateManager &Mgr = State->getStateManager();
+  ASTContext &ASTCtx = Mgr.getContext();
+  SValBuilder &svalBuilder = Ctx.getSValBuilder();
+  MemRegionManager &memRegionMgr = svalBuilder.getRegionManager();
+
+  // If handle related args are null. Do not process this call
+  const Expr *HandleBufExpr = CE->getArg(3);
+  const Expr *InputSizeExpr = CE->getArg(5);
+  const Expr *OutputSizeExpr = CE->getArg(7);
+  if (CheckExprIsZero(HandleBufExpr, Ctx) ||
+      CheckExprIsZero(InputSizeExpr, Ctx) ||
+      CheckExprIsZero(OutputSizeExpr, Ctx))
+    return false;
+
+  // Conjure a failed state which does not allocate any handle
+  DefinedOrUnknownSVal FailedStatusSVal =
+      svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, Ctx.blockCount());
+  ProgramStateRef FailedState = State->BindExpr(CE, LCtx, FailedStatusSVal);
+  FailedState = FailedState->assumeInclusiveRange(
+      FailedStatusSVal, llvm::APSInt::get(INT_MIN), llvm::APSInt::get(-1),
+      true);
+
+  // Conjure a successful state with return value is 0
+  // Invalidate the handlebuf pointer and handle_count pointer
+  SmallVector<SVal, 2> InvalidateSVal;
+  InvalidateSVal.push_back(State->getSVal(HandleBufExpr, LCtx));
+  InvalidateSVal.push_back(State->getSVal(OutputSizeExpr, LCtx));
+  State = State->invalidateRegions(InvalidateSVal, CE, Ctx.blockCount(), LCtx,
+                                   false, nullptr, nullptr, nullptr);
+
+  SVal StatusSVal = Ctx.getSValBuilder().makeIntVal(llvm::APSInt::get(0));
+  State = State->BindExpr(CE, LCtx, StatusSVal);
+
+  const MemRegion *MemHandleBuf =
+      State->getSVal(HandleBufExpr, LCtx).getAsRegion();
+  if (!MemHandleBuf)
+    return false;
+
+  // The handle buffer is not an array or a pointer to a memory space
+  // It might be a pointer to a local variable. Currently ignore it
+  // TODO: Handle the case when the handle buffer is a single mx_handle_t
+  // variable
+  if (!isa<ElementRegion>(MemHandleBuf))
+    return false;
+
+  const SubRegion *SuperRegionSub = dyn_cast<SubRegion>(
+      dyn_cast<ElementRegion>(MemHandleBuf)->getSuperRegion());
+  if (!SuperRegionSub)
+    return false;
+
+  SVal HandleBufSVal = State->getSVal(MemHandleBuf);
+  SymbolRef SymHandleBuf = HandleBufSVal.getAsSymbol();
+  if (!SymHandleBuf)
+    return false;
+
+  SymbolRef ParentSymbol =
+      dyn_cast<SymbolDerived>(SymHandleBuf)->getParentSymbol();
+  if (!ParentSymbol)
+    return false;
+
+  // The handle buffer is a typedValueRegion
+  const TypedValueRegion *TR = cast<TypedValueRegion>(MemHandleBuf);
+  QualType HandleType = TR->getValueType();
+
+  // Construct derived symbol associated with handle buffer
+  // 1. When the supplied handle count can be determined at call time
+  //   (e.g. const/known variable), conjure exactly the number of handle symbols
+  // 2. if 1. is not satisfied, conjure exactly 4 handles
+  SVal InputSizeSVal = State->getSVal(InputSizeExpr, LCtx);
+  int64_t ExtVal = 4;
+  if (InputSizeSVal.getBaseKind() == SVal::NonLocKind &&
+      InputSizeSVal.getSubKind() == nonloc::ConcreteIntKind) {
+    ExtVal =
+        InputSizeSVal.castAs<nonloc::ConcreteInt>().getValue().getExtValue();
+  } else if (InputSizeSVal.getBaseKind() == SVal::LocKind &&
+             InputSizeSVal.getSubKind() == loc::ConcreteIntKind) {
+    ExtVal = InputSizeSVal.castAs<loc::ConcreteInt>().getValue().getExtValue();
+  }
+
+  if (ExtVal < 0)
+    return false;
+
+  for (int i = 0; i < ExtVal; ++i) {
+    NonLoc AryIndex = svalBuilder.makeArrayIndex(i);
+    const ElementRegion *CurElementRegion = memRegionMgr.getElementRegion(
+        HandleType, AryIndex, SuperRegionSub, ASTCtx);
+    if (!CurElementRegion)
+      return false;
+
+    // Build derived SVal for elements in the handle buffer
+    DefinedOrUnknownSVal CurSVal = svalBuilder.getDerivedRegionValueSymbolVal(
+        ParentSymbol, CurElementRegion);
+    SymbolRef CurSymbol = CurSVal.getAsSymbol();
+    if (!CurSymbol)
+      return false;
+    // A valid handle should be larger than 0. Add this constraint to the handle
+    if (CurSVal.getBaseKind() == SVal::NonLocKind)
+      State->assumeInclusiveRange(CurSVal, llvm::APSInt::get(1),
+                                  llvm::APSInt::get(2147483647), true);
+
+    // Set state for each constructed symbol
+    State = State->set<HStateMap>(CurSymbol, HandleState::getAllocated());
+  }
+  // Process the actual_handle returned
+  SVal ActHandleSVal = State->getSVal(OutputSizeExpr, LCtx);
+  SVal ConcreteInvSVal = svalBuilder.makeIntVal(llvm::APSInt::get(ExtVal));
+  State = State->bindLoc(ActHandleSVal, ConcreteInvSVal, LCtx);
+
+  Ctx.addTransition(State);
+  Ctx.addTransition(FailedState);
+
+  return true;
+}
+
+bool MagentaHandleChecker::processMxChannelCreate(const CallExpr *CE,
+                                                  CheckerContext &Ctx) const {
+  ProgramStateRef State = Ctx.getState();
+  const LocationContext *LCtx = Ctx.getLocationContext();
+  const Expr *Handle1Expr = CE->getArg(1);
+  const Expr *Handle2Expr = CE->getArg(2);
+  if (!(Handle1Expr && Handle2Expr))
+    return false;
+
+  // Conjure a failed state with no handle allocated and status less than 0
+  DefinedOrUnknownSVal FailedSVal = Ctx.getSValBuilder().conjureSymbolVal(
+      nullptr, CE, LCtx, Ctx.blockCount());
+  ProgramStateRef FailedState = State->BindExpr(CE, LCtx, FailedSVal);
+  FailedState = FailedState->assumeInclusiveRange(
+      FailedSVal, llvm::APSInt::get(INT_MIN), llvm::APSInt::get(-1), true);
+
+  SmallVector<SVal, 2> invalidateSVal;
+
+  // Conjure a successful state with status = 0
+  invalidateSVal.push_back(State->getSVal(Handle1Expr, LCtx));
+  invalidateSVal.push_back(State->getSVal(Handle2Expr, LCtx));
+
+  State = State->invalidateRegions(invalidateSVal, CE, Ctx.blockCount(), LCtx,
+                                   false, nullptr, nullptr, nullptr);
+  SVal StatusSVal = Ctx.getSValBuilder().makeIntVal(llvm::APSInt::get(0));
+  State = State->BindExpr(CE, LCtx, StatusSVal);
+
+  const MemRegion *MemHandle1 = State->getSVal(Handle1Expr, LCtx).getAsRegion();
+  const MemRegion *MemHandle2 = State->getSVal(Handle2Expr, LCtx).getAsRegion();
+  if (!MemHandle1 || !MemHandle2)
+    return false;
+
+  SVal SValHandle1 = State->getSVal(MemHandle1);
+  SVal SValHandle2 = State->getSVal(MemHandle2);
+  SymbolRef SymHandle1 = SValHandle1.getAsSymbol();
+  SymbolRef SymHandle2 = SValHandle2.getAsSymbol();
+  if (!SymHandle1 || !SymHandle2)
+    return false;
+  // Valid handle should be larger than 0
+  DefinedOrUnknownSVal DSValHandle1 =
+      SValHandle1.castAs<DefinedOrUnknownSVal>();
+  DefinedOrUnknownSVal DSValHandle2 =
+      SValHandle2.castAs<DefinedOrUnknownSVal>();
+  if (DSValHandle1.getBaseKind() == SVal::NonLocKind &&
+      DSValHandle2.getBaseKind() == SVal::NonLocKind) {
+    State->assumeInclusiveRange(DSValHandle1, llvm::APSInt::get(1),
+                                llvm::APSInt::get(2147483647), true);
+    State->assumeInclusiveRange(DSValHandle2, llvm::APSInt::get(1),
+                                llvm::APSInt::get(2147483647), true);
+  }
+  State = State->set<HStateMap>(SymHandle1, HandleState::getAllocated());
+  State = State->set<HStateMap>(SymHandle2, HandleState::getAllocated());
+  Ctx.addTransition(State);
+  Ctx.addTransition(FailedState);
+  return true;
+}
+
+bool MagentaHandleChecker::processMxChannelWrite(const CallExpr *CE,
+                                                 CheckerContext &Ctx) const {
+  ProgramStateRef State = Ctx.getState();
+  const LocationContext *LCtx = Ctx.getLocationContext();
+  ProgramStateManager &Mgr = State->getStateManager();
+  ASTContext &ASTCtx = Mgr.getContext();
+  SValBuilder &svalBuilder = Ctx.getSValBuilder();
+  MemRegionManager &memRegionMgr = svalBuilder.getRegionManager();
+
+  // If handle related arguments are 0, do not process this call
+  const Expr *HandleBufExpr = CE->getArg(4);
+  const Expr *InputSizeExpr = CE->getArg(5);
+  if (CheckExprIsZero(HandleBufExpr, Ctx) ||
+      CheckExprIsZero(InputSizeExpr, Ctx))
+    return false;
+
+  // Conjure a failed state which does not release any handle
+  DefinedOrUnknownSVal FailedStatusSVal =
+      svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, Ctx.blockCount());
+  ProgramStateRef FailedState = State->BindExpr(CE, LCtx, FailedStatusSVal);
+  FailedState = FailedState->assumeInclusiveRange(
+      FailedStatusSVal, llvm::APSInt::get(INT_MIN), llvm::APSInt::get(-1),
+      true);
+
+  // Conjure a successful state
+  SVal StatusSVal = Ctx.getSValBuilder().makeIntVal(llvm::APSInt::get(0));
+  State = State->BindExpr(CE, LCtx, StatusSVal);
+
+  // Determine the number of handles
+  SVal InputSizeSVal = State->getSVal(InputSizeExpr, LCtx);
+  int64_t ExtVal = -1;
+  if (InputSizeSVal.getBaseKind() == SVal::NonLocKind &&
+      InputSizeSVal.getSubKind() == nonloc::ConcreteIntKind) {
+    ExtVal =
+        InputSizeSVal.castAs<nonloc::ConcreteInt>().getValue().getExtValue();
+  } else if (InputSizeSVal.getBaseKind() == SVal::LocKind &&
+             InputSizeSVal.getSubKind() == loc::ConcreteIntKind) {
+    ExtVal = InputSizeSVal.castAs<loc::ConcreteInt>().getValue().getExtValue();
+  }
+  // Retrive MemRegion of the handle buffer
+  SVal HandleBufSValOrig = State->getSVal(HandleBufExpr, LCtx);
+  const MemRegion *MemHandleBuf = HandleBufSValOrig.getAsRegion();
+
+  if (isa<VarRegion>(MemHandleBuf)) {
+    // Handle buffer is a pointer to a local mx_handle_t variable
+    // Only 1 handle will be transmitted
+    ExtVal = 1;
+    SVal HandleBufSVal = State->getSVal(MemHandleBuf);
+    SymbolRef SymHandleBuf = HandleBufSVal.getAsSymbol();
+    if (!SymHandleBuf)
+      return false;
+
+    State = State->set<HStateMap>(SymHandleBuf, HandleState::getReleased());
+    Ctx.addTransition(FailedState);
+    Ctx.addTransition(State);
+    return true;
+  }
+  // Handle buffer is a pointer to a large region
+  const SubRegion *SuperRegionSub =
+      isa<ElementRegion>(MemHandleBuf)
+          ? dyn_cast<SubRegion>(
+                dyn_cast<ElementRegion>(MemHandleBuf)->getSuperRegion())
+          : NULL;
+  if (!SuperRegionSub)
+    return false;
+
+  SVal HandleBufSVal = State->getSVal(MemHandleBuf);
+  SymbolRef SymHandleBuf = HandleBufSVal.getAsSymbol();
+
+  if (!SymHandleBuf)
+    // The handle buffer is not initialized or the handles are not allocated.
+    // Rare to see in practice.
+    // TODO: Rethink this situation in the future.
+    return false;
+  if (!isa<SymbolDerived>(SymHandleBuf))
+    return false;
+
+  SymbolRef ParentSymbol =
+      dyn_cast<SymbolDerived>(SymHandleBuf)->getParentSymbol();
+  if (!ParentSymbol)
+    return false;
+
+  const TypedValueRegion *TR = cast<TypedValueRegion>(MemHandleBuf);
+  QualType HandleType = TR->getValueType();
+
+  bool TransitionHappened = false;
+  if (ExtVal < 0 || ExtVal > 5) {
+    // We don't know the exact number of handles that should be transferred or
+    // it is too large. Query the map and release all handles that may related.
+    HStateMapTy TrackedHandle = State->get<HStateMap>();
+    for (auto &CurItem : TrackedHandle) {
+      SymbolRef Sym = CurItem.first;
+      HandleState CurHandleState = CurItem.second;
+      const SymbolDerived *ElementSym = dyn_cast<SymbolDerived>(Sym);
+      if (!ElementSym)
+        continue;
+
+      if (ElementSym->getParentSymbol() == ParentSymbol) {
+        if (CurHandleState.isReleased()) {
+          // Double release
+          reportDoubleRelease(Sym, CE->getSourceRange(), Ctx);
+        } else {
+          State = State->set<HStateMap>(Sym, HandleState::getReleased());
+          TransitionHappened = true;
+        }
+      }
+    }
+  } else {
+    // We know the exact number of handles. Release exact number of handles
+    for (int i = 0; i < ExtVal; ++i) {
+      NonLoc AryIndex = svalBuilder.makeArrayIndex(i);
+      const ElementRegion *CurElementRegion = memRegionMgr.getElementRegion(
+          HandleType, AryIndex, SuperRegionSub, ASTCtx);
+      if (!CurElementRegion)
+        return false;
+
+      // Build derived SVal for elements in the handle buffer
+      DefinedOrUnknownSVal CurSVal = svalBuilder.getDerivedRegionValueSymbolVal(
+          ParentSymbol, CurElementRegion);
+      SymbolRef CurSymbol = CurSVal.getAsSymbol();
+      if (!CurSymbol)
+        return false;
+
+      const HandleState *SymState = State->get<HStateMap>(CurSymbol);
+      if (!SymState)
+        continue;
+
+      if (SymState->isReleased()) {
+        reportDoubleRelease(CurSymbol, CE->getSourceRange(), Ctx);
+      } else {
+        State = State->set<HStateMap>(CurSymbol, HandleState::getReleased());
+        TransitionHappened = true;
+      }
+    }
+  }
+  if (TransitionHappened)
+    Ctx.addTransition(State);
+
+  Ctx.addTransition(FailedState);
+  return true;
+}
+
+void MagentaHandleChecker::processMxHandleClose(ProgramStateRef State,
+                                                const CallEvent &Call,
+                                                CheckerContext &Ctx) const {
+  // Retrive symbolic value for passed in handle
+  SymbolRef SymHandle = Call.getArgSVal(0).getAsSymbol();
+  if (!SymHandle)
+    return;
+
+  const HandleState *SymState = State->get<HStateMap>(SymHandle);
+  if (!SymState)
+    return;
+
+  if (SymState->isReleased()) {
+    // Double release
+    reportDoubleRelease(SymHandle, Call.getSourceRange(), Ctx);
+    return;
+  }
+  State = State->set<HStateMap>(SymHandle, HandleState::getReleased());
+  Ctx.addTransition(State);
+}
+
+void MagentaHandleChecker::reportLeaks(ArrayRef<SymbolRef> LeakedHandles,
+                                       CheckerContext &C,
+                                       ExplodedNode *ErrorNode) const {
+  for (SymbolRef LeakedHandle : LeakedHandles) {
+    auto R = llvm::make_unique<BugReport>(
+        *LeakBugType,
+        "Allocated handle is never released; potential resource leak",
+        ErrorNode);
+    R->markInteresting(LeakedHandle);
+    R->disablePathPruning();
+    C.emitReport(std::move(R));
+  }
+}
+
+void MagentaHandleChecker::reportDoubleRelease(SymbolRef HandleSym,
+                                               const SourceRange &Range,
+                                               CheckerContext &C) const {
+  ExplodedNode *ErrNode = C.generateErrorNode();
+  if (!ErrNode)
+    return;
+
+  auto R = llvm::make_unique<BugReport>(
+      *DoubleReleaseBugType, "Releasing a previously released handle", ErrNode);
+  R->addRange(Range);
+  R->markInteresting(HandleSym);
+  C.emitReport(std::move(R));
+}
+
+void MagentaHandleChecker::reportUseAfterFree(SymbolRef HandleSym,
+                                              const SourceRange &Range,
+                                              CheckerContext &Ctx) const {
+  ExplodedNode *ErrNode = Ctx.generateErrorNode();
+  if (!ErrNode)
+    return;
+
+  auto R = llvm::make_unique<BugReport>(
+      *UseAfterFreeBugType, "Using a previously released handle", ErrNode);
+  R->addRange(Range);
+  R->markInteresting(HandleSym);
+  Ctx.emitReport(std::move(R));
+}
+
+void ento::registerMagentaHandleChecker(CheckerManager &mgr) {
+  mgr.registerChecker<MagentaHandleChecker>();
+}
Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -45,6 +45,7 @@
   LocalizationChecker.cpp
   MacOSKeychainAPIChecker.cpp
   MacOSXAPIChecker.cpp
+  MagentaHandleChecker.cpp
   MallocChecker.cpp
   MallocOverflowSecurityChecker.cpp
   MallocSizeofChecker.cpp
Index: include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -69,6 +69,7 @@
 def OSX : Package<"osx">;
 def OSXAlpha : Package<"osx">, InPackage<Alpha>, Hidden;
 def OSXOptIn : Package<"osx">, InPackage<OptIn>;
+def Magenta : Package<"magenta">, InPackage<Alpha>;
 
 def Cocoa : Package<"cocoa">, InPackage<OSX>;
 def CocoaAlpha : Package<"cocoa">, InPackage<OSXAlpha>, Hidden;
@@ -770,3 +771,15 @@
   DescFile<"UnixAPIChecker.cpp">;
 
 } // end optin.portability
+
+//===----------------------------------------------------------------------===//
+// Magenta Specified Checkers
+//===----------------------------------------------------------------------===//
+
+let ParentPackage = Magenta in {
+
+def MagentaHandleChecker : Checker<"MagentaHandleChecker">,
+  HelpText<"A Checker that detect leaks related to Magenta handles">,
+  DescFile<"MagentaHandleChecker.cpp">;
+
+} // end "magenta"
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to