#include "ErrnoModeling.h"
#include "clang/AST/ParentMapContext.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
#include "llvm/ADT/STLExtras.h"
using namespace clang;
using namespace ento;
using namespace errno_modeling;
namespace {
class ErrnoChecker
: public Checker<check::Location, check::PreCall, check::RegionChanges> {
public:
void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
CheckerContext &) const;
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
ProgramStateRef
checkRegionChanges(ProgramStateRef State,
const InvalidatedSymbols *Invalidated,
ArrayRef<const MemRegion *> ExplicitRegions,
ArrayRef<const MemRegion *> Regions,
const LocationContext *LCtx, const CallEvent *Call) const;
void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const;
bool AllowErrnoReadOutsideConditions = true;
private:
void generateErrnoNotCheckedBug(CheckerContext &C, ProgramStateRef State,
const MemRegion *ErrnoRegion,
const CallEvent *CallMayChangeErrno) const;
BugType BT_InvalidErrnoRead{this, "Value of 'errno' could be undefined",
"Error handling"};
BugType BT_ErrnoNotChecked{this, "Value of 'errno' was not checked",
"Error handling"};
};
}
static ProgramStateRef setErrnoStateIrrelevant(ProgramStateRef State) {
return setErrnoState(State, Irrelevant);
}
static bool isInCondition(const Stmt *S, CheckerContext &C) {
ParentMapContext &ParentCtx = C.getASTContext().getParentMapContext();
bool CondFound = false;
while (S && !CondFound) {
const DynTypedNodeList Parents = ParentCtx.getParents(*S);
if (Parents.empty())
break;
const auto *ParentS = Parents[0].get<Stmt>();
if (!ParentS || isa<CallExpr>(ParentS))
break;
switch (ParentS->getStmtClass()) {
case Expr::IfStmtClass:
CondFound = (S == cast<IfStmt>(ParentS)->getCond());
break;
case Expr::ForStmtClass:
CondFound = (S == cast<ForStmt>(ParentS)->getCond());
break;
case Expr::DoStmtClass:
CondFound = (S == cast<DoStmt>(ParentS)->getCond());
break;
case Expr::WhileStmtClass:
CondFound = (S == cast<WhileStmt>(ParentS)->getCond());
break;
case Expr::SwitchStmtClass:
CondFound = (S == cast<SwitchStmt>(ParentS)->getCond());
break;
case Expr::ConditionalOperatorClass:
CondFound = (S == cast<ConditionalOperator>(ParentS)->getCond());
break;
case Expr::BinaryConditionalOperatorClass:
CondFound = (S == cast<BinaryConditionalOperator>(ParentS)->getCommon());
break;
default:
break;
}
S = ParentS;
}
return CondFound;
}
void ErrnoChecker::generateErrnoNotCheckedBug(
CheckerContext &C, ProgramStateRef State, const MemRegion *ErrnoRegion,
const CallEvent *CallMayChangeErrno) const {
if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
SmallString<100> StrBuf;
llvm::raw_svector_ostream OS(StrBuf);
if (CallMayChangeErrno) {
OS << "Value of 'errno' was not checked and may be overwritten by "
"function '";
const auto *CallD =
dyn_cast_or_null<FunctionDecl>(CallMayChangeErrno->getDecl());
assert(CallD && CallD->getIdentifier());
OS << CallD->getIdentifier()->getName() << "'";
} else {
OS << "Value of 'errno' was not checked and is overwritten here";
}
auto BR = std::make_unique<PathSensitiveBugReport>(BT_ErrnoNotChecked,
OS.str(), N);
BR->markInteresting(ErrnoRegion);
C.emitReport(std::move(BR));
}
}
void ErrnoChecker::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
CheckerContext &C) const {
Optional<ento::Loc> ErrnoLoc = getErrnoLoc(C.getState());
if (!ErrnoLoc)
return;
auto L = Loc.getAs<ento::Loc>();
if (!L || *ErrnoLoc != *L)
return;
ProgramStateRef State = C.getState();
ErrnoCheckState EState = getErrnoState(State);
if (IsLoad) {
switch (EState) {
case MustNotBeChecked:
if (!AllowErrnoReadOutsideConditions || isInCondition(S, C)) {
if (ExplodedNode *N = C.generateErrorNode()) {
auto BR = std::make_unique<PathSensitiveBugReport>(
BT_InvalidErrnoRead,
"An undefined value may be read from 'errno'", N);
BR->markInteresting(ErrnoLoc->getAsRegion());
C.emitReport(std::move(BR));
}
}
break;
case MustBeChecked:
State = setErrnoStateIrrelevant(State);
C.addTransition(State);
break;
default:
break;
}
} else {
switch (EState) {
case MustBeChecked:
generateErrnoNotCheckedBug(C, setErrnoStateIrrelevant(State),
ErrnoLoc->getAsRegion(), nullptr);
break;
case MustNotBeChecked:
State = setErrnoStateIrrelevant(State);
C.addTransition(State);
break;
default:
break;
}
}
}
void ErrnoChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
const auto *CallF = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
if (!CallF)
return;
CallF = CallF->getCanonicalDecl();
if (CallF->isExternC() && CallF->isGlobal() &&
C.getSourceManager().isInSystemHeader(CallF->getLocation()) &&
!isErrno(CallF)) {
if (getErrnoState(C.getState()) == MustBeChecked) {
Optional<ento::Loc> ErrnoLoc = getErrnoLoc(C.getState());
assert(ErrnoLoc && "ErrnoLoc should exist if an errno state is set.");
generateErrnoNotCheckedBug(C, setErrnoStateIrrelevant(C.getState()),
ErrnoLoc->getAsRegion(), &Call);
}
}
}
ProgramStateRef ErrnoChecker::checkRegionChanges(
ProgramStateRef State, const InvalidatedSymbols *Invalidated,
ArrayRef<const MemRegion *> ExplicitRegions,
ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
const CallEvent *Call) const {
Optional<ento::Loc> ErrnoLoc = getErrnoLoc(State);
if (!ErrnoLoc)
return State;
const MemRegion *ErrnoRegion = ErrnoLoc->getAsRegion();
if (llvm::is_contained(Regions, ErrnoRegion))
return setErrnoStateIrrelevant(State);
if (llvm::is_contained(Regions, ErrnoRegion->getMemorySpace()))
return setErrnoStateIrrelevant(State);
return State;
}
void ento::registerErrnoChecker(CheckerManager &mgr) {
const AnalyzerOptions &Opts = mgr.getAnalyzerOptions();
auto *Checker = mgr.registerChecker<ErrnoChecker>();
Checker->AllowErrnoReadOutsideConditions = Opts.getCheckerBooleanOption(
Checker, "AllowErrnoReadOutsideConditionExpressions");
}
bool ento::shouldRegisterErrnoChecker(const CheckerManager &mgr) {
return true;
}