//== NullDerefChecker.cpp - Null dereference checker ------------*- C++ -*--==//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This defines NullDerefChecker, a builtin check in GRExprEngine that performs
// checks for null pointers at loads and stores.
//
//===----------------------------------------------------------------------===//

#include "clang/Analysis/PathSensitive/Checkers/DereferenceChecker.h"
#include "clang/Analysis/PathSensitive/Checker.h"
#include "clang/Analysis/PathSensitive/GRExprEngine.h"
#include "clang/Analysis/PathSensitive/BugReporter.h"
#include "GRExprEngineInternalChecks.h"

using namespace clang;

namespace {
class VISIBILITY_HIDDEN DereferenceChecker : public Checker {
  BuiltinBug *BT_null;
  BuiltinBug *BT_undef;
  llvm::SmallVector<ExplodedNode*, 2> ImplicitNullDerefNodes;
public:
  DereferenceChecker() : BT_null(0), BT_undef(0) {}
  static void *getTag() { static int tag = 0; return &tag; }
  void VisitLocation(CheckerContext &C, const Stmt *S, SVal location);
  
  std::pair<ExplodedNode * const*, ExplodedNode * const*>
  getImplicitNodes() const {    
    return std::make_pair(ImplicitNullDerefNodes.data(),
                          ImplicitNullDerefNodes.data() +
                          ImplicitNullDerefNodes.size());
  }
};
} // end anonymous namespace

void clang::RegisterDereferenceChecker(GRExprEngine &Eng) {
  Eng.registerCheck(new DereferenceChecker());
}

std::pair<ExplodedNode * const *, ExplodedNode * const *>
clang::GetImplicitNullDereferences(GRExprEngine &Eng) {
  DereferenceChecker *checker = Eng.getChecker<DereferenceChecker>();
  if (!checker)
    return std::make_pair((ExplodedNode * const *) 0,
                          (ExplodedNode * const *) 0);
  return checker->getImplicitNodes();
}

void DereferenceChecker::VisitLocation(CheckerContext &C, const Stmt *S,
                                       SVal l) {
  // Check for dereference of an undefined value.
  if (l.isUndef()) {
    if (ExplodedNode *N = C.GenerateSink()) {
      if (!BT_undef)
        BT_undef = new BuiltinBug("Dereference of undefined pointer value");
      
      EnhancedBugReport *report =
        new EnhancedBugReport(*BT_undef, BT_undef->getDescription(), N);
      report->addVisitorCreator(bugreporter::registerTrackNullOrUndefValue,
                                bugreporter::GetDerefExpr(N));
      C.EmitReport(report);
    }
    return;
  }
  
  DefinedOrUnknownSVal location = cast<DefinedOrUnknownSVal>(l);
  
  // Check for null dereferences.  
  if (!isa<Loc>(location))
    return;
  
  const GRState *state = C.getState();
  const GRState *notNullState, *nullState;
  llvm::tie(notNullState, nullState) = state->Assume(location);
  
  // The explicit NULL case.
  if (nullState) {
    if (!notNullState) {    
      // Generate an error node.
      ExplodedNode *N = C.GenerateSink(nullState);
      if (!N)
        return;
      
      // We know that 'location' cannot be non-null.  This is what
      // we call an "explicit" null dereference.        
      if (!BT_null)
        BT_null = new BuiltinBug("Dereference of null pointer");
      
      llvm::SmallString<100> buf;

      switch (S->getStmtClass()) {
        case Stmt::UnaryOperatorClass: {
          const UnaryOperator *U = cast<UnaryOperator>(S);
          const Expr *SU = U->getSubExpr()->IgnoreParens();
          if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(SU)) {
            if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) {
              llvm::raw_svector_ostream os(buf);
              os << "Dereference of null pointer loaded from variable '"
                 << VD->getName() << '\'';
            }
          }
        }
        default:
          break;
      }

      EnhancedBugReport *report =
        new EnhancedBugReport(*BT_null,
                              buf.empty() ? BT_null->getDescription():buf.str(),
                              N);

      report->addVisitorCreator(bugreporter::registerTrackNullOrUndefValue,
                                bugreporter::GetDerefExpr(N));
      
      C.EmitReport(report);
      return;
    }
    else {
      // Otherwise, we have the case where the location could either be
      // null or not-null.  Record the error node as an "implicit" null
      // dereference.      
      if (ExplodedNode *N = C.GenerateSink(nullState))
        ImplicitNullDerefNodes.push_back(N);
    }
  }
  
  // From this point forward, we know that the location is not null.
  C.addTransition(notNullState);
}
