Skip to content

Instantly share code, notes, and snippets.

@zeroomega
Created May 31, 2017 00:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeroomega/2fcabf5979a0e42db1ccb8f30472d071 to your computer and use it in GitHub Desktop.
Save zeroomega/2fcabf5979a0e42db1ccb8f30472d071 to your computer and use it in GitHub Desktop.
//== TestHandleChecker.cpp -- test checker ----------------------*- C++ -*--==//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Just a simple handle leak checker
//
//
//===----------------------------------------------------------------------===//
#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/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.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 <utility>
#include <string>
#include <stdio.h>
using namespace clang;
using namespace ento;
namespace {
enum CallKind {
HANDLE_REQUEST,
HANDLE_RELEASE,
UNRELATED_CALL
};
struct HandleState {
private:
enum Kind {
// Handle allocated
Allocated,
// Handle released
Released
} 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; }
static HandleState getAllocated() {
return HandleState(Allocated);
}
static HandleState getReleased() {
return HandleState(Released);
}
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)
}
}
LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); }
};
class TestHandleChecker : public Checker< check::DeadSymbols,
check::PreCall,
check::PostCall > {
std::unique_ptr<BugType> LeakBugType;
std::unique_ptr<BugType> DoubleReleaseBugType;
std::unique_ptr<BugType> UseAfterFreeBugType;
public:
TestHandleChecker();
// Checker callbacks
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
// Bug report functions
void reportLeaks(ArrayRef<SymbolRef> LeakedHandles,
CheckerContext &C, ExplodedNode *ErrorNode) const;
void reportDoubleRelease(SymbolRef HandlerSym,
const CallEvent &Call, CheckerContext &C) const;
// Utility functions
CallKind CheckCallSignature(const CallEvent &Call) const;
// bool CheckSymbolDeathWithParents(SymbolReaper &SymReaper,
// SymbolRef sym, CheckerContext &C) const;
static void dumpSymbol(SymbolRef SymRef) {
if (SymRef) {
char buf[256];
sprintf(buf, "%p", SymRef);
SymRef->dumpToStream(llvm::errs());
llvm::errs()<<" Addr:"<<buf<<'\n';
}
else {
llvm::errs()<<"SymbolRef is null\n";
}
}
static bool isLeaked(SymbolRef SymHandler,
const HandleState &handleState,
bool isSymDead,
ProgramStateRef State) {
if (isSymDead && handleState.isAllocated()) {
// Handle's symbol is dead and is not in released state
return true;
}
return false;
}
};
} // end anonymous namespace
REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState)
TestHandleChecker::TestHandleChecker() {
// Initialize the bug types.
LeakBugType.reset(
new BugType(this, "Handle Leak", "Handle Error"));
DoubleReleaseBugType.reset(
new BugType(this, "Handle Double Release", "Handle Error"));
UseAfterFreeBugType.reset(
new BugType(this, "Handle Use After Free", "Handle Error"));
}
CallKind TestHandleChecker::CheckCallSignature(const CallEvent &Call)
const {
if (const AnyFunctionCall *func_call = dyn_cast<AnyFunctionCall>(&Call)) {
const FunctionDecl *func_decl = func_call->getDecl();
if (!func_decl) {
return UNRELATED_CALL;
}
DeclarationName DeclName = func_decl->getDeclName();
if (!DeclName.isIdentifier()) {
return UNRELATED_CALL;
}
// Currently just use function name to identify target calls to simplify
// the test. In the future, strictly check the whole signature of the
// function
StringRef func_name = func_decl->getName();
if (func_name.equals("request")) {
return HANDLE_REQUEST;
}
if (func_name.equals("release")) {
return HANDLE_RELEASE;
}
}
return UNRELATED_CALL;
}
void TestHandleChecker::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
CallKind call_kind = CheckCallSignature(Call);
if (call_kind == HANDLE_REQUEST) {
// Retrieve symbolic value for allocated handler
const MemRegion* MemHandle1 = Call.getArgSVal(1).getAsRegion();
const MemRegion* MemHandle2 = Call.getArgSVal(2).getAsRegion();
if (!MemHandle1 || !MemHandle2) {
return;
}
SVal SValHandle1 = State->getSVal(MemHandle1);
SVal SValHandle2 = State->getSVal(MemHandle2);
SymbolRef SymHandle1 = SValHandle1.getAsSymbol();
SymbolRef SymHandle2 = SValHandle2.getAsSymbol();
if (!SymHandle1 || !SymHandle2) {
// There is at least one symbol that is null
return;
}
llvm::errs()<<"Requested Handles Dump: \n";
dumpSymbol(SymHandle1);
dumpSymbol(SymHandle2);
llvm::errs()<<"Dump End\n\n";
// Track symbol for handler
State = State->set<HStateMap>(SymHandle1, HandleState::getAllocated());
State = State->set<HStateMap>(SymHandle2, HandleState::getAllocated());
C.addTransition(State);
}
}
void TestHandleChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
CallKind call_kind = CheckCallSignature(Call);
if (call_kind == HANDLE_RELEASE) {
// Retrive symbolic value for handle
SymbolRef SymHandle = Call.getArgSVal(0).getAsSymbol();
if (!SymHandle) {
return;
}
llvm::errs()<<"Released Handle Dump: \n";
//Call.getArgSVal(0).dumpToStream(llvm::errs());
llvm::errs()<<" ";
dumpSymbol(SymHandle);
llvm::errs()<<"Dump End\n\n";
const HandleState * SymState = State->get<HStateMap>(SymHandle);
if (!SymState) {
return;
}
if (SymState->isReleased()) {
// Double release
llvm::errs()<<"Double release detected!\n";
reportDoubleRelease(SymHandle, Call, C);
return;
}
State = State->set<HStateMap>(SymHandle, HandleState::getReleased());
C.addTransition(State);
}
}
void TestHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
if (SymReaper.hasDeadSymbols()) {
llvm::errs()<<"Dead Symbols detected\n";
llvm::errs()<<"Dead Symbols dump\n";
for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(),
E = SymReaper.dead_end(); I != E; ++I) {
SymbolRef Sym = * I;
dumpSymbol(Sym);
}
llvm::errs()<<"Dump End\n\n";
} else {
llvm::errs()<<"Dead Symbol not available\n\n";
}
SmallVector<SymbolRef, 2> LeakVector;
HStateMapTy tracked_stream = State->get<HStateMap>();
for (HStateMapTy::iterator I = tracked_stream.begin(),
E = tracked_stream.end(); I != E; ++I) {
SymbolRef SymHandle = I->first;
llvm::errs()<<"Test on SymbolRef: ";
dumpSymbol(SymHandle);
bool IsSymDead = SymReaper.isDead(SymHandle);
if (IsSymDead) {
llvm::errs()<<"It is dead\n";
} else {
llvm::errs()<<"It is not dead\n";
}
HandleState CurrentHandlerState = I->second;
if (CurrentHandlerState.isAllocated()) {
llvm::errs()<<"It is requested\n";
}
if (CurrentHandlerState.isReleased()) {
llvm::errs()<<"It is released\n";
}
if (isLeaked(SymHandle, CurrentHandlerState, IsSymDead, State)) {
llvm::errs()<<"Handler is leaking\n";
LeakVector.push_back(SymHandle);
}
if (IsSymDead) {
State = State->remove<HStateMap>(SymHandle);
}
llvm::errs()<<"\n";
}
if (!LeakVector.empty()) {
ExplodedNode *N = C.generateNonFatalErrorNode(State);
if (!N) {
return;
}
reportLeaks(LeakVector, C, N);
}
}
void TestHandleChecker::reportLeaks(ArrayRef<SymbolRef> LeakedHandles,
CheckerContext &C,
ExplodedNode *ErrorNode) const {
for (SymbolRef LeakedHandle : LeakedHandles) {
auto R = llvm::make_unique<BugReport>(*LeakBugType,
"Allocated handler is never released; potential resource leak",
ErrorNode);
llvm::errs()<<"Leaking handle: ";
dumpSymbol(LeakedHandle);
llvm::errs()<<"\n";
R->markInteresting(LeakedHandle);
C.emitReport(std::move(R));
}
}
void TestHandleChecker::reportDoubleRelease(SymbolRef HandlerSym,
const CallEvent &Call,
CheckerContext &C) const {
ExplodedNode *ErrNode = C.generateErrorNode();
if (!ErrNode) {
return;
}
auto R = llvm::make_unique<BugReport>(*DoubleReleaseBugType,
"Releasing a previously released handler", ErrNode);
R->addRange(Call.getSourceRange());
R->markInteresting(HandlerSym);
C.emitReport(std::move(R));
}
void ento::registerTestHandleChecker(CheckerManager &mgr) {
mgr.registerChecker<TestHandleChecker>();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment