Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ def CallAndMessageChecker
"Check whether the reciever in the message expression is "
"undefined",
"true", Released>,
CmdLineOption<
Boolean, "ArgPointeeInitializednessComplete",
"If set to true, treat a struct as initialized when all of its "
"members are initialized, otherwise when it has any initialized "
"member (used only at ArgPointeeInitializedness)",
"false", Released>,
]>,
Documentation<HasDocumentation>;

Expand Down
198 changes: 122 additions & 76 deletions clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"

using namespace clang;
Expand Down Expand Up @@ -77,6 +78,10 @@ class CallAndMessageChecker

bool ChecksEnabled[CK_NumCheckKinds] = {false};

/// When checking a struct value for uninitialized data, should all the fields
/// be un-initialized or only find one uninitialized field.
bool StructInitializednessComplete = true;

void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;

/// Fill in the return value that results from messaging nil based on the
Expand Down Expand Up @@ -179,69 +184,23 @@ static void describeUninitializedArgumentInCall(const CallEvent &Call,
}
}

bool CallAndMessageChecker::uninitRefOrPointer(
CheckerContext &C, SVal V, SourceRange ArgRange, const Expr *ArgEx,
const BugType &BT, const ParmVarDecl *ParamDecl, int ArgumentNumber) const {

// The pointee being uninitialized is a sign of code smell, not a bug, no need
// to sink here.
if (!ChecksEnabled[CK_ArgPointeeInitializedness])
return false;

// No parameter declaration available, i.e. variadic function argument.
if(!ParamDecl)
return false;

// If parameter is declared as pointer to const in function declaration,
// then check if corresponding argument in function call is
// pointing to undefined symbol value (uninitialized memory).
SmallString<200> Buf;
llvm::raw_svector_ostream Os(Buf);

if (ParamDecl->getType()->isPointerType()) {
Os << (ArgumentNumber + 1) << llvm::getOrdinalSuffix(ArgumentNumber + 1)
<< " function call argument is a pointer to uninitialized value";
} else if (ParamDecl->getType()->isReferenceType()) {
Os << (ArgumentNumber + 1) << llvm::getOrdinalSuffix(ArgumentNumber + 1)
<< " function call argument is an uninitialized value";
} else
return false;

if(!ParamDecl->getType()->getPointeeType().isConstQualified())
return false;

if (const MemRegion *SValMemRegion = V.getAsRegion()) {
const ProgramStateRef State = C.getState();
const SVal PSV = State->getSVal(SValMemRegion, C.getASTContext().CharTy);
if (PSV.isUndef()) {
if (ExplodedNode *N = C.generateErrorNode()) {
auto R = std::make_unique<PathSensitiveBugReport>(BT, Os.str(), N);
R->addRange(ArgRange);
if (ArgEx)
bugreporter::trackExpressionValue(N, ArgEx, *R);

C.emitReport(std::move(R));
}
return true;
}
}
return false;
}

namespace {
class FindUninitializedField {
public:
SmallVector<const FieldDecl *, 10> FieldChain;
using FieldChainTy = SmallVector<const FieldDecl *, 10>;
FieldChainTy FieldChain;

private:
StoreManager &StoreMgr;
MemRegionManager &MrMgr;
Store store;
bool FindNotUninitialized;

public:
FindUninitializedField(StoreManager &storeMgr, MemRegionManager &mrMgr,
Store s)
: StoreMgr(storeMgr), MrMgr(mrMgr), store(s) {}
Store s, bool FindNotUninitialized = false)
: StoreMgr(storeMgr), MrMgr(mrMgr), store(s),
FindNotUninitialized(FindNotUninitialized) {}

bool Find(const TypedValueRegion *R) {
QualType T = R->getValueType();
Expand All @@ -255,22 +214,120 @@ class FindUninitializedField {
FieldChain.push_back(I);
T = I->getType();
if (T->isStructureType()) {
if (Find(FR))
return true;
if (FindNotUninitialized ? !Find(FR) : Find(FR))
return !FindNotUninitialized;
} else {
SVal V = StoreMgr.getBinding(store, loc::MemRegionVal(FR));
if (V.isUndef())
return true;
if (FindNotUninitialized ? !V.isUndef() : V.isUndef())
return !FindNotUninitialized;
}
FieldChain.pop_back();
}
}

return false;
return FindNotUninitialized;
}
};
} // namespace

namespace llvm {
template <> struct format_provider<FindUninitializedField::FieldChainTy> {
static void format(const FindUninitializedField::FieldChainTy &V,
raw_ostream &Stream, StringRef Style) {
if (V.size() == 0)
return;
else if (V.size() == 1)
Stream << " (e.g., field: '" << *V[0] << "')";
else {
Stream << " (e.g., via the field chain: '";
bool First = true;
for (const FieldDecl *FD : V) {
if (First)
First = false;
else
Stream << '.';
Stream << *FD;
}
Stream << "')";
}
}
};
} // namespace llvm

bool CallAndMessageChecker::uninitRefOrPointer(
CheckerContext &C, SVal V, SourceRange ArgRange, const Expr *ArgEx,
const BugType &BT, const ParmVarDecl *ParamDecl, int ArgumentNumber) const {

if (!ChecksEnabled[CK_ArgPointeeInitializedness])
return false;

// No parameter declaration available, i.e. variadic function argument.
if (!ParamDecl)
return false;

QualType ParamT = ParamDecl->getType();
if (!ParamT->isPointerOrReferenceType())
return false;

QualType PointeeT = ParamT->getPointeeType();
if (!PointeeT.isConstQualified())
return false;

const MemRegion *SValMemRegion = V.getAsRegion();
if (!SValMemRegion)
return false;

// If parameter is declared as pointer to const in function declaration,
// then check if corresponding argument in function call is
// pointing to undefined symbol value (uninitialized memory).

const ProgramStateRef State = C.getState();
if (PointeeT->isVoidType())
PointeeT = C.getASTContext().CharTy;
const SVal PointeeV = State->getSVal(SValMemRegion, PointeeT);

if (PointeeV.isUndef()) {
if (ExplodedNode *N = C.generateErrorNode()) {
std::string Msg = llvm::formatv(
"{0}{1} function call argument is {2} uninitialized value",
ArgumentNumber + 1, llvm::getOrdinalSuffix(ArgumentNumber + 1),
ParamT->isPointerType() ? "a pointer to" : "an");
auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
R->addRange(ArgRange);
if (ArgEx)
bugreporter::trackExpressionValue(N, ArgEx, *R);

C.emitReport(std::move(R));
}
return true;
}

if (auto LV = PointeeV.getAs<nonloc::LazyCompoundVal>()) {
const LazyCompoundValData *D = LV->getCVData();
FindUninitializedField F(C.getState()->getStateManager().getStoreManager(),
C.getSValBuilder().getRegionManager(),
D->getStore(), StructInitializednessComplete);

if (F.Find(D->getRegion())) {
if (ExplodedNode *N = C.generateErrorNode()) {
std::string Msg = llvm::formatv(
"{0}{1} function call argument {2} an uninitialized value{3}",
(ArgumentNumber + 1), llvm::getOrdinalSuffix(ArgumentNumber + 1),
ParamT->isPointerType() ? "points to" : "references", F.FieldChain);
auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
R->addRange(ArgRange);
if (ArgEx)
bugreporter::trackExpressionValue(N, ArgEx, *R);

C.emitReport(std::move(R));
}
return true;
}
}

return false;
}

bool CallAndMessageChecker::PreVisitProcessArg(
CheckerContext &C, SVal V, SourceRange ArgRange, const Expr *ArgEx,
int ArgumentNumber, bool CheckUninitFields, const CallEvent &Call,
Expand Down Expand Up @@ -313,27 +370,12 @@ bool CallAndMessageChecker::PreVisitProcessArg(
return true;
}
if (ExplodedNode *N = C.generateErrorNode()) {
SmallString<512> Str;
llvm::raw_svector_ostream os(Str);
os << "Passed-by-value struct argument contains uninitialized data";

if (F.FieldChain.size() == 1)
os << " (e.g., field: '" << *F.FieldChain[0] << "')";
else {
os << " (e.g., via the field chain: '";
bool first = true;
for (const FieldDecl *FD : F.FieldChain) {
if (first)
first = false;
else
os << '.';
os << *FD;
}
os << "')";
}
std::string Msg = llvm::formatv(
"Passed-by-value struct argument contains uninitialized data{0}",
F.FieldChain);

// Generate a report for this bug.
auto R = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);
auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
R->addRange(ArgRange);

if (ArgEx)
Expand Down Expand Up @@ -689,6 +731,10 @@ void ento::registerCallAndMessageChecker(CheckerManager &Mgr) {
QUERY_CHECKER_OPTION(ArgPointeeInitializedness)
QUERY_CHECKER_OPTION(NilReceiver)
QUERY_CHECKER_OPTION(UndefReceiver)

Chk->StructInitializednessComplete =
Mgr.getAnalyzerOptions().getCheckerBooleanOption(
Mgr.getCurrentCheckerName(), "ArgPointeeInitializednessComplete");
}

bool ento::shouldRegisterCallAndMessageChecker(const CheckerManager &) {
Expand Down
1 change: 1 addition & 0 deletions clang/test/Analysis/analyzer-config.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
// CHECK-NEXT: core.BitwiseShift:Pedantic = false
// CHECK-NEXT: core.CallAndMessage:ArgInitializedness = true
// CHECK-NEXT: core.CallAndMessage:ArgPointeeInitializedness = false
// CHECK-NEXT: core.CallAndMessage:ArgPointeeInitializednessComplete = false
// CHECK-NEXT: core.CallAndMessage:CXXDeallocationArg = true
// CHECK-NEXT: core.CallAndMessage:CXXThisMethodCall = true
// CHECK-NEXT: core.CallAndMessage:FunctionPointer = true
Expand Down
98 changes: 98 additions & 0 deletions clang/test/Analysis/call-and-message-argpointeeinitializedness.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// RUN: %clang_analyze_cc1 %s -verify=initializedness-complete \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-config core.CallAndMessage:ArgPointeeInitializedness=true \
// RUN: -analyzer-config core.CallAndMessage:ArgPointeeInitializednessComplete=true \
// RUN: -analyzer-config core.CallAndMessage:ArgInitializedness=false

// RUN: %clang_analyze_cc1 %s -verify=initializedness-partial \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-config core.CallAndMessage:ArgPointeeInitializedness=true \
// RUN: -analyzer-config core.CallAndMessage:ArgInitializedness=false

struct S1 {
char c;
};

struct S {
int a;
S1 b;
};

S GlobalS;

void doStuffP(const S *);
void doStuffR(const S &);

void uninit_val_p() {
S s;
doStuffP(&s); // initializedness-partial-warning{{1st function call argument points to an uninitialized value (e.g., field: 'a')}} \
// initializedness-complete-warning{{1st function call argument points to an uninitialized value}}
}

void uninit_val_r() {
S s;
doStuffR(s); // initializedness-partial-warning{{1st function call argument references an uninitialized value (e.g., field: 'a')}} \
// initializedness-complete-warning{{1st function call argument references an uninitialized value}}
}

S *uninit_new() {
S *s = new S;
doStuffP(s); // initializedness-partial-warning{{1st function call argument points to an uninitialized value (e.g., field: 'a')}} \
// initializedness-complete-warning{{1st function call argument points to an uninitialized value}}
return s;
}

void uninit_ctr() {
S s = S();
doStuffP(&s);
}

void uninit_init() {
S s{};
doStuffP(&s);
}

void uninit_init_val() {
S s{1, {2}};
doStuffP(&s);
}

void uninit_parm_ptr(S *s) {
doStuffP(s);
}

void uninit_parm_val(S s) {
doStuffP(&s);
}

void uninit_parm_ref(S &s) {
doStuffP(&s);
}

void init_val() {
S s;
s.a = 1;
s.b.c = 1;
doStuffP(&s);
}

void uninit_global() {
doStuffP(&GlobalS);
}

void uninit_static() {
static S s;
doStuffP(&s);
}

void uninit_val_partial_1() {
S s;
s.a = 1;
doStuffR(s); // initializedness-partial-warning{{1st function call argument references an uninitialized value (e.g., via the field chain: 'b.c')}}
}

void uninit_val_partial_2() {
S s;
s.b.c = 1;
doStuffR(s); // initializedness-partial-warning{{1st function call argument references an uninitialized value (e.g., field: 'a')}}
}
Loading