[clang] [compiler-rt] [llvm] [UBSan] Add -fsanitize=uninitialized-read check (ISO C 6.3.2.1p2) (PR #207529)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 16 21:51:06 PDT 2026
https://github.com/devsw-prayas updated https://github.com/llvm/llvm-project/pull/207529
>From 0ecfdd2290a069a0fd92ac5c366672b13e580789 Mon Sep 17 00:00:00 2001
From: Prayas <prayas4494 at gnail.com>
Date: Fri, 17 Jul 2026 04:45:02 +0000
Subject: [PATCH] Remove UBSan uninitialized-read check, add MSan-based
replacement
The original UBSan implementation (Sanitizers.def entry,
SanitizerHandler.h check, ubsan_checks.inc, ubsan_handlers.h/.cpp) is
removed. In its place, detection is folded into MemorySanitizer, per
thurstond's review suggestion on PR #207529 -- mirroring the existing
-fsanitize-memory-param-retval precedent instead of adding a new
standalone UBSan check.
New MSan-side pieces: MD_msan_check_uninit_read metadata kind,
CheckLocalUninitReads option threaded through MemorySanitizerOptions,
a visitLoadInst hook calling insertCheckShadowOf, and driver flag
-fsanitize-memory-local-address-never-taken plumbed through
Options.td/CodeGenOptions.def/BackendUtil.cpp/SanitizerArgs.
CGExprScalar.cpp's UninitLocalVarVisitor is rebuilt to attach check
metadata after load emission instead of emitting a UBSan handler call
before it. VisitBinaryOperator and VisitGCCAsmStmt erasure guards are
dropped as redundant: MSan's own shadow propagation is already
path-sensitive, and MSan already zeroes shadow for asm output
operands. VisitUnaryOperator (address-taken exclusion) is kept --
that's a DR338/C11 scope boundary, not a defensive patch.
Adds compiler-rt/test/msan/local_uninit_read.c covering positive,
negative, path-sensitive, and language-gate cases.
878/940 check-ubsan, 344/352 check-msan, no regressions.
---
clang/include/clang/Basic/CodeGenOptions.def | 3 +
clang/include/clang/Driver/SanitizerArgs.h | 1 +
clang/include/clang/Options/Options.td | 7 ++
clang/lib/CodeGen/BackendUtil.cpp | 6 +-
clang/lib/CodeGen/CGExprScalar.cpp | 69 ++++++++++-
clang/lib/CodeGen/CodeGenFunction.h | 5 +
clang/lib/Driver/SanitizerArgs.cpp | 9 ++
compiler-rt/test/msan/local_uninit_read.c | 107 ++++++++++++++++++
llvm/include/llvm/IR/FixedMetadataKinds.def | 1 +
llvm/include/llvm/IR/Instruction.h | 4 +
.../Instrumentation/MemorySanitizer.h | 4 +-
llvm/lib/IR/Metadata.cpp | 5 +
.../Instrumentation/MemorySanitizer.cpp | 25 +++-
13 files changed, 239 insertions(+), 7 deletions(-)
create mode 100644 compiler-rt/test/msan/local_uninit_read.c
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index 7e54e75752f39..b4edb5bb3ad7b 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -283,6 +283,9 @@ ENUM_CODEGENOPT(SanitizeAddressDtor, AsanDtorKind, 2,
CODEGENOPT(SanitizeMemoryParamRetval, 1, 0, Benign) ///< Enable detection of uninitialized
///< parameters and return values
///< in MemorySanitizer
+CODEGENOPT(SanitizeMemoryLocalAddressNeverTaken, 1, 0, Benign) ///< Enable detection of
+ ///< uninitialized reads of
+ ///< locals never address-taken
CODEGENOPT(SanitizeMemoryUseAfterDtor, 1, 0, Benign) ///< Enable use-after-delete detection
///< in MemorySanitizer
CODEGENOPT(SanitizeCfiCrossDso, 1, 0, Benign) ///< Enable cross-dso support in CFI.
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 6a01b3e36d44c..b5fca73f74451 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -44,6 +44,7 @@ class SanitizerArgs {
int MsanTrackOrigins = 0;
bool MsanUseAfterDtor = true;
bool MsanParamRetval = true;
+ bool MsanCheckLocalUninitReads = false;
bool CfiCrossDso = false;
bool CfiICallGeneralizePointers = false;
bool CfiICallNormalizeIntegers = false;
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 4974209b8db30..034dcfa9c5fb1 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2744,6 +2744,13 @@ defm sanitize_memory_param_retval
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,
NegFlag<SetFalse, [], [ClangOption], "Disable">,
BothFlags<[], [ClangOption], " detection of uninitialized parameters and return values">>;
+defm sanitize_memory_local_address_never_taken
+ : BoolFOption<"sanitize-memory-local-address-never-taken",
+ CodeGenOpts<"SanitizeMemoryLocalAddressNeverTaken">,
+ DefaultFalse,
+ PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,
+ NegFlag<SetFalse, [], [ClangOption], "Disable">,
+ BothFlags<[], [ClangOption], " detection of reads of uninitialized local variables whose address is never taken">>;
//// Note: This flag was introduced when it was necessary to distinguish between
// ABI for correct codegen. This is no longer needed, but the flag is
// not removed since targeting either ABI will behave the same.
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index 2b755fa916e55..125724bd99eaf 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -714,8 +714,10 @@ static void addSanitizers(const Triple &TargetTriple,
int TrackOrigins = CodeGenOpts.SanitizeMemoryTrackOrigins;
bool Recover = CodeGenOpts.SanitizeRecover.has(Mask);
- MemorySanitizerOptions options(TrackOrigins, Recover, CompileKernel,
- CodeGenOpts.SanitizeMemoryParamRetval);
+ MemorySanitizerOptions options(
+ TrackOrigins, Recover, CompileKernel,
+ CodeGenOpts.SanitizeMemoryParamRetval,
+ CodeGenOpts.SanitizeMemoryLocalAddressNeverTaken);
MPM.addPass(MemorySanitizerPass(options));
if (Level != OptimizationLevel::O0) {
// MemorySanitizer inserts complex instrumentation that mostly follows
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 6f7b7e7d6840e..57f9c583c2afe 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -29,6 +29,7 @@
#include "clang/AST/MatrixUtils.h"
#include "clang/AST/ParentMapContext.h"
#include "clang/AST/RecordLayout.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/CodeGenOptions.h"
#include "clang/Basic/DiagnosticTrap.h"
@@ -294,6 +295,51 @@ static bool CanElideOverflowCheck(ASTContext &Ctx, const BinOpInfo &Op) {
(2 * Ctx.getTypeSize(RHSTy)) < PromotedSize;
}
+/// Walks a C function body once, collecting local scalar VarDecls that are
+/// never assigned and never have their address taken. These are candidates
+/// for MSan's local uninitialized-read check (ISO C 6.3.2.1p2 indeterminate
+/// value; the "address never taken" gate is spec-correct since DR338/C11,
+/// not C23-specific). Scoped to C only — C++ lambdas, captures, and
+/// aggregates are out of scope.
+class UninitLocalVarVisitor
+ : public RecursiveASTVisitor<UninitLocalVarVisitor> {
+ llvm::DenseMap<const VarDecl *, bool> &Candidates;
+
+public:
+ UninitLocalVarVisitor(llvm::DenseMap<const VarDecl *, bool> &Candidates)
+ : Candidates(Candidates) {}
+
+ bool VisitVarDecl(VarDecl *VD) {
+ if (VD->isLocalVarDecl() && !VD->isStaticLocal() && !VD->hasInit() &&
+ VD->getType()->isScalarType())
+ Candidates[VD] = true;
+ return true;
+ }
+
+ // Deliberately no VisitBinaryOperator override to erase candidates on
+ // assignment. Under MSan, attaching the check to more loads is safe:
+ // if a path actually initializes the variable, MSan's own shadow
+ // tracking (genuinely path-sensitive at runtime, via phi-node shadow
+ // merging) reflects that and the check silently passes. Erasing on
+ // any assignment, anywhere in the function, made sense for the prior
+ // UBSan-based design (an unconditional trap with no runtime awareness
+ // of which path was taken) but is unnecessarily conservative here --
+ // it would produce false negatives on variables that are correctly
+ // initialized on some paths and read uninitialized on others.
+ // Verified empirically: no regressions on check-msan/check-ubsan, and
+ // confirmed correct runtime behavior on both branches of a genuinely
+ // conditional assignment (warns on the uninitialized path, silent on
+ // the initialized one).
+
+ bool VisitUnaryOperator(UnaryOperator *UO) {
+ if (UO->getOpcode() == UO_AddrOf)
+ if (auto *DRE = dyn_cast<DeclRefExpr>(UO->getSubExpr()->IgnoreParens()))
+ if (auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+ Candidates.erase(VD);
+ return true;
+ }
+};
+
class ScalarExprEmitter
: public StmtVisitor<ScalarExprEmitter, Value*> {
CodeGenFunction &CGF;
@@ -608,7 +654,28 @@ class ScalarExprEmitter
Value *VisitDeclRefExpr(DeclRefExpr *E) {
if (CodeGenFunction::ConstantEmission Constant = CGF.tryEmitAsConstant(E))
return CGF.emitScalarConstant(Constant, E);
- return EmitLoadOfLValue(E);
+
+ Value *V = EmitLoadOfLValue(E);
+
+ if (CGF.SanOpts.has(SanitizerKind::Memory) &&
+ CGF.CGM.getCodeGenOpts().SanitizeMemoryLocalAddressNeverTaken &&
+ !CGF.getLangOpts().CPlusPlus) {
+ if (auto *VD = dyn_cast<VarDecl>(E->getDecl())) {
+ if (!CGF.UninitReadAnalysisDone) {
+ if (auto *FD = dyn_cast<FunctionDecl>(CGF.CurFuncDecl))
+ if (const Stmt *Body = FD->getBody())
+ UninitLocalVarVisitor(CGF.UninitReadCandidates)
+ .TraverseStmt(const_cast<Stmt *>(Body));
+ CGF.UninitReadAnalysisDone = true;
+ }
+ if (CGF.UninitReadCandidates.count(VD)) {
+ if (auto *LI = dyn_cast<llvm::LoadInst>(V))
+ LI->setUninitReadCheckMetadata();
+ }
+ }
+ }
+
+ return V;
}
Value *VisitObjCSelectorExpr(ObjCSelectorExpr *E) {
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index e7c24f1f36f1e..643c5a415c35c 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -591,6 +591,11 @@ class CodeGenFunction : public CodeGenTypeCache {
~SanitizerScope();
};
+ /// Cache for MSan local-uninitialized-read analysis.
+ /// Populated lazily on first scalar read in a function body.
+ bool UninitReadAnalysisDone = false;
+ llvm::DenseMap<const VarDecl *, bool> UninitReadCandidates;
+
/// In C++, whether we are code generating a thunk. This controls whether we
/// should emit cleanups.
bool CurFuncIsThunk = false;
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index e813efc89073d..96fded1bd1c69 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -902,14 +902,20 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
MsanParamRetval = Args.hasFlag(
options::OPT_fsanitize_memory_param_retval,
options::OPT_fno_sanitize_memory_param_retval, MsanParamRetval);
+ MsanCheckLocalUninitReads =
+ Args.hasFlag(options::OPT_fsanitize_memory_local_address_never_taken,
+ options::OPT_fno_sanitize_memory_local_address_never_taken,
+ MsanCheckLocalUninitReads);
} else if (AllAddedKinds & SanitizerKind::KernelMemory) {
MsanUseAfterDtor = false;
MsanParamRetval = Args.hasFlag(
options::OPT_fsanitize_memory_param_retval,
options::OPT_fno_sanitize_memory_param_retval, MsanParamRetval);
+ MsanCheckLocalUninitReads = false;
} else {
MsanUseAfterDtor = false;
MsanParamRetval = false;
+ MsanCheckLocalUninitReads = false;
}
if (AllAddedKinds & SanitizerKind::MemTag) {
@@ -1535,6 +1541,9 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args,
if (!MsanParamRetval)
CmdArgs.push_back("-fno-sanitize-memory-param-retval");
+ if (MsanCheckLocalUninitReads)
+ CmdArgs.push_back("-fsanitize-memory-local-address-never-taken");
+
// FIXME: Pass these parameters as function attributes, not as -llvm flags.
if (!TsanMemoryAccess) {
CmdArgs.push_back("-mllvm");
diff --git a/compiler-rt/test/msan/local_uninit_read.c b/compiler-rt/test/msan/local_uninit_read.c
new file mode 100644
index 0000000000000..484e7d5b964e4
--- /dev/null
+++ b/compiler-rt/test/msan/local_uninit_read.c
@@ -0,0 +1,107 @@
+// Tests the opt-in MSan local-uninitialized-read check
+// (-fsanitize-memory-local-address-never-taken): flags reads of local
+// scalar variables whose address is never taken, for values that are
+// not always initialized before the read (ISO C 6.3.2.1p2 indeterminate
+// value; the "address never taken" gate has been part of this rule
+// since DR338/C11, not C23-specific).
+//
+// The canonical positive case below is a *dead load* (read, then
+// discarded, no branch/return/store on the value) precisely because
+// that pattern reaches none of MSan's other checked sinks on its own --
+// unlike `return x;`, which vanilla -fsanitize=memory already flags via
+// its own return-value shadow tracking, independent of this feature.
+// Using `return x;` here would not actually test this code path; this
+// was confirmed empirically before writing this test.
+//
+// Positive: dead load of a never-initialized scalar -> warns.
+// RUN: %clang_msan -fsanitize-memory-local-address-never-taken -O0 -Wno-unused-value %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=WARN
+//
+// Negative: same dead load, but flag not passed -> vanilla MSan misses it.
+// RUN: %clang_msan -O0 -Wno-unused-value %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s --check-prefix=SILENT --allow-empty
+//
+// Negative: address taken -> not flagged even though never initialized.
+// RUN: %clang_msan -fsanitize-memory-local-address-never-taken -O0 -Wno-unused-value -DADDR_TAKEN %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s --check-prefix=SILENT --allow-empty
+//
+// Negative: initialized via GCC asm output operand -> not flagged.
+// RUN: %clang_msan -fsanitize-memory-local-address-never-taken -O0 -Wno-unused-value -DASM_INIT %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s --check-prefix=SILENT --allow-empty
+//
+// Negative: aggregate type (struct) -> out of scope (scalars only),
+// not flagged either with or without the feature.
+// RUN: %clang_msan -fsanitize-memory-local-address-never-taken -O0 -Wno-unused-value -DAGGREGATE %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s --check-prefix=SILENT --allow-empty
+//
+// Path-sensitive: conditionally-assigned scalar, dead load. Warns only
+// when the uninitialized path is actually taken at runtime (no extra
+// argv), silent when the initializing path was taken (extra argv
+// present). This works via MSan's own runtime shadow propagation
+// (genuinely path-sensitive via phi-node shadow merging at branch
+// joins) -- UninitLocalVarVisitor's own AST-level candidate detection
+// is a coarse whole-function existence pre-filter, not itself path
+// sensitive; it does not erase a candidate merely because it is
+// assigned somewhere in the function, and relies on MSan's shadow to
+// correctly reflect whether that assignment actually dominated this
+// particular execution.
+//
+// Note: deliberately not testing __builtin_unreachable() as a
+// substitute for the "always initialized" path here. Reaching a branch
+// marked unreachable at runtime is undefined behavior independent of
+// this feature; any apparent pass/fail from doing so is a coincidence
+// of code layout, not attributable to this check, and was confirmed
+// unreliable before this test was written.
+// RUN: %clang_msan -fsanitize-memory-local-address-never-taken -O0 -Wno-unused-value -DCONDITIONAL %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s --check-prefix=WARN
+// RUN: %run %t extra_arg 2>&1 | FileCheck %s --check-prefix=SILENT --allow-empty
+//
+// Negative: C++ excluded entirely by the language gate
+// (!CGF.getLangOpts().CPlusPlus in VisitDeclRefExpr).
+// RUN: %clangxx_msan -fsanitize-memory-local-address-never-taken -O0 -Wno-unused-value -x c++ %s -o %t
+// RUN: %run %t 2>&1 | FileCheck %s --check-prefix=SILENT --allow-empty
+
+#if defined(ADDR_TAKEN)
+int main() {
+ int x;
+ int *p = &x;
+ (void)p;
+ x;
+ return 0;
+}
+#elif defined(ASM_INIT)
+int main() {
+ int x;
+#if defined(__x86_64__) || defined(__i386__)
+ __asm__("movl $0, %0" : "=r"(x));
+#else
+ x = 0;
+#endif
+ x;
+ return 0;
+}
+#elif defined(AGGREGATE)
+struct S { int a; };
+int main() {
+ struct S s;
+ s.a;
+ return 0;
+}
+#elif defined(CONDITIONAL)
+int main(int argc, char **argv) {
+ int x;
+ if (argc > 1)
+ x = 5;
+ x;
+ return 0;
+}
+#else
+int main() {
+ int x;
+ x;
+ return 0;
+}
+#endif
+
+// WARN: WARNING: MemorySanitizer: use-of-uninitialized-value
+// SILENT-NOT: MemorySanitizer
diff --git a/llvm/include/llvm/IR/FixedMetadataKinds.def b/llvm/include/llvm/IR/FixedMetadataKinds.def
index cb6ebfb5c0fb5..8d23aadf0d2c7 100644
--- a/llvm/include/llvm/IR/FixedMetadataKinds.def
+++ b/llvm/include/llvm/IR/FixedMetadataKinds.def
@@ -66,4 +66,5 @@ LLVM_FIXED_MD_KIND(MD_unique_id, "guid", 51)
LLVM_FIXED_MD_KIND(MD_mem_cache_hint, "mem.cache_hint", 52)
LLVM_FIXED_MD_KIND(MD_block_uniformity_profile, "block.uniformity.profile", 53)
LLVM_FIXED_MD_KIND(MD_callgraph, "callgraph", 54)
+LLVM_FIXED_MD_KIND(MD_msan_check_uninit_read, "msan.check-uninit-read", 55)
diff --git a/llvm/include/llvm/IR/Instruction.h b/llvm/include/llvm/IR/Instruction.h
index 86843b339117f..6c8b5276c89a2 100644
--- a/llvm/include/llvm/IR/Instruction.h
+++ b/llvm/include/llvm/IR/Instruction.h
@@ -534,6 +534,10 @@ class Instruction : public User,
/// Sets the nosanitize metadata on this instruction.
LLVM_ABI void setNoSanitizeMetadata();
+ /// Marks this load as needing an MSan uninitialized-read check
+ /// (locals whose address is never taken).
+ LLVM_ABI void setUninitReadCheckMetadata();
+
/// Retrieve total raw weight values of a branch.
/// Returns true on success with profile total weights filled in.
/// Returns false if no metadata was found.
diff --git a/llvm/include/llvm/Transforms/Instrumentation/MemorySanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/MemorySanitizer.h
index b42f2e21aa52f..60d738f5c2ac5 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/MemorySanitizer.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/MemorySanitizer.h
@@ -27,11 +27,13 @@ struct MemorySanitizerOptions {
MemorySanitizerOptions(int TrackOrigins, bool Recover, bool Kernel)
: MemorySanitizerOptions(TrackOrigins, Recover, Kernel, false) {}
LLVM_ABI MemorySanitizerOptions(int TrackOrigins, bool Recover, bool Kernel,
- bool EagerChecks);
+ bool EagerChecks,
+ bool CheckLocalUninitReads = false);
bool Kernel;
int TrackOrigins;
bool Recover;
bool EagerChecks;
+ bool CheckLocalUninitReads;
};
/// A module pass for msan instrumentation.
diff --git a/llvm/lib/IR/Metadata.cpp b/llvm/lib/IR/Metadata.cpp
index 491c788fc4445..076734ac67c2c 100644
--- a/llvm/lib/IR/Metadata.cpp
+++ b/llvm/lib/IR/Metadata.cpp
@@ -1862,6 +1862,11 @@ void Instruction::setNoSanitizeMetadata() {
llvm::MDNode::get(getContext(), {}));
}
+void Instruction::setUninitReadCheckMetadata() {
+ setMetadata(llvm::LLVMContext::MD_msan_check_uninit_read,
+ llvm::MDNode::get(getContext(), {}));
+}
+
void Instruction::getAllMetadataImpl(
SmallVectorImpl<std::pair<unsigned, MDNode *>> &Result) const {
Result.clear();
diff --git a/llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp b/llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp
index cdce2e039154f..25ffe4eb0d998 100644
--- a/llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/MemorySanitizer.cpp
@@ -343,6 +343,12 @@ static cl::opt<bool> ClEagerChecks(
cl::desc("check arguments and return values at function call boundaries"),
cl::Hidden, cl::init(false));
+static cl::opt<bool> ClCheckLocalUninitReads(
+ "msan-check-local-uninit-reads",
+ cl::desc("check reads of local variables whose address is never "
+ "taken for uninitialized values"),
+ cl::Hidden, cl::init(false));
+
static cl::opt<bool> ClDumpStrictInstructions(
"msan-dump-strict-instructions",
cl::desc("print out instructions with default strict semantics i.e.,"
@@ -616,7 +622,8 @@ class MemorySanitizer {
public:
MemorySanitizer(Module &M, MemorySanitizerOptions Options)
: CompileKernel(Options.Kernel), TrackOrigins(Options.TrackOrigins),
- Recover(Options.Recover), EagerChecks(Options.EagerChecks) {
+ Recover(Options.Recover), EagerChecks(Options.EagerChecks),
+ CheckLocalUninitReads(Options.CheckLocalUninitReads) {
initializeModule(M);
}
@@ -654,6 +661,7 @@ class MemorySanitizer {
int TrackOrigins;
bool Recover;
bool EagerChecks;
+ bool CheckLocalUninitReads;
Triple TargetTriple;
LLVMContext *C;
@@ -778,11 +786,14 @@ template <class T> T getOptOrDefault(const cl::opt<T> &Opt, T Default) {
} // end anonymous namespace
MemorySanitizerOptions::MemorySanitizerOptions(int TO, bool R, bool K,
- bool EagerChecks)
+ bool EagerChecks,
+ bool CheckLocalUninitReads)
: Kernel(getOptOrDefault(ClEnableKmsan, K)),
TrackOrigins(getOptOrDefault(ClTrackOrigins, Kernel ? 2 : TO)),
Recover(getOptOrDefault(ClKeepGoing, Kernel || R)),
- EagerChecks(getOptOrDefault(ClEagerChecks, EagerChecks)) {}
+ EagerChecks(getOptOrDefault(ClEagerChecks, EagerChecks)),
+ CheckLocalUninitReads(
+ getOptOrDefault(ClCheckLocalUninitReads, CheckLocalUninitReads)) {}
PreservedAnalyses MemorySanitizerPass::run(Module &M,
ModuleAnalysisManager &AM) {
@@ -2371,6 +2382,10 @@ struct MemorySanitizerVisitor : public InstVisitor<MemorySanitizerVisitor> {
void visitLoadInst(LoadInst &I) {
assert(I.getType()->isSized() && "Load type must have size");
assert(!I.getMetadata(LLVMContext::MD_nosanitize));
+ // Captured before any shadow/origin instructions are spliced in after I,
+ // so this stays a valid anchor for a check that depends on I's own
+ // shadow (which NextNodeIRBuilder inserts *after* I, not before).
+ Instruction *UninitReadCheckAnchor = I.getNextNode();
NextNodeIRBuilder IRB(&I);
Type *ShadowTy = getShadowTy(&I);
Value *Addr = I.getPointerOperand();
@@ -2388,6 +2403,10 @@ struct MemorySanitizerVisitor : public InstVisitor<MemorySanitizerVisitor> {
if (ClCheckAccessAddress)
insertCheckShadowOf(I.getPointerOperand(), &I);
+ if (MS.CheckLocalUninitReads &&
+ I.getMetadata(LLVMContext::MD_msan_check_uninit_read))
+ insertCheckShadowOf(&I, UninitReadCheckAnchor);
+
if (I.isAtomic())
I.setOrdering(addAcquireOrdering(I.getOrdering()));
More information about the cfe-commits
mailing list