diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2019-12-20 19:53:05 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2019-12-20 19:53:05 +0000 |
commit | 0b57cec536236d46e3dba9bd041533462f33dbb7 (patch) | |
tree | 56229dbdbbf76d18580f72f789003db17246c8d9 /contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp | |
parent | 718ef55ec7785aae63f98f8ca05dc07ed399c16d (diff) |
Notes
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp')
-rw-r--r-- | contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp new file mode 100644 index 000000000000..2c264833f2a9 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -0,0 +1,309 @@ +//===-- DereferenceChecker.cpp - Null dereference checker -----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This defines NullDerefChecker, a builtin check in ExprEngine that performs +// checks for null pointers at loads and stores. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/ExprObjC.h" +#include "clang/AST/ExprOpenMP.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/CheckerHelpers.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace ento; + +namespace { +class DereferenceChecker + : public Checker< check::Location, + check::Bind, + EventDispatcher<ImplicitNullDerefEvent> > { + mutable std::unique_ptr<BuiltinBug> BT_null; + mutable std::unique_ptr<BuiltinBug> BT_undef; + + void reportBug(ProgramStateRef State, const Stmt *S, CheckerContext &C) const; + +public: + void checkLocation(SVal location, bool isLoad, const Stmt* S, + CheckerContext &C) const; + void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; + + static void AddDerefSource(raw_ostream &os, + SmallVectorImpl<SourceRange> &Ranges, + const Expr *Ex, const ProgramState *state, + const LocationContext *LCtx, + bool loadedFrom = false); +}; +} // end anonymous namespace + +void +DereferenceChecker::AddDerefSource(raw_ostream &os, + SmallVectorImpl<SourceRange> &Ranges, + const Expr *Ex, + const ProgramState *state, + const LocationContext *LCtx, + bool loadedFrom) { + Ex = Ex->IgnoreParenLValueCasts(); + switch (Ex->getStmtClass()) { + default: + break; + case Stmt::DeclRefExprClass: { + const DeclRefExpr *DR = cast<DeclRefExpr>(Ex); + if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) { + os << " (" << (loadedFrom ? "loaded from" : "from") + << " variable '" << VD->getName() << "')"; + Ranges.push_back(DR->getSourceRange()); + } + break; + } + case Stmt::MemberExprClass: { + const MemberExpr *ME = cast<MemberExpr>(Ex); + os << " (" << (loadedFrom ? "loaded from" : "via") + << " field '" << ME->getMemberNameInfo() << "')"; + SourceLocation L = ME->getMemberLoc(); + Ranges.push_back(SourceRange(L, L)); + break; + } + case Stmt::ObjCIvarRefExprClass: { + const ObjCIvarRefExpr *IV = cast<ObjCIvarRefExpr>(Ex); + os << " (" << (loadedFrom ? "loaded from" : "via") + << " ivar '" << IV->getDecl()->getName() << "')"; + SourceLocation L = IV->getLocation(); + Ranges.push_back(SourceRange(L, L)); + break; + } + } +} + +static const Expr *getDereferenceExpr(const Stmt *S, bool IsBind=false){ + const Expr *E = nullptr; + + // Walk through lvalue casts to get the original expression + // that syntactically caused the load. + if (const Expr *expr = dyn_cast<Expr>(S)) + E = expr->IgnoreParenLValueCasts(); + + if (IsBind) { + const VarDecl *VD; + const Expr *Init; + std::tie(VD, Init) = parseAssignment(S); + if (VD && Init) + E = Init; + } + return E; +} + +static bool suppressReport(const Expr *E) { + // Do not report dereferences on memory in non-default address spaces. + return E->getType().getQualifiers().hasAddressSpace(); +} + +static bool isDeclRefExprToReference(const Expr *E) { + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) + return DRE->getDecl()->getType()->isReferenceType(); + return false; +} + +void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, + CheckerContext &C) const { + // Generate an error node. + ExplodedNode *N = C.generateErrorNode(State); + 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.reset(new BuiltinBug(this, "Dereference of null pointer")); + + SmallString<100> buf; + llvm::raw_svector_ostream os(buf); + + SmallVector<SourceRange, 2> Ranges; + + switch (S->getStmtClass()) { + case Stmt::ArraySubscriptExprClass: { + os << "Array access"; + const ArraySubscriptExpr *AE = cast<ArraySubscriptExpr>(S); + AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts(), + State.get(), N->getLocationContext()); + os << " results in a null pointer dereference"; + break; + } + case Stmt::OMPArraySectionExprClass: { + os << "Array access"; + const OMPArraySectionExpr *AE = cast<OMPArraySectionExpr>(S); + AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts(), + State.get(), N->getLocationContext()); + os << " results in a null pointer dereference"; + break; + } + case Stmt::UnaryOperatorClass: { + os << "Dereference of null pointer"; + const UnaryOperator *U = cast<UnaryOperator>(S); + AddDerefSource(os, Ranges, U->getSubExpr()->IgnoreParens(), + State.get(), N->getLocationContext(), true); + break; + } + case Stmt::MemberExprClass: { + const MemberExpr *M = cast<MemberExpr>(S); + if (M->isArrow() || isDeclRefExprToReference(M->getBase())) { + os << "Access to field '" << M->getMemberNameInfo() + << "' results in a dereference of a null pointer"; + AddDerefSource(os, Ranges, M->getBase()->IgnoreParenCasts(), + State.get(), N->getLocationContext(), true); + } + break; + } + case Stmt::ObjCIvarRefExprClass: { + const ObjCIvarRefExpr *IV = cast<ObjCIvarRefExpr>(S); + os << "Access to instance variable '" << *IV->getDecl() + << "' results in a dereference of a null pointer"; + AddDerefSource(os, Ranges, IV->getBase()->IgnoreParenCasts(), + State.get(), N->getLocationContext(), true); + break; + } + default: + break; + } + + auto report = llvm::make_unique<BugReport>( + *BT_null, buf.empty() ? BT_null->getDescription() : StringRef(buf), N); + + bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); + + for (SmallVectorImpl<SourceRange>::iterator + I = Ranges.begin(), E = Ranges.end(); I!=E; ++I) + report->addRange(*I); + + C.emitReport(std::move(report)); +} + +void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, + CheckerContext &C) const { + // Check for dereference of an undefined value. + if (l.isUndef()) { + if (ExplodedNode *N = C.generateErrorNode()) { + if (!BT_undef) + BT_undef.reset( + new BuiltinBug(this, "Dereference of undefined pointer value")); + + auto report = + llvm::make_unique<BugReport>(*BT_undef, BT_undef->getDescription(), N); + bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); + C.emitReport(std::move(report)); + } + return; + } + + DefinedOrUnknownSVal location = l.castAs<DefinedOrUnknownSVal>(); + + // Check for null dereferences. + if (!location.getAs<Loc>()) + return; + + ProgramStateRef state = C.getState(); + + ProgramStateRef notNullState, nullState; + std::tie(notNullState, nullState) = state->assume(location); + + // The explicit NULL case. + if (nullState) { + if (!notNullState) { + const Expr *expr = getDereferenceExpr(S); + if (!suppressReport(expr)) { + reportBug(nullState, expr, C); + return; + } + } + + // 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, C.getPredecessor())) { + ImplicitNullDerefEvent event = {l, isLoad, N, &C.getBugReporter(), + /*IsDirectDereference=*/true}; + dispatchEvent(event); + } + } + + // From this point forward, we know that the location is not null. + C.addTransition(notNullState); +} + +void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S, + CheckerContext &C) const { + // If we're binding to a reference, check if the value is known to be null. + if (V.isUndef()) + return; + + const MemRegion *MR = L.getAsRegion(); + const TypedValueRegion *TVR = dyn_cast_or_null<TypedValueRegion>(MR); + if (!TVR) + return; + + if (!TVR->getValueType()->isReferenceType()) + return; + + ProgramStateRef State = C.getState(); + + ProgramStateRef StNonNull, StNull; + std::tie(StNonNull, StNull) = State->assume(V.castAs<DefinedOrUnknownSVal>()); + + if (StNull) { + if (!StNonNull) { + const Expr *expr = getDereferenceExpr(S, /*IsBind=*/true); + if (!suppressReport(expr)) { + reportBug(StNull, expr, C); + return; + } + } + + // At this point the value could be either null or non-null. + // Record this as an "implicit" null dereference. + if (ExplodedNode *N = C.generateSink(StNull, C.getPredecessor())) { + ImplicitNullDerefEvent event = {V, /*isLoad=*/true, N, + &C.getBugReporter(), + /*IsDirectDereference=*/true}; + dispatchEvent(event); + } + } + + // Unlike a regular null dereference, initializing a reference with a + // dereferenced null pointer does not actually cause a runtime exception in + // Clang's implementation of references. + // + // int &r = *p; // safe?? + // if (p != NULL) return; // uh-oh + // r = 5; // trap here + // + // The standard says this is invalid as soon as we try to create a "null + // reference" (there is no such thing), but turning this into an assumption + // that 'p' is never null will not match our actual runtime behavior. + // So we do not record this assumption, allowing us to warn on the last line + // of this example. + // + // We do need to add a transition because we may have generated a sink for + // the "implicit" null dereference. + C.addTransition(State, this); +} + +void ento::registerDereferenceChecker(CheckerManager &mgr) { + mgr.registerChecker<DereferenceChecker>(); +} + +bool ento::shouldRegisterDereferenceChecker(const LangOptions &LO) { + return true; +} |