#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Expr.h"
#include "clang/Basic/LangOptions.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
using namespace ento;
namespace {
class GTestChecker : public Checker<check::PostCall> {
mutable IdentifierInfo *AssertionResultII;
mutable IdentifierInfo *SuccessII;
public:
GTestChecker();
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
private:
void modelAssertionResultBoolConstructor(const CXXConstructorCall *Call,
bool IsRef, CheckerContext &C) const;
void modelAssertionResultCopyConstructor(const CXXConstructorCall *Call,
CheckerContext &C) const;
void initIdentifierInfo(ASTContext &Ctx) const;
SVal
getAssertionResultSuccessFieldValue(const CXXRecordDecl *AssertionResultDecl,
SVal Instance,
ProgramStateRef State) const;
static ProgramStateRef assumeValuesEqual(SVal Val1, SVal Val2,
ProgramStateRef State,
CheckerContext &C);
};
}
GTestChecker::GTestChecker() : AssertionResultII(nullptr), SuccessII(nullptr) {}
void GTestChecker::modelAssertionResultBoolConstructor(
const CXXConstructorCall *Call, bool IsRef, CheckerContext &C) const {
assert(Call->getNumArgs() >= 1 && Call->getNumArgs() <= 2);
ProgramStateRef State = C.getState();
SVal BooleanArgVal = Call->getArgSVal(0);
if (IsRef) {
if (!isa<Loc>(BooleanArgVal))
return;
BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>());
}
SVal ThisVal = Call->getCXXThisVal();
SVal ThisSuccess = getAssertionResultSuccessFieldValue(
Call->getDecl()->getParent(), ThisVal, State);
State = assumeValuesEqual(ThisSuccess, BooleanArgVal, State, C);
C.addTransition(State);
}
void GTestChecker::modelAssertionResultCopyConstructor(
const CXXConstructorCall *Call, CheckerContext &C) const {
assert(Call->getNumArgs() == 1);
SVal OtherVal = Call->getArgSVal(0);
SVal ThisVal = Call->getCXXThisVal();
const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent();
ProgramStateRef State = C.getState();
SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
ThisVal, State);
SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
OtherVal, State);
State = assumeValuesEqual(ThisSuccess, OtherSuccess, State, C);
C.addTransition(State);
}
void GTestChecker::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
if (C.wasInlined)
return;
initIdentifierInfo(C.getASTContext());
auto *CtorCall = dyn_cast<CXXConstructorCall>(&Call);
if (!CtorCall)
return;
const CXXConstructorDecl *CtorDecl = CtorCall->getDecl();
const CXXRecordDecl *CtorParent = CtorDecl->getParent();
if (CtorParent->getIdentifier() != AssertionResultII)
return;
unsigned ParamCount = CtorDecl->getNumParams();
if (CtorDecl->isCopyConstructor() && ParamCount == 1) {
modelAssertionResultCopyConstructor(CtorCall, C);
return;
}
CanQualType BoolTy = C.getASTContext().BoolTy;
if (ParamCount == 1 && CtorDecl->getParamDecl(0)->getType() == BoolTy) {
modelAssertionResultBoolConstructor(CtorCall, false, C);
return;
}
if (ParamCount == 2){
auto *RefTy = CtorDecl->getParamDecl(0)->getType()->getAs<ReferenceType>();
if (RefTy &&
RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) {
modelAssertionResultBoolConstructor(CtorCall, true, C);
return;
}
}
}
void GTestChecker::initIdentifierInfo(ASTContext &Ctx) const {
if (AssertionResultII)
return;
AssertionResultII = &Ctx.Idents.get("AssertionResult");
SuccessII = &Ctx.Idents.get("success_");
}
SVal GTestChecker::getAssertionResultSuccessFieldValue(
const CXXRecordDecl *AssertionResultDecl, SVal Instance,
ProgramStateRef State) const {
DeclContext::lookup_result Result = AssertionResultDecl->lookup(SuccessII);
if (Result.empty())
return UnknownVal();
auto *SuccessField = dyn_cast<FieldDecl>(Result.front());
if (!SuccessField)
return UnknownVal();
Optional<Loc> FieldLoc =
State->getLValue(SuccessField, Instance).getAs<Loc>();
if (!FieldLoc)
return UnknownVal();
return State->getSVal(*FieldLoc);
}
ProgramStateRef GTestChecker::assumeValuesEqual(SVal Val1, SVal Val2,
ProgramStateRef State,
CheckerContext &C) {
auto DVal1 = Val1.getAs<DefinedOrUnknownSVal>();
auto DVal2 = Val2.getAs<DefinedOrUnknownSVal>();
if (!DVal1 || !DVal2)
return State;
auto ValuesEqual =
C.getSValBuilder().evalEQ(State, *DVal1, *DVal2).getAs<DefinedSVal>();
if (!ValuesEqual)
return State;
State = C.getConstraintManager().assume(State, *ValuesEqual, true);
return State;
}
void ento::registerGTestChecker(CheckerManager &Mgr) {
Mgr.registerChecker<GTestChecker>();
}
bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) {
const LangOptions &LO = mgr.getLangOpts();
return LO.CPlusPlus;
}