966 lines
32 KiB
C++
966 lines
32 KiB
C++
|
//===- UninitializedValues.cpp - Find Uninitialized Values ----------------===//
|
||
|
//
|
||
|
// 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 file implements uninitialized values analysis for source-level CFGs.
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "clang/Analysis/Analyses/UninitializedValues.h"
|
||
|
#include "clang/AST/Attr.h"
|
||
|
#include "clang/AST/Decl.h"
|
||
|
#include "clang/AST/DeclBase.h"
|
||
|
#include "clang/AST/Expr.h"
|
||
|
#include "clang/AST/OperationKinds.h"
|
||
|
#include "clang/AST/Stmt.h"
|
||
|
#include "clang/AST/StmtObjC.h"
|
||
|
#include "clang/AST/StmtVisitor.h"
|
||
|
#include "clang/AST/Type.h"
|
||
|
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
|
||
|
#include "clang/Analysis/AnalysisDeclContext.h"
|
||
|
#include "clang/Analysis/CFG.h"
|
||
|
#include "clang/Analysis/DomainSpecific/ObjCNoReturn.h"
|
||
|
#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
|
||
|
#include "clang/Basic/LLVM.h"
|
||
|
#include "llvm/ADT/BitVector.h"
|
||
|
#include "llvm/ADT/DenseMap.h"
|
||
|
#include "llvm/ADT/None.h"
|
||
|
#include "llvm/ADT/Optional.h"
|
||
|
#include "llvm/ADT/PackedVector.h"
|
||
|
#include "llvm/ADT/SmallBitVector.h"
|
||
|
#include "llvm/ADT/SmallVector.h"
|
||
|
#include "llvm/Support/Casting.h"
|
||
|
#include <algorithm>
|
||
|
#include <cassert>
|
||
|
|
||
|
using namespace clang;
|
||
|
|
||
|
#define DEBUG_LOGGING 0
|
||
|
|
||
|
static bool isTrackedVar(const VarDecl *vd, const DeclContext *dc) {
|
||
|
if (vd->isLocalVarDecl() && !vd->hasGlobalStorage() &&
|
||
|
!vd->isExceptionVariable() && !vd->isInitCapture() &&
|
||
|
!vd->isImplicit() && vd->getDeclContext() == dc) {
|
||
|
QualType ty = vd->getType();
|
||
|
return ty->isScalarType() || ty->isVectorType() || ty->isRecordType();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------====//
|
||
|
// DeclToIndex: a mapping from Decls we track to value indices.
|
||
|
//====------------------------------------------------------------------------//
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
class DeclToIndex {
|
||
|
llvm::DenseMap<const VarDecl *, unsigned> map;
|
||
|
|
||
|
public:
|
||
|
DeclToIndex() = default;
|
||
|
|
||
|
/// Compute the actual mapping from declarations to bits.
|
||
|
void computeMap(const DeclContext &dc);
|
||
|
|
||
|
/// Return the number of declarations in the map.
|
||
|
unsigned size() const { return map.size(); }
|
||
|
|
||
|
/// Returns the bit vector index for a given declaration.
|
||
|
Optional<unsigned> getValueIndex(const VarDecl *d) const;
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
void DeclToIndex::computeMap(const DeclContext &dc) {
|
||
|
unsigned count = 0;
|
||
|
DeclContext::specific_decl_iterator<VarDecl> I(dc.decls_begin()),
|
||
|
E(dc.decls_end());
|
||
|
for ( ; I != E; ++I) {
|
||
|
const VarDecl *vd = *I;
|
||
|
if (isTrackedVar(vd, &dc))
|
||
|
map[vd] = count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Optional<unsigned> DeclToIndex::getValueIndex(const VarDecl *d) const {
|
||
|
llvm::DenseMap<const VarDecl *, unsigned>::const_iterator I = map.find(d);
|
||
|
if (I == map.end())
|
||
|
return None;
|
||
|
return I->second;
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------====//
|
||
|
// CFGBlockValues: dataflow values for CFG blocks.
|
||
|
//====------------------------------------------------------------------------//
|
||
|
|
||
|
// These values are defined in such a way that a merge can be done using
|
||
|
// a bitwise OR.
|
||
|
enum Value { Unknown = 0x0, /* 00 */
|
||
|
Initialized = 0x1, /* 01 */
|
||
|
Uninitialized = 0x2, /* 10 */
|
||
|
MayUninitialized = 0x3 /* 11 */ };
|
||
|
|
||
|
static bool isUninitialized(const Value v) {
|
||
|
return v >= Uninitialized;
|
||
|
}
|
||
|
|
||
|
static bool isAlwaysUninit(const Value v) {
|
||
|
return v == Uninitialized;
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
using ValueVector = llvm::PackedVector<Value, 2, llvm::SmallBitVector>;
|
||
|
|
||
|
class CFGBlockValues {
|
||
|
const CFG &cfg;
|
||
|
SmallVector<ValueVector, 8> vals;
|
||
|
ValueVector scratch;
|
||
|
DeclToIndex declToIndex;
|
||
|
|
||
|
public:
|
||
|
CFGBlockValues(const CFG &cfg);
|
||
|
|
||
|
unsigned getNumEntries() const { return declToIndex.size(); }
|
||
|
|
||
|
void computeSetOfDeclarations(const DeclContext &dc);
|
||
|
|
||
|
ValueVector &getValueVector(const CFGBlock *block) {
|
||
|
return vals[block->getBlockID()];
|
||
|
}
|
||
|
|
||
|
void setAllScratchValues(Value V);
|
||
|
void mergeIntoScratch(ValueVector const &source, bool isFirst);
|
||
|
bool updateValueVectorWithScratch(const CFGBlock *block);
|
||
|
|
||
|
bool hasNoDeclarations() const {
|
||
|
return declToIndex.size() == 0;
|
||
|
}
|
||
|
|
||
|
void resetScratch();
|
||
|
|
||
|
ValueVector::reference operator[](const VarDecl *vd);
|
||
|
|
||
|
Value getValue(const CFGBlock *block, const CFGBlock *dstBlock,
|
||
|
const VarDecl *vd) {
|
||
|
const Optional<unsigned> &idx = declToIndex.getValueIndex(vd);
|
||
|
assert(idx.hasValue());
|
||
|
return getValueVector(block)[idx.getValue()];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
CFGBlockValues::CFGBlockValues(const CFG &c) : cfg(c), vals(0) {}
|
||
|
|
||
|
void CFGBlockValues::computeSetOfDeclarations(const DeclContext &dc) {
|
||
|
declToIndex.computeMap(dc);
|
||
|
unsigned decls = declToIndex.size();
|
||
|
scratch.resize(decls);
|
||
|
unsigned n = cfg.getNumBlockIDs();
|
||
|
if (!n)
|
||
|
return;
|
||
|
vals.resize(n);
|
||
|
for (auto &val : vals)
|
||
|
val.resize(decls);
|
||
|
}
|
||
|
|
||
|
#if DEBUG_LOGGING
|
||
|
static void printVector(const CFGBlock *block, ValueVector &bv,
|
||
|
unsigned num) {
|
||
|
llvm::errs() << block->getBlockID() << " :";
|
||
|
for (const auto &i : bv)
|
||
|
llvm::errs() << ' ' << i;
|
||
|
llvm::errs() << " : " << num << '\n';
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
void CFGBlockValues::setAllScratchValues(Value V) {
|
||
|
for (unsigned I = 0, E = scratch.size(); I != E; ++I)
|
||
|
scratch[I] = V;
|
||
|
}
|
||
|
|
||
|
void CFGBlockValues::mergeIntoScratch(ValueVector const &source,
|
||
|
bool isFirst) {
|
||
|
if (isFirst)
|
||
|
scratch = source;
|
||
|
else
|
||
|
scratch |= source;
|
||
|
}
|
||
|
|
||
|
bool CFGBlockValues::updateValueVectorWithScratch(const CFGBlock *block) {
|
||
|
ValueVector &dst = getValueVector(block);
|
||
|
bool changed = (dst != scratch);
|
||
|
if (changed)
|
||
|
dst = scratch;
|
||
|
#if DEBUG_LOGGING
|
||
|
printVector(block, scratch, 0);
|
||
|
#endif
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
void CFGBlockValues::resetScratch() {
|
||
|
scratch.reset();
|
||
|
}
|
||
|
|
||
|
ValueVector::reference CFGBlockValues::operator[](const VarDecl *vd) {
|
||
|
const Optional<unsigned> &idx = declToIndex.getValueIndex(vd);
|
||
|
assert(idx.hasValue());
|
||
|
return scratch[idx.getValue()];
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------====//
|
||
|
// Classification of DeclRefExprs as use or initialization.
|
||
|
//====------------------------------------------------------------------------//
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
class FindVarResult {
|
||
|
const VarDecl *vd;
|
||
|
const DeclRefExpr *dr;
|
||
|
|
||
|
public:
|
||
|
FindVarResult(const VarDecl *vd, const DeclRefExpr *dr) : vd(vd), dr(dr) {}
|
||
|
|
||
|
const DeclRefExpr *getDeclRefExpr() const { return dr; }
|
||
|
const VarDecl *getDecl() const { return vd; }
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
static const Expr *stripCasts(ASTContext &C, const Expr *Ex) {
|
||
|
while (Ex) {
|
||
|
Ex = Ex->IgnoreParenNoopCasts(C);
|
||
|
if (const auto *CE = dyn_cast<CastExpr>(Ex)) {
|
||
|
if (CE->getCastKind() == CK_LValueBitCast) {
|
||
|
Ex = CE->getSubExpr();
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
return Ex;
|
||
|
}
|
||
|
|
||
|
/// If E is an expression comprising a reference to a single variable, find that
|
||
|
/// variable.
|
||
|
static FindVarResult findVar(const Expr *E, const DeclContext *DC) {
|
||
|
if (const auto *DRE =
|
||
|
dyn_cast<DeclRefExpr>(stripCasts(DC->getParentASTContext(), E)))
|
||
|
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
|
||
|
if (isTrackedVar(VD, DC))
|
||
|
return FindVarResult(VD, DRE);
|
||
|
return FindVarResult(nullptr, nullptr);
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
/// Classify each DeclRefExpr as an initialization or a use. Any
|
||
|
/// DeclRefExpr which isn't explicitly classified will be assumed to have
|
||
|
/// escaped the analysis and will be treated as an initialization.
|
||
|
class ClassifyRefs : public StmtVisitor<ClassifyRefs> {
|
||
|
public:
|
||
|
enum Class {
|
||
|
Init,
|
||
|
Use,
|
||
|
SelfInit,
|
||
|
ConstRefUse,
|
||
|
Ignore
|
||
|
};
|
||
|
|
||
|
private:
|
||
|
const DeclContext *DC;
|
||
|
llvm::DenseMap<const DeclRefExpr *, Class> Classification;
|
||
|
|
||
|
bool isTrackedVar(const VarDecl *VD) const {
|
||
|
return ::isTrackedVar(VD, DC);
|
||
|
}
|
||
|
|
||
|
void classify(const Expr *E, Class C);
|
||
|
|
||
|
public:
|
||
|
ClassifyRefs(AnalysisDeclContext &AC) : DC(cast<DeclContext>(AC.getDecl())) {}
|
||
|
|
||
|
void VisitDeclStmt(DeclStmt *DS);
|
||
|
void VisitUnaryOperator(UnaryOperator *UO);
|
||
|
void VisitBinaryOperator(BinaryOperator *BO);
|
||
|
void VisitCallExpr(CallExpr *CE);
|
||
|
void VisitCastExpr(CastExpr *CE);
|
||
|
void VisitOMPExecutableDirective(OMPExecutableDirective *ED);
|
||
|
|
||
|
void operator()(Stmt *S) { Visit(S); }
|
||
|
|
||
|
Class get(const DeclRefExpr *DRE) const {
|
||
|
llvm::DenseMap<const DeclRefExpr*, Class>::const_iterator I
|
||
|
= Classification.find(DRE);
|
||
|
if (I != Classification.end())
|
||
|
return I->second;
|
||
|
|
||
|
const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
|
||
|
if (!VD || !isTrackedVar(VD))
|
||
|
return Ignore;
|
||
|
|
||
|
return Init;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
static const DeclRefExpr *getSelfInitExpr(VarDecl *VD) {
|
||
|
if (VD->getType()->isRecordType())
|
||
|
return nullptr;
|
||
|
if (Expr *Init = VD->getInit()) {
|
||
|
const auto *DRE =
|
||
|
dyn_cast<DeclRefExpr>(stripCasts(VD->getASTContext(), Init));
|
||
|
if (DRE && DRE->getDecl() == VD)
|
||
|
return DRE;
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
void ClassifyRefs::classify(const Expr *E, Class C) {
|
||
|
// The result of a ?: could also be an lvalue.
|
||
|
E = E->IgnoreParens();
|
||
|
if (const auto *CO = dyn_cast<ConditionalOperator>(E)) {
|
||
|
classify(CO->getTrueExpr(), C);
|
||
|
classify(CO->getFalseExpr(), C);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (const auto *BCO = dyn_cast<BinaryConditionalOperator>(E)) {
|
||
|
classify(BCO->getFalseExpr(), C);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (const auto *OVE = dyn_cast<OpaqueValueExpr>(E)) {
|
||
|
classify(OVE->getSourceExpr(), C);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (const auto *ME = dyn_cast<MemberExpr>(E)) {
|
||
|
if (const auto *VD = dyn_cast<VarDecl>(ME->getMemberDecl())) {
|
||
|
if (!VD->isStaticDataMember())
|
||
|
classify(ME->getBase(), C);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (const auto *BO = dyn_cast<BinaryOperator>(E)) {
|
||
|
switch (BO->getOpcode()) {
|
||
|
case BO_PtrMemD:
|
||
|
case BO_PtrMemI:
|
||
|
classify(BO->getLHS(), C);
|
||
|
return;
|
||
|
case BO_Comma:
|
||
|
classify(BO->getRHS(), C);
|
||
|
return;
|
||
|
default:
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FindVarResult Var = findVar(E, DC);
|
||
|
if (const DeclRefExpr *DRE = Var.getDeclRefExpr())
|
||
|
Classification[DRE] = std::max(Classification[DRE], C);
|
||
|
}
|
||
|
|
||
|
void ClassifyRefs::VisitDeclStmt(DeclStmt *DS) {
|
||
|
for (auto *DI : DS->decls()) {
|
||
|
auto *VD = dyn_cast<VarDecl>(DI);
|
||
|
if (VD && isTrackedVar(VD))
|
||
|
if (const DeclRefExpr *DRE = getSelfInitExpr(VD))
|
||
|
Classification[DRE] = SelfInit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ClassifyRefs::VisitBinaryOperator(BinaryOperator *BO) {
|
||
|
// Ignore the evaluation of a DeclRefExpr on the LHS of an assignment. If this
|
||
|
// is not a compound-assignment, we will treat it as initializing the variable
|
||
|
// when TransferFunctions visits it. A compound-assignment does not affect
|
||
|
// whether a variable is uninitialized, and there's no point counting it as a
|
||
|
// use.
|
||
|
if (BO->isCompoundAssignmentOp())
|
||
|
classify(BO->getLHS(), Use);
|
||
|
else if (BO->getOpcode() == BO_Assign || BO->getOpcode() == BO_Comma)
|
||
|
classify(BO->getLHS(), Ignore);
|
||
|
}
|
||
|
|
||
|
void ClassifyRefs::VisitUnaryOperator(UnaryOperator *UO) {
|
||
|
// Increment and decrement are uses despite there being no lvalue-to-rvalue
|
||
|
// conversion.
|
||
|
if (UO->isIncrementDecrementOp())
|
||
|
classify(UO->getSubExpr(), Use);
|
||
|
}
|
||
|
|
||
|
void ClassifyRefs::VisitOMPExecutableDirective(OMPExecutableDirective *ED) {
|
||
|
for (Stmt *S : OMPExecutableDirective::used_clauses_children(ED->clauses()))
|
||
|
classify(cast<Expr>(S), Use);
|
||
|
}
|
||
|
|
||
|
static bool isPointerToConst(const QualType &QT) {
|
||
|
return QT->isAnyPointerType() && QT->getPointeeType().isConstQualified();
|
||
|
}
|
||
|
|
||
|
static bool hasTrivialBody(CallExpr *CE) {
|
||
|
if (FunctionDecl *FD = CE->getDirectCallee()) {
|
||
|
if (FunctionTemplateDecl *FTD = FD->getPrimaryTemplate())
|
||
|
return FTD->getTemplatedDecl()->hasTrivialBody();
|
||
|
return FD->hasTrivialBody();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void ClassifyRefs::VisitCallExpr(CallExpr *CE) {
|
||
|
// Classify arguments to std::move as used.
|
||
|
if (CE->isCallToStdMove()) {
|
||
|
// RecordTypes are handled in SemaDeclCXX.cpp.
|
||
|
if (!CE->getArg(0)->getType()->isRecordType())
|
||
|
classify(CE->getArg(0), Use);
|
||
|
return;
|
||
|
}
|
||
|
bool isTrivialBody = hasTrivialBody(CE);
|
||
|
// If a value is passed by const pointer to a function,
|
||
|
// we should not assume that it is initialized by the call, and we
|
||
|
// conservatively do not assume that it is used.
|
||
|
// If a value is passed by const reference to a function,
|
||
|
// it should already be initialized.
|
||
|
for (CallExpr::arg_iterator I = CE->arg_begin(), E = CE->arg_end();
|
||
|
I != E; ++I) {
|
||
|
if ((*I)->isGLValue()) {
|
||
|
if ((*I)->getType().isConstQualified())
|
||
|
classify((*I), isTrivialBody ? Ignore : ConstRefUse);
|
||
|
} else if (isPointerToConst((*I)->getType())) {
|
||
|
const Expr *Ex = stripCasts(DC->getParentASTContext(), *I);
|
||
|
const auto *UO = dyn_cast<UnaryOperator>(Ex);
|
||
|
if (UO && UO->getOpcode() == UO_AddrOf)
|
||
|
Ex = UO->getSubExpr();
|
||
|
classify(Ex, Ignore);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ClassifyRefs::VisitCastExpr(CastExpr *CE) {
|
||
|
if (CE->getCastKind() == CK_LValueToRValue)
|
||
|
classify(CE->getSubExpr(), Use);
|
||
|
else if (const auto *CSE = dyn_cast<CStyleCastExpr>(CE)) {
|
||
|
if (CSE->getType()->isVoidType()) {
|
||
|
// Squelch any detected load of an uninitialized value if
|
||
|
// we cast it to void.
|
||
|
// e.g. (void) x;
|
||
|
classify(CSE->getSubExpr(), Ignore);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------====//
|
||
|
// Transfer function for uninitialized values analysis.
|
||
|
//====------------------------------------------------------------------------//
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
class TransferFunctions : public StmtVisitor<TransferFunctions> {
|
||
|
CFGBlockValues &vals;
|
||
|
const CFG &cfg;
|
||
|
const CFGBlock *block;
|
||
|
AnalysisDeclContext ∾
|
||
|
const ClassifyRefs &classification;
|
||
|
ObjCNoReturn objCNoRet;
|
||
|
UninitVariablesHandler &handler;
|
||
|
|
||
|
public:
|
||
|
TransferFunctions(CFGBlockValues &vals, const CFG &cfg,
|
||
|
const CFGBlock *block, AnalysisDeclContext &ac,
|
||
|
const ClassifyRefs &classification,
|
||
|
UninitVariablesHandler &handler)
|
||
|
: vals(vals), cfg(cfg), block(block), ac(ac),
|
||
|
classification(classification), objCNoRet(ac.getASTContext()),
|
||
|
handler(handler) {}
|
||
|
|
||
|
void reportUse(const Expr *ex, const VarDecl *vd);
|
||
|
void reportConstRefUse(const Expr *ex, const VarDecl *vd);
|
||
|
|
||
|
void VisitBinaryOperator(BinaryOperator *bo);
|
||
|
void VisitBlockExpr(BlockExpr *be);
|
||
|
void VisitCallExpr(CallExpr *ce);
|
||
|
void VisitDeclRefExpr(DeclRefExpr *dr);
|
||
|
void VisitDeclStmt(DeclStmt *ds);
|
||
|
void VisitGCCAsmStmt(GCCAsmStmt *as);
|
||
|
void VisitObjCForCollectionStmt(ObjCForCollectionStmt *FS);
|
||
|
void VisitObjCMessageExpr(ObjCMessageExpr *ME);
|
||
|
void VisitOMPExecutableDirective(OMPExecutableDirective *ED);
|
||
|
|
||
|
bool isTrackedVar(const VarDecl *vd) {
|
||
|
return ::isTrackedVar(vd, cast<DeclContext>(ac.getDecl()));
|
||
|
}
|
||
|
|
||
|
FindVarResult findVar(const Expr *ex) {
|
||
|
return ::findVar(ex, cast<DeclContext>(ac.getDecl()));
|
||
|
}
|
||
|
|
||
|
UninitUse getUninitUse(const Expr *ex, const VarDecl *vd, Value v) {
|
||
|
UninitUse Use(ex, isAlwaysUninit(v));
|
||
|
|
||
|
assert(isUninitialized(v));
|
||
|
if (Use.getKind() == UninitUse::Always)
|
||
|
return Use;
|
||
|
|
||
|
// If an edge which leads unconditionally to this use did not initialize
|
||
|
// the variable, we can say something stronger than 'may be uninitialized':
|
||
|
// we can say 'either it's used uninitialized or you have dead code'.
|
||
|
//
|
||
|
// We track the number of successors of a node which have been visited, and
|
||
|
// visit a node once we have visited all of its successors. Only edges where
|
||
|
// the variable might still be uninitialized are followed. Since a variable
|
||
|
// can't transfer from being initialized to being uninitialized, this will
|
||
|
// trace out the subgraph which inevitably leads to the use and does not
|
||
|
// initialize the variable. We do not want to skip past loops, since their
|
||
|
// non-termination might be correlated with the initialization condition.
|
||
|
//
|
||
|
// For example:
|
||
|
//
|
||
|
// void f(bool a, bool b) {
|
||
|
// block1: int n;
|
||
|
// if (a) {
|
||
|
// block2: if (b)
|
||
|
// block3: n = 1;
|
||
|
// block4: } else if (b) {
|
||
|
// block5: while (!a) {
|
||
|
// block6: do_work(&a);
|
||
|
// n = 2;
|
||
|
// }
|
||
|
// }
|
||
|
// block7: if (a)
|
||
|
// block8: g();
|
||
|
// block9: return n;
|
||
|
// }
|
||
|
//
|
||
|
// Starting from the maybe-uninitialized use in block 9:
|
||
|
// * Block 7 is not visited because we have only visited one of its two
|
||
|
// successors.
|
||
|
// * Block 8 is visited because we've visited its only successor.
|
||
|
// From block 8:
|
||
|
// * Block 7 is visited because we've now visited both of its successors.
|
||
|
// From block 7:
|
||
|
// * Blocks 1, 2, 4, 5, and 6 are not visited because we didn't visit all
|
||
|
// of their successors (we didn't visit 4, 3, 5, 6, and 5, respectively).
|
||
|
// * Block 3 is not visited because it initializes 'n'.
|
||
|
// Now the algorithm terminates, having visited blocks 7 and 8, and having
|
||
|
// found the frontier is blocks 2, 4, and 5.
|
||
|
//
|
||
|
// 'n' is definitely uninitialized for two edges into block 7 (from blocks 2
|
||
|
// and 4), so we report that any time either of those edges is taken (in
|
||
|
// each case when 'b == false'), 'n' is used uninitialized.
|
||
|
SmallVector<const CFGBlock*, 32> Queue;
|
||
|
SmallVector<unsigned, 32> SuccsVisited(cfg.getNumBlockIDs(), 0);
|
||
|
Queue.push_back(block);
|
||
|
// Specify that we've already visited all successors of the starting block.
|
||
|
// This has the dual purpose of ensuring we never add it to the queue, and
|
||
|
// of marking it as not being a candidate element of the frontier.
|
||
|
SuccsVisited[block->getBlockID()] = block->succ_size();
|
||
|
while (!Queue.empty()) {
|
||
|
const CFGBlock *B = Queue.pop_back_val();
|
||
|
|
||
|
// If the use is always reached from the entry block, make a note of that.
|
||
|
if (B == &cfg.getEntry())
|
||
|
Use.setUninitAfterCall();
|
||
|
|
||
|
for (CFGBlock::const_pred_iterator I = B->pred_begin(), E = B->pred_end();
|
||
|
I != E; ++I) {
|
||
|
const CFGBlock *Pred = *I;
|
||
|
if (!Pred)
|
||
|
continue;
|
||
|
|
||
|
Value AtPredExit = vals.getValue(Pred, B, vd);
|
||
|
if (AtPredExit == Initialized)
|
||
|
// This block initializes the variable.
|
||
|
continue;
|
||
|
if (AtPredExit == MayUninitialized &&
|
||
|
vals.getValue(B, nullptr, vd) == Uninitialized) {
|
||
|
// This block declares the variable (uninitialized), and is reachable
|
||
|
// from a block that initializes the variable. We can't guarantee to
|
||
|
// give an earlier location for the diagnostic (and it appears that
|
||
|
// this code is intended to be reachable) so give a diagnostic here
|
||
|
// and go no further down this path.
|
||
|
Use.setUninitAfterDecl();
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (AtPredExit == MayUninitialized) {
|
||
|
// If the predecessor's terminator is an "asm goto" that initializes
|
||
|
// the variable, then it won't be counted as "initialized" on the
|
||
|
// non-fallthrough paths.
|
||
|
CFGTerminator term = Pred->getTerminator();
|
||
|
if (const auto *as = dyn_cast_or_null<GCCAsmStmt>(term.getStmt())) {
|
||
|
const CFGBlock *fallthrough = *Pred->succ_begin();
|
||
|
if (as->isAsmGoto() &&
|
||
|
llvm::any_of(as->outputs(), [&](const Expr *output) {
|
||
|
return vd == findVar(output).getDecl() &&
|
||
|
llvm::any_of(as->labels(),
|
||
|
[&](const AddrLabelExpr *label) {
|
||
|
return label->getLabel()->getStmt() == B->Label &&
|
||
|
B != fallthrough;
|
||
|
});
|
||
|
})) {
|
||
|
Use.setUninitAfterDecl();
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
unsigned &SV = SuccsVisited[Pred->getBlockID()];
|
||
|
if (!SV) {
|
||
|
// When visiting the first successor of a block, mark all NULL
|
||
|
// successors as having been visited.
|
||
|
for (CFGBlock::const_succ_iterator SI = Pred->succ_begin(),
|
||
|
SE = Pred->succ_end();
|
||
|
SI != SE; ++SI)
|
||
|
if (!*SI)
|
||
|
++SV;
|
||
|
}
|
||
|
|
||
|
if (++SV == Pred->succ_size())
|
||
|
// All paths from this block lead to the use and don't initialize the
|
||
|
// variable.
|
||
|
Queue.push_back(Pred);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Scan the frontier, looking for blocks where the variable was
|
||
|
// uninitialized.
|
||
|
for (const auto *Block : cfg) {
|
||
|
unsigned BlockID = Block->getBlockID();
|
||
|
const Stmt *Term = Block->getTerminatorStmt();
|
||
|
if (SuccsVisited[BlockID] && SuccsVisited[BlockID] < Block->succ_size() &&
|
||
|
Term) {
|
||
|
// This block inevitably leads to the use. If we have an edge from here
|
||
|
// to a post-dominator block, and the variable is uninitialized on that
|
||
|
// edge, we have found a bug.
|
||
|
for (CFGBlock::const_succ_iterator I = Block->succ_begin(),
|
||
|
E = Block->succ_end(); I != E; ++I) {
|
||
|
const CFGBlock *Succ = *I;
|
||
|
if (Succ && SuccsVisited[Succ->getBlockID()] >= Succ->succ_size() &&
|
||
|
vals.getValue(Block, Succ, vd) == Uninitialized) {
|
||
|
// Switch cases are a special case: report the label to the caller
|
||
|
// as the 'terminator', not the switch statement itself. Suppress
|
||
|
// situations where no label matched: we can't be sure that's
|
||
|
// possible.
|
||
|
if (isa<SwitchStmt>(Term)) {
|
||
|
const Stmt *Label = Succ->getLabel();
|
||
|
if (!Label || !isa<SwitchCase>(Label))
|
||
|
// Might not be possible.
|
||
|
continue;
|
||
|
UninitUse::Branch Branch;
|
||
|
Branch.Terminator = Label;
|
||
|
Branch.Output = 0; // Ignored.
|
||
|
Use.addUninitBranch(Branch);
|
||
|
} else {
|
||
|
UninitUse::Branch Branch;
|
||
|
Branch.Terminator = Term;
|
||
|
Branch.Output = I - Block->succ_begin();
|
||
|
Use.addUninitBranch(Branch);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return Use;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
void TransferFunctions::reportUse(const Expr *ex, const VarDecl *vd) {
|
||
|
Value v = vals[vd];
|
||
|
if (isUninitialized(v))
|
||
|
handler.handleUseOfUninitVariable(vd, getUninitUse(ex, vd, v));
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::reportConstRefUse(const Expr *ex, const VarDecl *vd) {
|
||
|
Value v = vals[vd];
|
||
|
if (isAlwaysUninit(v))
|
||
|
handler.handleConstRefUseOfUninitVariable(vd, getUninitUse(ex, vd, v));
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::VisitObjCForCollectionStmt(ObjCForCollectionStmt *FS) {
|
||
|
// This represents an initialization of the 'element' value.
|
||
|
if (const auto *DS = dyn_cast<DeclStmt>(FS->getElement())) {
|
||
|
const auto *VD = cast<VarDecl>(DS->getSingleDecl());
|
||
|
if (isTrackedVar(VD))
|
||
|
vals[VD] = Initialized;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::VisitOMPExecutableDirective(
|
||
|
OMPExecutableDirective *ED) {
|
||
|
for (Stmt *S : OMPExecutableDirective::used_clauses_children(ED->clauses())) {
|
||
|
assert(S && "Expected non-null used-in-clause child.");
|
||
|
Visit(S);
|
||
|
}
|
||
|
if (!ED->isStandaloneDirective())
|
||
|
Visit(ED->getStructuredBlock());
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::VisitBlockExpr(BlockExpr *be) {
|
||
|
const BlockDecl *bd = be->getBlockDecl();
|
||
|
for (const auto &I : bd->captures()) {
|
||
|
const VarDecl *vd = I.getVariable();
|
||
|
if (!isTrackedVar(vd))
|
||
|
continue;
|
||
|
if (I.isByRef()) {
|
||
|
vals[vd] = Initialized;
|
||
|
continue;
|
||
|
}
|
||
|
reportUse(be, vd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::VisitCallExpr(CallExpr *ce) {
|
||
|
if (Decl *Callee = ce->getCalleeDecl()) {
|
||
|
if (Callee->hasAttr<ReturnsTwiceAttr>()) {
|
||
|
// After a call to a function like setjmp or vfork, any variable which is
|
||
|
// initialized anywhere within this function may now be initialized. For
|
||
|
// now, just assume such a call initializes all variables. FIXME: Only
|
||
|
// mark variables as initialized if they have an initializer which is
|
||
|
// reachable from here.
|
||
|
vals.setAllScratchValues(Initialized);
|
||
|
}
|
||
|
else if (Callee->hasAttr<AnalyzerNoReturnAttr>()) {
|
||
|
// Functions labeled like "analyzer_noreturn" are often used to denote
|
||
|
// "panic" functions that in special debug situations can still return,
|
||
|
// but for the most part should not be treated as returning. This is a
|
||
|
// useful annotation borrowed from the static analyzer that is useful for
|
||
|
// suppressing branch-specific false positives when we call one of these
|
||
|
// functions but keep pretending the path continues (when in reality the
|
||
|
// user doesn't care).
|
||
|
vals.setAllScratchValues(Unknown);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::VisitDeclRefExpr(DeclRefExpr *dr) {
|
||
|
switch (classification.get(dr)) {
|
||
|
case ClassifyRefs::Ignore:
|
||
|
break;
|
||
|
case ClassifyRefs::Use:
|
||
|
reportUse(dr, cast<VarDecl>(dr->getDecl()));
|
||
|
break;
|
||
|
case ClassifyRefs::Init:
|
||
|
vals[cast<VarDecl>(dr->getDecl())] = Initialized;
|
||
|
break;
|
||
|
case ClassifyRefs::SelfInit:
|
||
|
handler.handleSelfInit(cast<VarDecl>(dr->getDecl()));
|
||
|
break;
|
||
|
case ClassifyRefs::ConstRefUse:
|
||
|
reportConstRefUse(dr, cast<VarDecl>(dr->getDecl()));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::VisitBinaryOperator(BinaryOperator *BO) {
|
||
|
if (BO->getOpcode() == BO_Assign) {
|
||
|
FindVarResult Var = findVar(BO->getLHS());
|
||
|
if (const VarDecl *VD = Var.getDecl())
|
||
|
vals[VD] = Initialized;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::VisitDeclStmt(DeclStmt *DS) {
|
||
|
for (auto *DI : DS->decls()) {
|
||
|
auto *VD = dyn_cast<VarDecl>(DI);
|
||
|
if (VD && isTrackedVar(VD)) {
|
||
|
if (getSelfInitExpr(VD)) {
|
||
|
// If the initializer consists solely of a reference to itself, we
|
||
|
// explicitly mark the variable as uninitialized. This allows code
|
||
|
// like the following:
|
||
|
//
|
||
|
// int x = x;
|
||
|
//
|
||
|
// to deliberately leave a variable uninitialized. Different analysis
|
||
|
// clients can detect this pattern and adjust their reporting
|
||
|
// appropriately, but we need to continue to analyze subsequent uses
|
||
|
// of the variable.
|
||
|
vals[VD] = Uninitialized;
|
||
|
} else if (VD->getInit()) {
|
||
|
// Treat the new variable as initialized.
|
||
|
vals[VD] = Initialized;
|
||
|
} else {
|
||
|
// No initializer: the variable is now uninitialized. This matters
|
||
|
// for cases like:
|
||
|
// while (...) {
|
||
|
// int n;
|
||
|
// use(n);
|
||
|
// n = 0;
|
||
|
// }
|
||
|
// FIXME: Mark the variable as uninitialized whenever its scope is
|
||
|
// left, since its scope could be re-entered by a jump over the
|
||
|
// declaration.
|
||
|
vals[VD] = Uninitialized;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::VisitGCCAsmStmt(GCCAsmStmt *as) {
|
||
|
// An "asm goto" statement is a terminator that may initialize some variables.
|
||
|
if (!as->isAsmGoto())
|
||
|
return;
|
||
|
|
||
|
for (const Expr *o : as->outputs())
|
||
|
if (const VarDecl *VD = findVar(o).getDecl())
|
||
|
if (vals[VD] != Initialized)
|
||
|
// If the variable isn't initialized by the time we get here, then we
|
||
|
// mark it as potentially uninitialized for those cases where it's used
|
||
|
// on an indirect path, where it's not guaranteed to be defined.
|
||
|
vals[VD] = MayUninitialized;
|
||
|
}
|
||
|
|
||
|
void TransferFunctions::VisitObjCMessageExpr(ObjCMessageExpr *ME) {
|
||
|
// If the Objective-C message expression is an implicit no-return that
|
||
|
// is not modeled in the CFG, set the tracked dataflow values to Unknown.
|
||
|
if (objCNoRet.isImplicitNoReturn(ME)) {
|
||
|
vals.setAllScratchValues(Unknown);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------====//
|
||
|
// High-level "driver" logic for uninitialized values analysis.
|
||
|
//====------------------------------------------------------------------------//
|
||
|
|
||
|
static bool runOnBlock(const CFGBlock *block, const CFG &cfg,
|
||
|
AnalysisDeclContext &ac, CFGBlockValues &vals,
|
||
|
const ClassifyRefs &classification,
|
||
|
llvm::BitVector &wasAnalyzed,
|
||
|
UninitVariablesHandler &handler) {
|
||
|
wasAnalyzed[block->getBlockID()] = true;
|
||
|
vals.resetScratch();
|
||
|
// Merge in values of predecessor blocks.
|
||
|
bool isFirst = true;
|
||
|
for (CFGBlock::const_pred_iterator I = block->pred_begin(),
|
||
|
E = block->pred_end(); I != E; ++I) {
|
||
|
const CFGBlock *pred = *I;
|
||
|
if (!pred)
|
||
|
continue;
|
||
|
if (wasAnalyzed[pred->getBlockID()]) {
|
||
|
vals.mergeIntoScratch(vals.getValueVector(pred), isFirst);
|
||
|
isFirst = false;
|
||
|
}
|
||
|
}
|
||
|
// Apply the transfer function.
|
||
|
TransferFunctions tf(vals, cfg, block, ac, classification, handler);
|
||
|
for (const auto &I : *block) {
|
||
|
if (Optional<CFGStmt> cs = I.getAs<CFGStmt>())
|
||
|
tf.Visit(const_cast<Stmt *>(cs->getStmt()));
|
||
|
}
|
||
|
CFGTerminator terminator = block->getTerminator();
|
||
|
if (auto *as = dyn_cast_or_null<GCCAsmStmt>(terminator.getStmt()))
|
||
|
if (as->isAsmGoto())
|
||
|
tf.Visit(as);
|
||
|
return vals.updateValueVectorWithScratch(block);
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
/// PruneBlocksHandler is a special UninitVariablesHandler that is used
|
||
|
/// to detect when a CFGBlock has any *potential* use of an uninitialized
|
||
|
/// variable. It is mainly used to prune out work during the final
|
||
|
/// reporting pass.
|
||
|
struct PruneBlocksHandler : public UninitVariablesHandler {
|
||
|
/// Records if a CFGBlock had a potential use of an uninitialized variable.
|
||
|
llvm::BitVector hadUse;
|
||
|
|
||
|
/// Records if any CFGBlock had a potential use of an uninitialized variable.
|
||
|
bool hadAnyUse = false;
|
||
|
|
||
|
/// The current block to scribble use information.
|
||
|
unsigned currentBlock = 0;
|
||
|
|
||
|
PruneBlocksHandler(unsigned numBlocks) : hadUse(numBlocks, false) {}
|
||
|
|
||
|
~PruneBlocksHandler() override = default;
|
||
|
|
||
|
void handleUseOfUninitVariable(const VarDecl *vd,
|
||
|
const UninitUse &use) override {
|
||
|
hadUse[currentBlock] = true;
|
||
|
hadAnyUse = true;
|
||
|
}
|
||
|
|
||
|
void handleConstRefUseOfUninitVariable(const VarDecl *vd,
|
||
|
const UninitUse &use) override {
|
||
|
hadUse[currentBlock] = true;
|
||
|
hadAnyUse = true;
|
||
|
}
|
||
|
|
||
|
/// Called when the uninitialized variable analysis detects the
|
||
|
/// idiom 'int x = x'. All other uses of 'x' within the initializer
|
||
|
/// are handled by handleUseOfUninitVariable.
|
||
|
void handleSelfInit(const VarDecl *vd) override {
|
||
|
hadUse[currentBlock] = true;
|
||
|
hadAnyUse = true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
void clang::runUninitializedVariablesAnalysis(
|
||
|
const DeclContext &dc,
|
||
|
const CFG &cfg,
|
||
|
AnalysisDeclContext &ac,
|
||
|
UninitVariablesHandler &handler,
|
||
|
UninitVariablesAnalysisStats &stats) {
|
||
|
CFGBlockValues vals(cfg);
|
||
|
vals.computeSetOfDeclarations(dc);
|
||
|
if (vals.hasNoDeclarations())
|
||
|
return;
|
||
|
|
||
|
stats.NumVariablesAnalyzed = vals.getNumEntries();
|
||
|
|
||
|
// Precompute which expressions are uses and which are initializations.
|
||
|
ClassifyRefs classification(ac);
|
||
|
cfg.VisitBlockStmts(classification);
|
||
|
|
||
|
// Mark all variables uninitialized at the entry.
|
||
|
const CFGBlock &entry = cfg.getEntry();
|
||
|
ValueVector &vec = vals.getValueVector(&entry);
|
||
|
const unsigned n = vals.getNumEntries();
|
||
|
for (unsigned j = 0; j < n; ++j) {
|
||
|
vec[j] = Uninitialized;
|
||
|
}
|
||
|
|
||
|
// Proceed with the workist.
|
||
|
ForwardDataflowWorklist worklist(cfg, ac);
|
||
|
llvm::BitVector previouslyVisited(cfg.getNumBlockIDs());
|
||
|
worklist.enqueueSuccessors(&cfg.getEntry());
|
||
|
llvm::BitVector wasAnalyzed(cfg.getNumBlockIDs(), false);
|
||
|
wasAnalyzed[cfg.getEntry().getBlockID()] = true;
|
||
|
PruneBlocksHandler PBH(cfg.getNumBlockIDs());
|
||
|
|
||
|
while (const CFGBlock *block = worklist.dequeue()) {
|
||
|
PBH.currentBlock = block->getBlockID();
|
||
|
|
||
|
// Did the block change?
|
||
|
bool changed = runOnBlock(block, cfg, ac, vals,
|
||
|
classification, wasAnalyzed, PBH);
|
||
|
++stats.NumBlockVisits;
|
||
|
if (changed || !previouslyVisited[block->getBlockID()])
|
||
|
worklist.enqueueSuccessors(block);
|
||
|
previouslyVisited[block->getBlockID()] = true;
|
||
|
}
|
||
|
|
||
|
if (!PBH.hadAnyUse)
|
||
|
return;
|
||
|
|
||
|
// Run through the blocks one more time, and report uninitialized variables.
|
||
|
for (const auto *block : cfg)
|
||
|
if (PBH.hadUse[block->getBlockID()]) {
|
||
|
runOnBlock(block, cfg, ac, vals, classification, wasAnalyzed, handler);
|
||
|
++stats.NumBlockVisits;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UninitVariablesHandler::~UninitVariablesHandler() = default;
|