[clang] [llvm] [LLVM][Clang] Diagnose uninitialized record fields in IR (PR #227723)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 1 02:13:41 PDT 2026
https://github.com/XinlongZHANG-Bob updated https://github.com/llvm/llvm-project/pull/227723
>From 337ed983d059d63a825a235cdbf34cd281f5cf4e Mon Sep 17 00:00:00 2001
From: XinlongZHANG-Bob <zhangxinlong.bob at bytedance.com>
Date: Thu, 1 Oct 2026 17:12:23 +0800
Subject: [PATCH] [LLVM][Clang] Diagnose uninitialized record fields in IR
Add MemorySSA-based early and late passes for diagnosing scalar field
loads from uninitialized local record objects.
The early pass handles local stack objects, including field-sensitive
stores, control-flow merges, aggregate copies, and bounded callee
summaries. The late pass runs after inlining and additionally handles
fresh stack and heap objects exposed at call sites.
Report definite uses through -Wuninitialized and path-dependent uses
through -Wconditional-uninitialized. Unknown calls, escaped objects,
unsupported memory operations, and analysis limits remain conservative
and do not produce diagnostics.
Preserve source locations with LocTrackingOnly when either warning is
enabled, including -g0 builds.
---
.../clang/Basic/DiagnosticFrontendKinds.td | 7 +
.../CodeGenUtils/BackendDiagnosticHandler.h | 2 +
clang/lib/CodeGen/BackendUtil.cpp | 19 +
.../CodeGenUtils/BackendDiagnosticHandler.cpp | 31 +-
.../CodeGenCXX/warn-uninitialized-fields.cpp | 75 ++
.../CodeGenCXX/warn-uninitialized-late.cpp | 111 +++
llvm/include/llvm/IR/DiagnosticInfo.h | 21 +
.../Transforms/Scalar/WarnUninitialized.h | 50 ++
llvm/lib/IR/DiagnosticInfo.cpp | 11 +
llvm/lib/Passes/PassBuilder.cpp | 1 +
llvm/lib/Passes/PassRegistry.def | 2 +
llvm/lib/Transforms/Scalar/CMakeLists.txt | 1 +
.../Transforms/Scalar/WarnUninitialized.cpp | 753 ++++++++++++++++++
.../Transforms/WarnUninitialized/basic.ll | 318 ++++++++
.../test/Transforms/WarnUninitialized/late.ll | 181 +++++
15 files changed, 1575 insertions(+), 8 deletions(-)
create mode 100644 clang/test/CodeGenCXX/warn-uninitialized-fields.cpp
create mode 100644 clang/test/CodeGenCXX/warn-uninitialized-late.cpp
create mode 100644 llvm/include/llvm/Transforms/Scalar/WarnUninitialized.h
create mode 100644 llvm/lib/Transforms/Scalar/WarnUninitialized.cpp
create mode 100644 llvm/test/Transforms/WarnUninitialized/basic.ll
create mode 100644 llvm/test/Transforms/WarnUninitialized/late.ll
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 031858610ede2..1b8bd08059ffb 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -561,4 +561,11 @@ def warn_dyndbg_unable_to_create_target : Warning<
def err_dyndbg_no_instrumentation : Error<
"'-fdynamic-debugging' unsupported with instrumentation (PGO/code coverage)">;
+
+def warn_fe_backend_uninitialized : Warning<
+ "field is uninitialized when used here">,
+ InGroup<Uninitialized>, DefaultIgnore;
+def warn_fe_backend_maybe_uninitialized : Warning<
+ "field may be uninitialized when used here">,
+ InGroup<UninitializedMaybe>, DefaultIgnore;
}
diff --git a/clang/include/clang/CodeGenUtils/BackendDiagnosticHandler.h b/clang/include/clang/CodeGenUtils/BackendDiagnosticHandler.h
index 484b5c6859495..7ed839562524d 100644
--- a/clang/include/clang/CodeGenUtils/BackendDiagnosticHandler.h
+++ b/clang/include/clang/CodeGenUtils/BackendDiagnosticHandler.h
@@ -37,6 +37,7 @@ class DiagnosticInfoOptimizationFailure;
class DiagnosticInfoResourceLimit;
class DiagnosticInfoSrcMgr;
class DiagnosticInfoStackSize;
+class DiagnosticInfoUninitialized;
class DiagnosticInfoUnsupported;
class DiagnosticInfoUnsupportedTargetIntrinsic;
class DiagnosticInfoWithLocationBase;
@@ -134,6 +135,7 @@ class BackendDiagnosticConsumer {
/// Specialized handler for misexpect warnings.
/// Note that misexpect remarks are emitted through ORE
void MisExpectDiagHandler(const llvm::DiagnosticInfoMisExpect &D);
+ void UninitializedDiagHandler(const llvm::DiagnosticInfoUninitialized &D);
DiagnosticsEngine &Diags;
const CodeGenOptions &CodeGenOpts;
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index c754c75aa59de..6cc50b49c9f9d 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -95,6 +95,7 @@
#include "llvm/Transforms/Scalar/EarlyCSE.h"
#include "llvm/Transforms/Scalar/GVN.h"
#include "llvm/Transforms/Scalar/JumpThreading.h"
+#include "llvm/Transforms/Scalar/WarnUninitialized.h"
#include "llvm/Transforms/Utils/AssignGUID.h"
#include "llvm/Transforms/Utils/Debugify.h"
#include "llvm/Transforms/Utils/DynamicDebugging.h"
@@ -898,6 +899,24 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
SI.registerCallbacks(PIC, &MAM);
PassBuilder PB(TM.get(), PTO, PGOOpt, &PIC, CI.getVirtualFileSystemPtr());
+ if (!CI.getDiagnostics().isIgnored(diag::warn_fe_backend_uninitialized,
+ SourceLocation()) ||
+ !CI.getDiagnostics().isIgnored(diag::warn_fe_backend_maybe_uninitialized,
+ SourceLocation())) {
+ auto DiagnosticState = createWarnUninitializedDiagnosticState();
+ PB.registerPipelineStartEPCallback(
+ [DiagnosticState](ModulePassManager &MPM, OptimizationLevel) {
+ MPM.addPass(createModuleToFunctionPassAdaptor(
+ WarnUninitializedEarlyPass(DiagnosticState)));
+ });
+ PB.registerCGSCCOptimizerLateEPCallback(
+ [DiagnosticState](CGSCCPassManager &CGPM, OptimizationLevel Level) {
+ if (Level != OptimizationLevel::O0)
+ CGPM.addPass(createCGSCCToFunctionPassAdaptor(
+ WarnUninitializedLatePass(DiagnosticState)));
+ });
+ }
+
// Handle the assignment tracking feature options.
switch (CodeGenOpts.getAssignmentTrackingMode()) {
case CodeGenOptions::AssignmentTrackingOpts::Forced:
diff --git a/clang/lib/CodeGenUtils/BackendDiagnosticHandler.cpp b/clang/lib/CodeGenUtils/BackendDiagnosticHandler.cpp
index 667a7948fa18f..4c9ac2b44d67a 100644
--- a/clang/lib/CodeGenUtils/BackendDiagnosticHandler.cpp
+++ b/clang/lib/CodeGenUtils/BackendDiagnosticHandler.cpp
@@ -178,14 +178,6 @@ const FullSourceLoc BackendDiagnosticConsumer::getBestLocationFromDebugLoc(
Loc = *MaybeLoc;
}
- if (DILoc.isInvalid() && D.isLocationAvailable())
- // If we were not able to translate the file:line:col information
- // back to a SourceLocation, at least emit a note stating that
- // we could not translate this location. This can happen in the
- // case of #line directives.
- Diags.Report(Loc, diag::note_fe_backend_invalid_loc)
- << Filename << Line << Column;
-
return Loc;
}
@@ -549,6 +541,26 @@ void BackendDiagnosticConsumer::MisExpectDiagHandler(
<< Filename << Line << Column;
}
+void BackendDiagnosticConsumer::UninitializedDiagHandler(
+ const llvm::DiagnosticInfoUninitialized &D) {
+ unsigned DiagID = D.isMaybe() ? diag::warn_fe_backend_maybe_uninitialized
+ : diag::warn_fe_backend_uninitialized;
+ StringRef Filename;
+ unsigned Line, Column;
+ bool BadDebugInfo = false;
+ FullSourceLoc Loc;
+ if (SM)
+ Loc = getBestLocationFromDebugLoc(D, BadDebugInfo, Filename, Line, Column);
+
+ if (Diags.isIgnored(DiagID, Loc))
+ return;
+ Diags.Report(Loc, DiagID);
+
+ if (BadDebugInfo)
+ Diags.Report(Loc, diag::note_fe_backend_invalid_loc)
+ << Filename << Line << Column;
+}
+
void BackendDiagnosticConsumer::handleDiagnostics(const DiagnosticInfo &DI) {
unsigned DiagID = diag::err_fe_inline_asm;
llvm::DiagnosticSeverity Severity = DI.getSeverity();
@@ -633,6 +645,9 @@ void BackendDiagnosticConsumer::handleDiagnostics(const DiagnosticInfo &DI) {
case llvm::DK_MisExpect:
MisExpectDiagHandler(cast<DiagnosticInfoMisExpect>(DI));
return;
+ case llvm::DK_Uninitialized:
+ UninitializedDiagHandler(cast<DiagnosticInfoUninitialized>(DI));
+ return;
default:
// Plugin IDs are not bound to any value as they are set dynamically.
ComputeDiagRemarkID(Severity, backend_plugin, DiagID);
diff --git a/clang/test/CodeGenCXX/warn-uninitialized-fields.cpp b/clang/test/CodeGenCXX/warn-uninitialized-fields.cpp
new file mode 100644
index 0000000000000..f7b83d8366d74
--- /dev/null
+++ b/clang/test/CodeGenCXX/warn-uninitialized-fields.cpp
@@ -0,0 +1,75 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn,late %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wall -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn,late %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 -Wuninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 -Wconditional-uninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wconditional-uninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized -Wconditional-uninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn,late,maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized -Wconditional-uninitialized -emit-obj -o /dev/null -verify=imprecise %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized -emit-llvm -o - %s 2>/dev/null | FileCheck %s --check-prefix=NO-DEBUG-IR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wno-uninitialized -emit-obj -o /dev/null -verify=disabled %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -emit-obj -o /dev/null -verify=disabled %s
+// disabled-no-diagnostics
+// NO-DEBUG-IR: define
+// NO-DEBUG-IR-NOT: !dbg
+
+void consume(int);
+
+struct C {
+ int i;
+ int j;
+ C() {}
+ void set_i() { i = 1; }
+ void set_j() { j = 1; }
+ void inspect() const { consume(j); } // late-warning {{field is uninitialized when used here}}
+};
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void no_write() { C c; consume(c.i); } // imprecise-warning {{field is uninitialized when used here}}
+
+void same_field_write() { C c; c.set_i(); consume(c.i); }
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void sibling_field_write() { C c; c.set_j(); consume(c.i); } // imprecise-warning {{field is uninitialized when used here}}
+
+void unknown(C &);
+void unknown_call() { C c; unknown(c); consume(c.i); }
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void readonly_call() { C c; c.inspect(); consume(c.i); } // imprecise-warning 2 {{field is uninitialized when used here}}
+
+void conditional_write(bool condition) { // imprecise-warning {{field may be uninitialized when used here}}
+ C c;
+ if (condition)
+ c.i = 1;
+ // maybe-warning at +1 {{field may be uninitialized when used here}}
+ consume(c.i);
+}
+
+void conditional_sibling_write(bool condition) { // imprecise-warning {{field is uninitialized when used here}}
+ C c;
+ if (condition)
+ c.j = 1;
+ // warn-warning at +1 {{field is uninitialized when used here}}
+ consume(c.i);
+}
+
+struct B {
+ int i;
+ int j;
+};
+
+struct F {
+ int padding;
+ B b;
+};
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void copy_uninitialized() { B b; F f; f.b = b; consume(f.b.i); } // imprecise-warning {{field is uninitialized when used here}}
+
+void copy_initialized() { B b; b.i = 1; F f; f.b = b; consume(f.b.i); }
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void copy_sibling_initialized() { B b; b.j = 1; F f; f.b = b; consume(f.b.i); } // imprecise-warning {{field is uninitialized when used here}}
+
+C *escaped;
+void escaped_address() { C c; escaped = &c; consume(c.i); }
diff --git a/clang/test/CodeGenCXX/warn-uninitialized-late.cpp b/clang/test/CodeGenCXX/warn-uninitialized-late.cpp
new file mode 100644
index 0000000000000..376e45cc25771
--- /dev/null
+++ b/clang/test/CodeGenCXX/warn-uninitialized-late.cpp
@@ -0,0 +1,111 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized \
+// RUN: -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall \
+// RUN: -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 \
+// RUN: -Wconditional-uninitialized -debug-info-kind=line-tables-only \
+// RUN: -emit-obj -o /dev/null -verify=maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wuninitialized \
+// RUN: -Wconditional-uninitialized -debug-info-kind=line-tables-only \
+// RUN: -emit-obj -o /dev/null -verify=warn,maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized \
+// RUN: -Wconditional-uninitialized -emit-obj -o /dev/null \
+// RUN: -verify=imprecise %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 -Wuninitialized \
+// RUN: -emit-obj -o /dev/null -verify=disabled %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wno-uninitialized \
+// RUN: -emit-obj -o /dev/null -verify=disabled %s
+// disabled-no-diagnostics
+
+void consume(int);
+
+struct C {
+ int i;
+ C() {}
+ void set(int value) { i = value; }
+ void use() {
+ // warn-warning at +2 4 {{field is uninitialized when used here}}
+ // maybe-warning at +1 2 {{field may be uninitialized when used here}}
+ consume(i);
+ }
+};
+
+void unknown(C &);
+
+void stack_uninitialized() { // imprecise-warning {{field is uninitialized when used here}}
+ C c;
+ c.use();
+}
+
+void stack_initialized() {
+ C c;
+ c.set(1);
+ c.use();
+}
+
+void stack_conditionally_initialized(bool condition) { // imprecise-warning {{field may be uninitialized when used here}}
+ C c;
+ if (condition)
+ c.set(1);
+ c.use();
+}
+
+void heap_uninitialized() { // imprecise-warning {{field is uninitialized when used here}}
+ C *c = new C;
+ c->use();
+}
+
+void heap_initialized() {
+ C *c = new C;
+ c->set(1);
+ c->use();
+}
+
+void heap_conditionally_initialized(bool condition) { // imprecise-warning {{field may be uninitialized when used here}}
+ C *c = new C;
+ if (condition)
+ c->set(1);
+ c->use();
+}
+
+void heap_unknown_call() {
+ C *c = new C;
+ unknown(*c);
+ c->use();
+}
+
+struct Owner {
+ C *pointer;
+ C *operator->() { return pointer; }
+};
+
+void owner_uninitialized() { // imprecise-warning {{field is uninitialized when used here}}
+ Owner owner{new C};
+ owner->use();
+}
+
+struct Box {
+ long control[2];
+ C object;
+};
+
+void nonzero_offset_uninitialized() { // imprecise-warning {{field is uninitialized when used here}}
+ Box *box = new Box;
+ box->object.use();
+}
+
+enum class Cache : unsigned char { No, Yes, Unknown };
+
+struct BitFields {
+ unsigned precedence : 6;
+ Cache rhs : 2;
+ Cache array : 2;
+ Cache function : 2;
+
+ BitFields(unsigned p, Cache r, Cache a, Cache f)
+ : precedence(p), rhs(r), array(a), function(f) {}
+};
+
+BitFields *initialize_bit_fields(unsigned p, Cache r, Cache a, Cache f) {
+ return new BitFields(p, r, a, f);
+}
diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h
index da62b62bd8c74..17fa699fb2525 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -93,6 +93,7 @@ enum DiagnosticKind {
DK_SrcMgr,
DK_DontCall,
DK_MisExpect,
+ DK_Uninitialized,
DK_FirstPluginKind // Must be last value to work with
// getNextAvailablePluginDiagnosticKind
};
@@ -1171,6 +1172,26 @@ class LLVM_ABI DiagnosticInfoMisExpect : public DiagnosticInfoWithLocationBase {
const Twine &Msg;
};
+/// Diagnostic information for an uninitialized load.
+class LLVM_ABI DiagnosticInfoUninitialized
+ : public DiagnosticInfoWithLocationBase {
+public:
+ explicit DiagnosticInfoUninitialized(const Instruction *Inst,
+ bool Maybe = false);
+
+ /// \see DiagnosticInfo::print.
+ void print(DiagnosticPrinter &DP) const override;
+
+ static bool classof(const DiagnosticInfo *DI) {
+ return DI->getKind() == DK_Uninitialized;
+ }
+
+ bool isMaybe() const { return Maybe; }
+
+private:
+ bool Maybe;
+};
+
static DiagnosticSeverity getDiagnosticSeverity(SourceMgr::DiagKind DK) {
switch (DK) {
case llvm::SourceMgr::DK_Error:
diff --git a/llvm/include/llvm/Transforms/Scalar/WarnUninitialized.h b/llvm/include/llvm/Transforms/Scalar/WarnUninitialized.h
new file mode 100644
index 0000000000000..5eaa314a174a1
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Scalar/WarnUninitialized.h
@@ -0,0 +1,50 @@
+//===- WarnUninitialized.h - Warn about uninitialized loads -----*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_SCALAR_WARNUNINITIALIZED_H
+#define LLVM_TRANSFORMS_SCALAR_WARNUNINITIALIZED_H
+
+#include "llvm/IR/PassManager.h"
+#include <memory>
+
+namespace llvm {
+
+class WarnUninitializedDiagnosticState;
+
+LLVM_ABI std::shared_ptr<WarnUninitializedDiagnosticState>
+createWarnUninitializedDiagnosticState();
+
+class WarnUninitializedEarlyPass
+ : public RequiredPassInfoMixin<WarnUninitializedEarlyPass> {
+public:
+ explicit WarnUninitializedEarlyPass(
+ std::shared_ptr<WarnUninitializedDiagnosticState> State = nullptr)
+ : State(State) {}
+
+ LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+
+private:
+ std::shared_ptr<WarnUninitializedDiagnosticState> State;
+};
+
+class WarnUninitializedLatePass
+ : public RequiredPassInfoMixin<WarnUninitializedLatePass> {
+public:
+ explicit WarnUninitializedLatePass(
+ std::shared_ptr<WarnUninitializedDiagnosticState> State = nullptr)
+ : State(State) {}
+
+ LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+
+private:
+ std::shared_ptr<WarnUninitializedDiagnosticState> State;
+};
+
+} // namespace llvm
+
+#endif // LLVM_TRANSFORMS_SCALAR_WARNUNINITIALIZED_H
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index a24b6d0935008..895fadb309fcf 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -495,6 +495,17 @@ void DiagnosticInfoMisExpect::print(DiagnosticPrinter &DP) const {
DP << getLocationStr() << ": " << getMsg();
}
+DiagnosticInfoUninitialized::DiagnosticInfoUninitialized(
+ const Instruction *Inst, bool Maybe)
+ : DiagnosticInfoWithLocationBase(DK_Uninitialized, DS_Warning,
+ *Inst->getFunction(), Inst->getDebugLoc()),
+ Maybe(Maybe) {}
+
+void DiagnosticInfoUninitialized::print(DiagnosticPrinter &DP) const {
+ DP << getLocationStr() << ": field " << (Maybe ? "may be" : "is")
+ << " uninitialized when used here";
+}
+
void OptimizationRemarkAnalysisFPCommute::anchor() {}
void OptimizationRemarkAnalysisAliasing::anchor() {}
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 30a6f75a1b86d..a86118a13a8e5 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -378,6 +378,7 @@
#include "llvm/Transforms/Scalar/StructurizeCFG.h"
#include "llvm/Transforms/Scalar/TailRecursionElimination.h"
#include "llvm/Transforms/Scalar/WarnMissedTransforms.h"
+#include "llvm/Transforms/Scalar/WarnUninitialized.h"
#include "llvm/Transforms/Utils/AddDiscriminators.h"
#include "llvm/Transforms/Utils/AssignGUID.h"
#include "llvm/Transforms/Utils/AssumeBundleBuilder.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index af4ce5029551d..22aa8b9abf1e2 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -554,6 +554,8 @@ FUNCTION_PASS("strip-gc-relocates", StripGCRelocates())
FUNCTION_PASS("tailcallelim", TailCallElimPass())
FUNCTION_PASS("transform-warning", WarnMissedTransformationsPass())
FUNCTION_PASS("trigger-crash-function", TriggerCrashFunctionPass())
+FUNCTION_PASS("warn-uninitialized-early", WarnUninitializedEarlyPass())
+FUNCTION_PASS("warn-uninitialized-late", WarnUninitializedLatePass())
FUNCTION_PASS("trigger-verifier-error", TriggerVerifierErrorPass())
FUNCTION_PASS("tsan", ThreadSanitizerPass())
FUNCTION_PASS("typepromotion", TypePromotionPass(*TM))
diff --git a/llvm/lib/Transforms/Scalar/CMakeLists.txt b/llvm/lib/Transforms/Scalar/CMakeLists.txt
index c92fd202af968..67f8b0e7c5217 100644
--- a/llvm/lib/Transforms/Scalar/CMakeLists.txt
+++ b/llvm/lib/Transforms/Scalar/CMakeLists.txt
@@ -82,6 +82,7 @@ add_llvm_component_library(LLVMScalarOpts
StructurizeCFG.cpp
TailRecursionElimination.cpp
WarnMissedTransforms.cpp
+ WarnUninitialized.cpp
ADDITIONAL_HEADER_DIRS
${LLVM_MAIN_INCLUDE_DIR}/llvm/Transforms
diff --git a/llvm/lib/Transforms/Scalar/WarnUninitialized.cpp b/llvm/lib/Transforms/Scalar/WarnUninitialized.cpp
new file mode 100644
index 0000000000000..11833d92f98d2
--- /dev/null
+++ b/llvm/lib/Transforms/Scalar/WarnUninitialized.cpp
@@ -0,0 +1,753 @@
+//===- WarnUninitialized.cpp - Warn about uninitialized loads -------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// These passes classify scalar field loads from local records as initialized,
+/// uninitialized, conditionally uninitialized, or unknown by walking MemorySSA.
+/// The late pass also handles fresh heap allocations exposed by inlining. Calls
+/// are barriers unless a bounded scan of a visible callee proves that it does
+/// not modify the queried byte range.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Scalar/WarnUninitialized.h"
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Analysis/AliasAnalysis.h"
+#include "llvm/Analysis/MemoryBuiltins.h"
+#include "llvm/Analysis/MemoryLocation.h"
+#include "llvm/Analysis/MemorySSA.h"
+#include "llvm/Analysis/TargetLibraryInfo.h"
+#include "llvm/Analysis/ValueTracking.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/DiagnosticInfo.h"
+#include "llvm/IR/Dominators.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/ValueHandle.h"
+#include "llvm/Support/MathExtras.h"
+#include <limits>
+#include <optional>
+
+using namespace llvm;
+
+class llvm::WarnUninitializedDiagnosticState {
+ SmallVector<WeakTrackingVH, 8> DiagnosedLoads;
+
+public:
+ bool contains(const Instruction *I) const {
+ for (const WeakTrackingVH &VH : DiagnosedLoads)
+ if (static_cast<Value *>(VH) == I)
+ return true;
+ return false;
+ }
+
+ void insert(Instruction *I) { DiagnosedLoads.emplace_back(I); }
+};
+
+std::shared_ptr<WarnUninitializedDiagnosticState>
+llvm::createWarnUninitializedDiagnosticState() {
+ return std::make_shared<WarnUninitializedDiagnosticState>();
+}
+
+namespace {
+
+struct ByteRange {
+ int64_t Offset;
+ uint64_t Size;
+};
+
+static std::optional<int64_t> getEnd(ByteRange Range) {
+ if (Range.Size > uint64_t(std::numeric_limits<int64_t>::max()))
+ return std::nullopt;
+ int64_t End;
+ if (AddOverflow(Range.Offset, int64_t(Range.Size), End))
+ return std::nullopt;
+ return End;
+}
+
+static bool rangesOverlap(ByteRange LHS, ByteRange RHS) {
+ std::optional<int64_t> LHSEnd = getEnd(LHS);
+ std::optional<int64_t> RHSEnd = getEnd(RHS);
+ return !LHSEnd || !RHSEnd || (LHS.Offset < *RHSEnd && RHS.Offset < *LHSEnd);
+}
+
+static bool rangeContains(ByteRange Outer, ByteRange Inner) {
+ std::optional<int64_t> OuterEnd = getEnd(Outer);
+ std::optional<int64_t> InnerEnd = getEnd(Inner);
+ return OuterEnd && InnerEnd && Outer.Offset <= Inner.Offset &&
+ *InnerEnd <= *OuterEnd;
+}
+
+static bool isMaskedReadModifyWrite(const LoadInst &LI) {
+ const Value *Current = &LI;
+ bool SawMask = false;
+
+ while (Current->hasOneUse()) {
+ const User *OnlyUser = *Current->user_begin();
+ if (const auto *SI = dyn_cast<StoreInst>(OnlyUser))
+ return SawMask && SI->isSimple() && SI->getValueOperand() == Current &&
+ SI->getPointerOperand()->stripPointerCasts() ==
+ LI.getPointerOperand()->stripPointerCasts();
+
+ const auto *BO = dyn_cast<BinaryOperator>(OnlyUser);
+ if (!BO)
+ return false;
+
+ const Value *Other =
+ BO->getOperand(0) == Current ? BO->getOperand(1) : BO->getOperand(0);
+ if (BO->getOpcode() == Instruction::And) {
+ const auto *Mask = dyn_cast<ConstantInt>(Other);
+ if (SawMask || !Mask || Mask->isMinusOne())
+ return false;
+ SawMask = true;
+ } else if (BO->getOpcode() != Instruction::Or || !SawMask) {
+ return false;
+ }
+ Current = BO;
+ }
+ return false;
+}
+
+struct Query {
+ const Value *Object;
+ ByteRange Range;
+ MemoryLocation Location;
+};
+
+struct RootAndOffset {
+ const Value *Root;
+ int64_t Offset;
+};
+
+enum class AnalysisStage { Early, Late };
+
+enum class InitializationState {
+ Initialized,
+ Uninitialized,
+ MaybeUninitialized,
+ Unknown
+};
+
+struct SummaryKey {
+ const Function *F;
+ unsigned ArgNo;
+ ByteRange Range;
+};
+
+class UninitializedUseAnalyzer {
+ static constexpr unsigned MaxMemoryAccesses = 128;
+ static constexpr unsigned MaxSummaryDepth = 8;
+ static constexpr unsigned MaxSummaryInstructions = 1024;
+
+ const DataLayout &DL;
+ MemorySSA &MSSA;
+ MemorySSAWalker *Walker;
+ BatchAAResults BatchAA;
+ const TargetLibraryInfo *TLI;
+ AnalysisStage Stage;
+ SmallVector<SummaryKey, 8> SummaryStack;
+
+ std::optional<int64_t>
+ getOffsetFromRootImpl(const Value *Ptr, const Value *Root,
+ SmallPtrSetImpl<const Value *> &Visited) const {
+ if (!Ptr->getType()->isPointerTy() || !Visited.insert(Ptr).second)
+ return std::nullopt;
+ scope_exit RemoveVisited([&] { Visited.erase(Ptr); });
+
+ int64_t OuterOffset = 0;
+ const Value *Base = GetPointerBaseWithConstantOffset(Ptr, OuterOffset, DL);
+ if (Base == Root)
+ return OuterOffset;
+
+ if (const auto *LI = dyn_cast<LoadInst>(Base)) {
+ int64_t SpillOffset = 0;
+ const auto *Spill = dyn_cast<AllocaInst>(GetPointerBaseWithConstantOffset(
+ LI->getPointerOperand(), SpillOffset, DL));
+ if (!Spill || SpillOffset != 0 ||
+ !Spill->getAllocatedType()->isPointerTy())
+ return std::nullopt;
+
+ const StoreInst *Def = nullptr;
+ for (const User *U : Spill->users()) {
+ if (const auto *SI = dyn_cast<StoreInst>(U)) {
+ int64_t Offset = 0;
+ if (GetPointerBaseWithConstantOffset(SI->getPointerOperand(), Offset,
+ DL) != Spill ||
+ Offset != 0)
+ return std::nullopt;
+ if (Def)
+ return std::nullopt;
+ Def = SI;
+ continue;
+ }
+ if (isa<LoadInst>(U))
+ continue;
+ const auto *I = dyn_cast<Instruction>(U);
+ if (!I || (!I->isLifetimeStartOrEnd() && !I->isDroppable()))
+ return std::nullopt;
+ }
+
+ if (!Def || Def->getParent() != LI->getParent() || !Def->comesBefore(LI))
+ return std::nullopt;
+ std::optional<int64_t> StoredOffset =
+ getOffsetFromRootImpl(Def->getValueOperand(), Root, Visited);
+ if (!StoredOffset)
+ return std::nullopt;
+ int64_t Result;
+ if (AddOverflow(*StoredOffset, OuterOffset, Result))
+ return std::nullopt;
+ return Result;
+ }
+
+ auto MergeOffset = [&](auto Values) -> std::optional<int64_t> {
+ std::optional<int64_t> Common;
+ for (const Value *V : Values) {
+ std::optional<int64_t> Offset = getOffsetFromRootImpl(V, Root, Visited);
+ if (!Offset)
+ return std::nullopt;
+ if (Common && *Common != *Offset)
+ return std::nullopt;
+ Common = Offset;
+ }
+ if (!Common)
+ return std::nullopt;
+ int64_t Result;
+ if (AddOverflow(*Common, OuterOffset, Result))
+ return std::nullopt;
+ return Result;
+ };
+
+ if (const auto *PN = dyn_cast<PHINode>(Base))
+ return MergeOffset(PN->incoming_values());
+ if (const auto *SI = dyn_cast<SelectInst>(Base))
+ return MergeOffset(
+ ArrayRef<const Value *>{SI->getTrueValue(), SI->getFalseValue()});
+ return std::nullopt;
+ }
+
+ std::optional<int64_t> getOffsetFromRoot(const Value *Ptr,
+ const Value *Root) const {
+ SmallPtrSet<const Value *, 16> Visited;
+ return getOffsetFromRootImpl(Ptr, Root, Visited);
+ }
+
+ std::optional<RootAndOffset>
+ getRootAndOffsetImpl(const Value *Ptr,
+ SmallPtrSetImpl<const Value *> &Visited) {
+ if (!Ptr->getType()->isPointerTy() || !Visited.insert(Ptr).second)
+ return std::nullopt;
+ scope_exit RemoveVisited([&] { Visited.erase(Ptr); });
+
+ int64_t OuterOffset = 0;
+ const Value *Base = GetPointerBaseWithConstantOffset(Ptr, OuterOffset, DL);
+ if (isa<AllocaInst, CallBase>(Base))
+ return RootAndOffset{Base, OuterOffset};
+
+ const auto *LI = dyn_cast<LoadInst>(Base);
+ if (!LI || !LI->isSimple())
+ return std::nullopt;
+
+ MemoryAccess *Use = MSSA.getMemoryAccess(LI);
+ if (!Use)
+ return std::nullopt;
+ auto *Def =
+ dyn_cast<MemoryDef>(Walker->getClobberingMemoryAccess(Use, BatchAA));
+ if (!Def || !Def->getMemoryInst())
+ return std::nullopt;
+ const auto *SI = dyn_cast<StoreInst>(Def->getMemoryInst());
+ if (!SI || !SI->getValueOperand()->getType()->isPointerTy() ||
+ BatchAA.alias(MemoryLocation::get(LI), MemoryLocation::get(SI)) !=
+ AliasResult::MustAlias)
+ return std::nullopt;
+
+ std::optional<RootAndOffset> Stored =
+ getRootAndOffsetImpl(SI->getValueOperand(), Visited);
+ if (!Stored)
+ return std::nullopt;
+ int64_t Offset;
+ if (AddOverflow(Stored->Offset, OuterOffset, Offset))
+ return std::nullopt;
+ Stored->Offset = Offset;
+ return Stored;
+ }
+
+ std::optional<RootAndOffset> getRootAndOffset(const Value *Ptr) {
+ SmallPtrSet<const Value *, 16> Visited;
+ return getRootAndOffsetImpl(Ptr, Visited);
+ }
+
+ bool isSeparateObject(const Value *Ptr, const Value *Root) const {
+ const Value *Object = getUnderlyingObject(Ptr);
+ return Object != Root &&
+ (isa<AllocaInst>(Object) || isa<GlobalValue>(Object));
+ }
+
+ bool isBenignPointerSpill(const Value *Ptr) const {
+ int64_t Offset = 0;
+ const auto *AI =
+ dyn_cast<AllocaInst>(GetPointerBaseWithConstantOffset(Ptr, Offset, DL));
+ if (!AI || Offset != 0 || !AI->getAllocatedType()->isPointerTy())
+ return false;
+
+ for (const User *U : AI->users()) {
+ if (const auto *SI = dyn_cast<StoreInst>(U)) {
+ int64_t StoreOffset = 0;
+ if (GetPointerBaseWithConstantOffset(SI->getPointerOperand(),
+ StoreOffset, DL) != AI ||
+ StoreOffset != 0)
+ return false;
+ continue;
+ }
+ if (isa<LoadInst>(U))
+ continue;
+ const auto *I = dyn_cast<Instruction>(U);
+ if (!I || (!I->isLifetimeStartOrEnd() && !I->isDroppable()))
+ return false;
+ }
+ return true;
+ }
+
+ bool hasNonCallEscape(const Value *Root) const {
+ SmallVector<const Value *, 16> Worklist(1, Root);
+ SmallPtrSet<const Value *, 16> Visited;
+ while (!Worklist.empty()) {
+ const Value *V = Worklist.pop_back_val();
+ if (!Visited.insert(V).second)
+ continue;
+
+ for (const User *U : V->users()) {
+ const auto *I = dyn_cast<Instruction>(U);
+ if (!I)
+ return true;
+ if (isa<CallBase>(I) || I->isDroppable())
+ continue;
+ if (const auto *SI = dyn_cast<StoreInst>(I)) {
+ if (SI->getValueOperand() == V)
+ return true;
+ continue;
+ }
+ if (isa<LoadInst, ICmpInst>(I))
+ continue;
+ if (I->getType()->isPointerTy() &&
+ isa<GetElementPtrInst, BitCastInst, AddrSpaceCastInst, PHINode,
+ SelectInst, FreezeInst>(I)) {
+ Worklist.push_back(I);
+ continue;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool writeDoesNotOverlap(const Value *Ptr, uint64_t Size, const Value *Root,
+ ByteRange Target) const {
+ if (std::optional<int64_t> Offset = getOffsetFromRoot(Ptr, Root))
+ return !rangesOverlap({*Offset, Size}, Target);
+ return isSeparateObject(Ptr, Root);
+ }
+
+ std::optional<InitializationState> classifyWrite(const Value *Ptr,
+ uint64_t Size,
+ const Query &Q,
+ bool Initializes) const {
+ std::optional<int64_t> Offset = getOffsetFromRoot(Ptr, Q.Object);
+ if (!Offset) {
+ if (isSeparateObject(Ptr, Q.Object))
+ return std::nullopt;
+ return InitializationState::Unknown;
+ }
+
+ ByteRange WriteRange{*Offset, Size};
+ if (!rangesOverlap(WriteRange, Q.Range))
+ return std::nullopt;
+ if (Initializes && rangeContains(WriteRange, Q.Range))
+ return InitializationState::Initialized;
+ return InitializationState::Unknown;
+ }
+
+ bool calleeDoesNotModify(const Function &Callee, unsigned ArgNo,
+ ByteRange Target, unsigned Depth) {
+ if (Callee.isDeclaration() || ArgNo >= Callee.arg_size() ||
+ Depth >= MaxSummaryDepth ||
+ Callee.getInstructionCount() > MaxSummaryInstructions)
+ return false;
+
+ for (const SummaryKey &Key : SummaryStack)
+ if (Key.F == &Callee && Key.ArgNo == ArgNo &&
+ Key.Range.Offset == Target.Offset && Key.Range.Size == Target.Size)
+ return false;
+
+ SummaryStack.push_back({&Callee, ArgNo, Target});
+ scope_exit PopStack([&] { SummaryStack.pop_back(); });
+ const Argument *Root = Callee.getArg(ArgNo);
+
+ for (const Instruction &I : instructions(Callee)) {
+ if (const auto *SI = dyn_cast<StoreInst>(&I)) {
+ TypeSize Size = DL.getTypeStoreSize(SI->getValueOperand()->getType());
+ if (Size.isScalable() ||
+ !writeDoesNotOverlap(SI->getPointerOperand(), Size.getFixedValue(),
+ Root, Target))
+ return false;
+
+ if (SI->getValueOperand()->getType()->isPointerTy() &&
+ getOffsetFromRoot(SI->getValueOperand(), Root) &&
+ !isBenignPointerSpill(SI->getPointerOperand()))
+ return false;
+ continue;
+ }
+
+ if (const auto *MI = dyn_cast<MemIntrinsic>(&I)) {
+ const auto *Length = dyn_cast<ConstantInt>(MI->getLength());
+ if (!Length || !writeDoesNotOverlap(
+ MI->getDest(), Length->getZExtValue(), Root, Target))
+ return false;
+ continue;
+ }
+
+ if (const auto *RMW = dyn_cast<AtomicRMWInst>(&I)) {
+ TypeSize Size = DL.getTypeStoreSize(RMW->getValOperand()->getType());
+ if (Size.isScalable() ||
+ !writeDoesNotOverlap(RMW->getPointerOperand(), Size.getFixedValue(),
+ Root, Target))
+ return false;
+ continue;
+ }
+
+ if (const auto *CX = dyn_cast<AtomicCmpXchgInst>(&I)) {
+ TypeSize Size = DL.getTypeStoreSize(CX->getCompareOperand()->getType());
+ if (Size.isScalable() ||
+ !writeDoesNotOverlap(CX->getPointerOperand(), Size.getFixedValue(),
+ Root, Target))
+ return false;
+ continue;
+ }
+
+ if (const auto *CB = dyn_cast<CallBase>(&I)) {
+ if (CB->isLifetimeStartOrEnd() || CB->onlyReadsMemory())
+ continue;
+
+ const Function *Nested = CB->getCalledFunction();
+ for (unsigned I = 0; I < CB->arg_size(); ++I) {
+ const Value *Arg = CB->getArgOperand(I);
+ if (!Arg->getType()->isPointerTy())
+ continue;
+ std::optional<int64_t> Offset = getOffsetFromRoot(Arg, Root);
+ if (!Offset) {
+ if (!isa<ConstantPointerNull>(Arg) && !isSeparateObject(Arg, Root))
+ return false;
+ continue;
+ }
+ int64_t RelativeOffset;
+ if (!Nested || I >= Nested->arg_size() ||
+ SubOverflow(Target.Offset, *Offset, RelativeOffset) ||
+ !calleeDoesNotModify(*Nested, I, {RelativeOffset, Target.Size},
+ Depth + 1))
+ return false;
+ }
+ continue;
+ }
+
+ if (const auto *PTI = dyn_cast<PtrToIntInst>(&I)) {
+ if (getOffsetFromRoot(PTI->getPointerOperand(), Root))
+ return false;
+ }
+
+ if (const auto *RI = dyn_cast<ReturnInst>(&I)) {
+ const Value *ReturnValue = RI->getReturnValue();
+ if (ReturnValue && ReturnValue->getType()->isPointerTy() &&
+ getOffsetFromRoot(ReturnValue, Root))
+ return false;
+ }
+
+ if (I.mayWriteToMemory())
+ return false;
+ }
+ return true;
+ }
+
+ bool callDoesNotModify(const CallBase &CB, const Query &Q) {
+ if (CB.onlyReadsMemory())
+ return true;
+ const Function *Callee = CB.getCalledFunction();
+ if (!Callee)
+ return false;
+
+ bool FoundObjectArgument = false;
+ for (unsigned I = 0; I < CB.arg_size(); ++I) {
+ const Value *Arg = CB.getArgOperand(I);
+ if (!Arg->getType()->isPointerTy())
+ continue;
+ std::optional<int64_t> ArgOffset = getOffsetFromRoot(Arg, Q.Object);
+ if (!ArgOffset)
+ continue;
+ FoundObjectArgument = true;
+ int64_t RelativeOffset;
+ if (I >= Callee->arg_size() ||
+ SubOverflow(Q.Range.Offset, *ArgOffset, RelativeOffset) ||
+ !calleeDoesNotModify(*Callee, I, {RelativeOffset, Q.Range.Size}, 0))
+ return false;
+ }
+ return FoundObjectArgument;
+ }
+
+ std::optional<Query> getMemcpySourceQuery(const MemCpyInst &Copy,
+ const Query &Q) const {
+ const auto *Length = dyn_cast<ConstantInt>(Copy.getLength());
+ std::optional<int64_t> DestOffset =
+ getOffsetFromRoot(Copy.getDest(), Q.Object);
+ if (!Length || !DestOffset ||
+ !rangeContains({*DestOffset, Length->getZExtValue()}, Q.Range))
+ return std::nullopt;
+
+ int64_t OffsetInCopy;
+ if (SubOverflow(Q.Range.Offset, *DestOffset, OffsetInCopy))
+ return std::nullopt;
+
+ int64_t SourceBaseOffset = 0;
+ auto *SourceObject = dyn_cast<AllocaInst>(GetPointerBaseWithConstantOffset(
+ Copy.getSource(), SourceBaseOffset, DL));
+ if (!SourceObject || !SourceObject->isStaticAlloca() ||
+ SourceObject->isArrayAllocation() ||
+ !SourceObject->getAllocatedType()->isStructTy() ||
+ hasNonCallEscape(SourceObject))
+ return std::nullopt;
+
+ int64_t SourceOffset;
+ if (AddOverflow(SourceBaseOffset, OffsetInCopy, SourceOffset))
+ return std::nullopt;
+ TypeSize ObjectSize = DL.getTypeAllocSize(SourceObject->getAllocatedType());
+ if (ObjectSize.isScalable() ||
+ !rangeContains({0, ObjectSize.getFixedValue()},
+ {SourceOffset, Q.Range.Size}))
+ return std::nullopt;
+
+ return Query{SourceObject,
+ {SourceOffset, Q.Range.Size},
+ MemoryLocation(SourceObject, ObjectSize)};
+ }
+
+ std::optional<InitializationState> getMemoryDefState(const Instruction &I,
+ const Query &Q) {
+ if (I.isLifetimeStartOrEnd())
+ return std::nullopt;
+
+ if (const auto *SI = dyn_cast<StoreInst>(&I)) {
+ TypeSize Size = DL.getTypeStoreSize(SI->getValueOperand()->getType());
+ if (Size.isScalable())
+ return InitializationState::Unknown;
+ return classifyWrite(SI->getPointerOperand(), Size.getFixedValue(), Q,
+ /*Initializes=*/true);
+ }
+
+ if (const auto *MI = dyn_cast<MemIntrinsic>(&I)) {
+ const auto *Length = dyn_cast<ConstantInt>(MI->getLength());
+ if (!Length)
+ return InitializationState::Unknown;
+ return classifyWrite(MI->getDest(), Length->getZExtValue(), Q,
+ /*Initializes=*/isa<MemSetInst>(MI));
+ }
+
+ if (const auto *CB = dyn_cast<CallBase>(&I)) {
+ if (callDoesNotModify(*CB, Q))
+ return std::nullopt;
+ return InitializationState::Unknown;
+ }
+
+ return InitializationState::Unknown;
+ }
+
+ InitializationState
+ getInitializationState(MemoryAccess *Access, const Query &Q,
+ SmallPtrSetImpl<MemoryAccess *> &Active,
+ unsigned &NumAccesses) {
+ if (++NumAccesses > MaxMemoryAccesses || !Active.insert(Access).second)
+ return InitializationState::Unknown;
+ scope_exit RemoveActive([&] { Active.erase(Access); });
+
+ if (MSSA.isLiveOnEntryDef(Access))
+ return InitializationState::Uninitialized;
+
+ if (auto *Phi = dyn_cast<MemoryPhi>(Access)) {
+ if (Phi->getNumIncomingValues() == 0)
+ return InitializationState::Unknown;
+ std::optional<InitializationState> Merged;
+ for (unsigned I = 0; I < Phi->getNumIncomingValues(); ++I) {
+ MemoryAccess *Incoming = Phi->getIncomingValue(I);
+ MemoryAccess *Clobber =
+ Walker->getClobberingMemoryAccess(Incoming, Q.Location, BatchAA);
+ InitializationState State =
+ getInitializationState(Clobber, Q, Active, NumAccesses);
+ if (State == InitializationState::Unknown)
+ return State;
+ if (!Merged)
+ Merged = State;
+ else if (*Merged != State)
+ Merged = InitializationState::MaybeUninitialized;
+ }
+ return *Merged;
+ }
+
+ auto *Def = dyn_cast<MemoryDef>(Access);
+ if (!Def)
+ return InitializationState::Unknown;
+
+ if (Def->getMemoryInst() == Q.Object)
+ return InitializationState::Uninitialized;
+
+ if (const auto *Copy = dyn_cast<MemCpyInst>(Def->getMemoryInst())) {
+ if (std::optional<Query> Source = getMemcpySourceQuery(*Copy, Q)) {
+ MemoryAccess *SourceClobber = Walker->getClobberingMemoryAccess(
+ Def->getDefiningAccess(), Source->Location, BatchAA);
+ return getInitializationState(SourceClobber, *Source, Active,
+ NumAccesses);
+ }
+ }
+
+ std::optional<InitializationState> State =
+ getMemoryDefState(*Def->getMemoryInst(), Q);
+ if (State)
+ return *State;
+
+ MemoryAccess *Clobber = Walker->getClobberingMemoryAccess(
+ Def->getDefiningAccess(), Q.Location, BatchAA);
+ return getInitializationState(Clobber, Q, Active, NumAccesses);
+ }
+
+public:
+ UninitializedUseAnalyzer(Function &F, AAResults &AA, MemorySSA &MSSA,
+ const TargetLibraryInfo *TLI, AnalysisStage Stage)
+ : DL(F.getDataLayout()), MSSA(MSSA), Walker(MSSA.getWalker()),
+ BatchAA(AA), TLI(TLI), Stage(Stage) {}
+
+ InitializationState getInitializationState(LoadInst &LI) {
+ if (!LI.isSimple() ||
+ !(LI.getType()->isIntegerTy() || LI.getType()->isFloatingPointTy() ||
+ LI.getType()->isPointerTy()))
+ return InitializationState::Unknown;
+
+ if (Stage == AnalysisStage::Early && isMaskedReadModifyWrite(LI))
+ return InitializationState::Unknown;
+
+ TypeSize Size = DL.getTypeStoreSize(LI.getType());
+ if (Size.isScalable())
+ return InitializationState::Unknown;
+
+ const Value *Object;
+ int64_t Offset;
+ if (Stage == AnalysisStage::Early) {
+ const Value *AccessPtr = LI.getPointerOperand();
+ if (!isa<GetElementPtrInst>(AccessPtr))
+ return InitializationState::Unknown;
+
+ auto *AI = dyn_cast<AllocaInst>(
+ GetPointerBaseWithConstantOffset(AccessPtr, Offset, DL));
+ if (!AI || !AI->isStaticAlloca() || AI->isArrayAllocation() ||
+ !AI->getAllocatedType()->isStructTy() || hasNonCallEscape(AI))
+ return InitializationState::Unknown;
+ Object = AI;
+ } else {
+ if (LI.getDebugLoc() && !LI.getDebugLoc().getInlinedAt())
+ return InitializationState::Unknown;
+
+ std::optional<RootAndOffset> Root =
+ getRootAndOffset(LI.getPointerOperand());
+ if (!Root)
+ return InitializationState::Unknown;
+ Object = Root->Root;
+ Offset = Root->Offset;
+
+ uint64_t ObjectSize;
+ if (const auto *AI = dyn_cast<AllocaInst>(Object)) {
+ TypeSize Size = DL.getTypeAllocSize(AI->getAllocatedType());
+ if (!AI->isStaticAlloca() || AI->isArrayAllocation() ||
+ !AI->getAllocatedType()->isStructTy() || Size.isScalable() ||
+ hasNonCallEscape(AI))
+ return InitializationState::Unknown;
+ ObjectSize = Size.getFixedValue();
+ } else {
+ const auto *Alloc = dyn_cast<CallBase>(Object);
+ if (!Alloc || !TLI ||
+ !isa_and_nonnull<UndefValue>(
+ getInitialValueOfAllocation(Alloc, TLI, LI.getType())))
+ return InitializationState::Unknown;
+ std::optional<APInt> Size = getAllocSize(Alloc, TLI);
+ std::optional<uint64_t> FixedSize =
+ Size ? Size->tryZExtValue() : std::nullopt;
+ if (!FixedSize)
+ return InitializationState::Unknown;
+ ObjectSize = *FixedSize;
+ }
+
+ if (!rangeContains({0, ObjectSize}, {Offset, Size.getFixedValue()}))
+ return InitializationState::Unknown;
+ }
+
+ MemoryAccess *Use = MSSA.getMemoryAccess(&LI);
+ if (!Use)
+ return InitializationState::Unknown;
+ Query Q{Object, {Offset, Size.getFixedValue()}, MemoryLocation::get(&LI)};
+ MemoryAccess *Clobber = Walker->getClobberingMemoryAccess(Use, BatchAA);
+ SmallPtrSet<MemoryAccess *, 16> Active;
+ unsigned NumAccesses = 0;
+ return getInitializationState(Clobber, Q, Active, NumAccesses);
+ }
+};
+
+} // namespace
+
+static PreservedAnalyses
+runUninitializedAnalysis(Function &F, FunctionAnalysisManager &AM,
+ AnalysisStage Stage,
+ WarnUninitializedDiagnosticState *DiagnosticState) {
+ if (F.isDeclaration())
+ return PreservedAnalyses::all();
+
+ AAResults &AA = AM.getResult<AAManager>(F);
+ DominatorTree &DT = AM.getResult<DominatorTreeAnalysis>(F);
+ MemorySSA &MSSA = AM.getResult<MemorySSAAnalysis>(F).getMSSA();
+ const TargetLibraryInfo *TLI = Stage == AnalysisStage::Late
+ ? &AM.getResult<TargetLibraryAnalysis>(F)
+ : nullptr;
+ UninitializedUseAnalyzer Analyzer(F, AA, MSSA, TLI, Stage);
+ for (BasicBlock &BB : F) {
+ if (!DT.isReachableFromEntry(&BB))
+ continue;
+ for (Instruction &I : BB) {
+ auto *LI = dyn_cast<LoadInst>(&I);
+ // Post-inlining IR may contain speculative loads whose undef or poison
+ // result is masked before it can trigger undefined behavior.
+ if (!LI || (DiagnosticState && DiagnosticState->contains(LI)) ||
+ (Stage == AnalysisStage::Late &&
+ !programUndefinedIfUndefOrPoison(LI)))
+ continue;
+ InitializationState State = Analyzer.getInitializationState(*LI);
+ if (State == InitializationState::Uninitialized ||
+ State == InitializationState::MaybeUninitialized) {
+ if (DiagnosticState)
+ DiagnosticState->insert(LI);
+ F.getContext().diagnose(DiagnosticInfoUninitialized(
+ LI, State == InitializationState::MaybeUninitialized));
+ }
+ }
+ }
+
+ return PreservedAnalyses::all();
+}
+
+PreservedAnalyses WarnUninitializedEarlyPass::run(Function &F,
+ FunctionAnalysisManager &AM) {
+ return runUninitializedAnalysis(F, AM, AnalysisStage::Early, State.get());
+}
+
+PreservedAnalyses WarnUninitializedLatePass::run(Function &F,
+ FunctionAnalysisManager &AM) {
+ return runUninitializedAnalysis(F, AM, AnalysisStage::Late, State.get());
+}
diff --git a/llvm/test/Transforms/WarnUninitialized/basic.ll b/llvm/test/Transforms/WarnUninitialized/basic.ll
new file mode 100644
index 0000000000000..6a2114e47ffba
--- /dev/null
+++ b/llvm/test/Transforms/WarnUninitialized/basic.ll
@@ -0,0 +1,318 @@
+; RUN: opt -passes=warn-uninitialized-early -disable-output %s 2>&1 | FileCheck %s
+; RUN: opt -passes=strip -S %s | opt -passes=warn-uninitialized-early \
+; RUN: -disable-output - 2>&1 | FileCheck %s --check-prefix=NO-DEBUG
+
+; NO-DEBUG: warning: <unknown>:0:0: field is uninitialized when used here
+
+%pair = type { i32, i32 }
+
+ at escaped = global ptr null
+
+declare void @opaque(ptr)
+declare void @readonly(ptr) memory(read)
+declare void @consume(i32)
+declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)
+
+define void @no_write() !dbg !5 {
+entry:
+ %p = alloca %pair, align 4
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !20
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:10:7: field is uninitialized when used here
+
+define void @same_field_write() !dbg !6 {
+entry:
+ %p = alloca %pair, align 4
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ store i32 1, ptr %field, align 4
+ %value = load i32, ptr %field, align 4, !dbg !21
+ ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:20:7
+
+define void @sibling_field_write() !dbg !7 {
+entry:
+ %p = alloca %pair, align 4
+ %sibling = getelementptr inbounds %pair, ptr %p, i64 0, i32 1
+ store i32 1, ptr %sibling, align 4
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !22
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:30:7: field is uninitialized when used here
+
+define void @unknown_call() !dbg !8 {
+entry:
+ %p = alloca %pair, align 4
+ call void @opaque(ptr %p)
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !23
+ ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:40:7
+
+define void @readonly_call() !dbg !9 {
+entry:
+ %p = alloca %pair, align 4
+ call void @readonly(ptr %p)
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !24
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:50:7: field is uninitialized when used here
+
+define void @conditional_write(i1 %condition) !dbg !10 {
+entry:
+ %p = alloca %pair, align 4
+ br i1 %condition, label %init, label %merge
+
+init:
+ %init.field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ store i32 1, ptr %init.field, align 4
+ br label %merge
+
+merge:
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !25
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:60:7: field may be uninitialized when used here
+
+define void @conditional_sibling_write(i1 %condition) !dbg !11 {
+entry:
+ %p = alloca %pair, align 4
+ br i1 %condition, label %init, label %merge
+
+init:
+ %sibling = getelementptr inbounds %pair, ptr %p, i64 0, i32 1
+ store i32 1, ptr %sibling, align 4
+ br label %merge
+
+merge:
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !26
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:70:7: field is uninitialized when used here
+
+define internal void @empty(ptr %this) {
+entry:
+ %slot = alloca ptr, align 8
+ store ptr %this, ptr %slot, align 8
+ %reload = load ptr, ptr %slot, align 8
+ ret void
+}
+
+define internal void @write_sibling(ptr %this) {
+entry:
+ %slot = alloca ptr, align 8
+ store ptr %this, ptr %slot, align 8
+ %reload = load ptr, ptr %slot, align 8
+ %sibling = getelementptr inbounds %pair, ptr %reload, i64 0, i32 1
+ store i32 1, ptr %sibling, align 4
+ ret void
+}
+
+define internal void @write_field(ptr %this) {
+entry:
+ %slot = alloca ptr, align 8
+ store ptr %this, ptr %slot, align 8
+ %reload = load ptr, ptr %slot, align 8
+ %field = getelementptr inbounds %pair, ptr %reload, i64 0, i32 0
+ store i32 1, ptr %field, align 4
+ ret void
+}
+
+define void @empty_callee() !dbg !12 {
+entry:
+ %p = alloca %pair, align 4
+ call void @empty(ptr %p)
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !27
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:80:7: field is uninitialized when used here
+
+define void @sibling_callee() !dbg !13 {
+entry:
+ %p = alloca %pair, align 4
+ call void @write_sibling(ptr %p)
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !28
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:90:7: field is uninitialized when used here
+
+define void @field_callee() !dbg !14 {
+entry:
+ %p = alloca %pair, align 4
+ call void @write_field(ptr %p)
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !29
+ ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:100:7
+
+
+define void @copy_uninitialized() !dbg !15 {
+entry:
+ %source = alloca %pair, align 4
+ %dest = alloca %pair, align 4
+ call void @llvm.memcpy.p0.p0.i64(ptr %dest, ptr %source, i64 8, i1 false)
+ %field = getelementptr inbounds %pair, ptr %dest, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !30
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:110:7: field is uninitialized when used here
+
+define void @copy_initialized() !dbg !16 {
+entry:
+ %source = alloca %pair, align 4
+ %dest = alloca %pair, align 4
+ %source.field = getelementptr inbounds %pair, ptr %source, i64 0, i32 0
+ store i32 1, ptr %source.field, align 4
+ call void @llvm.memcpy.p0.p0.i64(ptr %dest, ptr %source, i64 8, i1 false)
+ %field = getelementptr inbounds %pair, ptr %dest, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !31
+ ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:120:7
+
+define void @copy_sibling_initialized() !dbg !17 {
+entry:
+ %source = alloca %pair, align 4
+ %dest = alloca %pair, align 4
+ %source.sibling = getelementptr inbounds %pair, ptr %source, i64 0, i32 1
+ store i32 1, ptr %source.sibling, align 4
+ call void @llvm.memcpy.p0.p0.i64(ptr %dest, ptr %source, i64 8, i1 false)
+ %field = getelementptr inbounds %pair, ptr %dest, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !32
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:130:7: field is uninitialized when used here
+
+define void @escaped_address() !dbg !18 {
+entry:
+ %p = alloca %pair, align 4
+ store ptr %p, ptr @escaped, align 8
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !33
+ ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:140:7
+
+define internal ptr @return_pointer(ptr %pointer) {
+entry:
+ ret ptr %pointer
+}
+
+define void @returned_address() !dbg !19 {
+entry:
+ %p = alloca %pair, align 4
+ %escaped = call ptr @return_pointer(ptr %p)
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !34
+ ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:150:7
+
+define void @unreachable_block() !dbg !35 {
+entry:
+ %p = alloca %pair, align 4
+ ret void
+
+dead:
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %value = load i32, ptr %field, align 4, !dbg !36
+ ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:160:7
+
+define void @masked_read_modify_write() !dbg !37 {
+entry:
+ %p = alloca %pair, align 4
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %old = load i32, ptr %field, align 4, !dbg !38
+ %preserved = and i32 %old, -8
+ %new = or i32 %preserved, 3
+ store i32 %new, ptr %field, align 4
+ ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:170:7
+
+define void @masked_read_modify_write_with_use() !dbg !39 {
+entry:
+ %p = alloca %pair, align 4
+ %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+ %old = load i32, ptr %field, align 4, !dbg !40
+ %preserved = and i32 %old, -8
+ %new = or i32 %preserved, 3
+ store i32 %new, ptr %field, align 4
+ call void @consume(i32 %old)
+ ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:180:7: field is uninitialized when used here
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "test", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly)
+!1 = !DIFile(filename: "warn-uninitialized.cpp", directory: "")
+!2 = !DISubroutineType(types: !4)
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{}
+!5 = distinct !DISubprogram(name: "no_write", scope: !1, file: !1, line: 1, type: !2, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!6 = distinct !DISubprogram(name: "same_field_write", scope: !1, file: !1, line: 2, type: !2, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+!7 = distinct !DISubprogram(name: "sibling_field_write", scope: !1, file: !1, line: 3, type: !2, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !0)
+!8 = distinct !DISubprogram(name: "unknown_call", scope: !1, file: !1, line: 4, type: !2, scopeLine: 4, spFlags: DISPFlagDefinition, unit: !0)
+!9 = distinct !DISubprogram(name: "readonly_call", scope: !1, file: !1, line: 5, type: !2, scopeLine: 5, spFlags: DISPFlagDefinition, unit: !0)
+!10 = distinct !DISubprogram(name: "conditional_write", scope: !1, file: !1, line: 6, type: !2, scopeLine: 6, spFlags: DISPFlagDefinition, unit: !0)
+!11 = distinct !DISubprogram(name: "conditional_sibling_write", scope: !1, file: !1, line: 7, type: !2, scopeLine: 7, spFlags: DISPFlagDefinition, unit: !0)
+!12 = distinct !DISubprogram(name: "empty_callee", scope: !1, file: !1, line: 8, type: !2, scopeLine: 8, spFlags: DISPFlagDefinition, unit: !0)
+!13 = distinct !DISubprogram(name: "sibling_callee", scope: !1, file: !1, line: 9, type: !2, scopeLine: 9, spFlags: DISPFlagDefinition, unit: !0)
+!14 = distinct !DISubprogram(name: "field_callee", scope: !1, file: !1, line: 10, type: !2, scopeLine: 10, spFlags: DISPFlagDefinition, unit: !0)
+!15 = distinct !DISubprogram(name: "copy_uninitialized", scope: !1, file: !1, line: 11, type: !2, scopeLine: 11, spFlags: DISPFlagDefinition, unit: !0)
+!16 = distinct !DISubprogram(name: "copy_initialized", scope: !1, file: !1, line: 12, type: !2, scopeLine: 12, spFlags: DISPFlagDefinition, unit: !0)
+!17 = distinct !DISubprogram(name: "copy_sibling_initialized", scope: !1, file: !1, line: 13, type: !2, scopeLine: 13, spFlags: DISPFlagDefinition, unit: !0)
+!18 = distinct !DISubprogram(name: "escaped_address", scope: !1, file: !1, line: 14, type: !2, scopeLine: 14, spFlags: DISPFlagDefinition, unit: !0)
+!19 = distinct !DISubprogram(name: "returned_address", scope: !1, file: !1, line: 15, type: !2, scopeLine: 15, spFlags: DISPFlagDefinition, unit: !0)
+!20 = !DILocation(line: 10, column: 7, scope: !5)
+!21 = !DILocation(line: 20, column: 7, scope: !6)
+!22 = !DILocation(line: 30, column: 7, scope: !7)
+!23 = !DILocation(line: 40, column: 7, scope: !8)
+!24 = !DILocation(line: 50, column: 7, scope: !9)
+!25 = !DILocation(line: 60, column: 7, scope: !10)
+!26 = !DILocation(line: 70, column: 7, scope: !11)
+!27 = !DILocation(line: 80, column: 7, scope: !12)
+!28 = !DILocation(line: 90, column: 7, scope: !13)
+!29 = !DILocation(line: 100, column: 7, scope: !14)
+!30 = !DILocation(line: 110, column: 7, scope: !15)
+!31 = !DILocation(line: 120, column: 7, scope: !16)
+!32 = !DILocation(line: 130, column: 7, scope: !17)
+!33 = !DILocation(line: 140, column: 7, scope: !18)
+!34 = !DILocation(line: 150, column: 7, scope: !19)
+!35 = distinct !DISubprogram(name: "unreachable_block", scope: !1, file: !1, line: 16, type: !2, scopeLine: 16, spFlags: DISPFlagDefinition, unit: !0)
+!36 = !DILocation(line: 160, column: 7, scope: !35)
+!37 = distinct !DISubprogram(name: "masked_read_modify_write", scope: !1, file: !1, line: 17, type: !2, scopeLine: 17, spFlags: DISPFlagDefinition, unit: !0)
+!38 = !DILocation(line: 170, column: 7, scope: !37)
+!39 = distinct !DISubprogram(name: "masked_read_modify_write_with_use", scope: !1, file: !1, line: 18, type: !2, scopeLine: 18, spFlags: DISPFlagDefinition, unit: !0)
+!40 = !DILocation(line: 180, column: 7, scope: !39)
diff --git a/llvm/test/Transforms/WarnUninitialized/late.ll b/llvm/test/Transforms/WarnUninitialized/late.ll
new file mode 100644
index 0000000000000..de7eb124d959f
--- /dev/null
+++ b/llvm/test/Transforms/WarnUninitialized/late.ll
@@ -0,0 +1,181 @@
+; RUN: opt -passes=warn-uninitialized-late -disable-output %s 2>&1 | FileCheck %s
+; RUN: opt -passes=strip -S %s | opt -passes=warn-uninitialized-late \
+; RUN: -disable-output - 2>&1 | FileCheck %s --check-prefix=NO-DEBUG
+
+; NO-DEBUG: warning: <unknown>:0:0: field is uninitialized when used here
+
+target triple = "x86_64-unknown-linux-gnu"
+
+%pair = type { i32, i32 }
+
+declare noalias ptr @malloc(i64) nounwind allockind("alloc,uninitialized") allocsize(0)
+declare noalias ptr @calloc(i64, i64) nounwind allockind("alloc,zeroed") allocsize(0,1)
+declare void @opaque(ptr)
+declare void @consume(i32 noundef)
+
+define void @stack_uninitialized() !dbg !5 {
+entry:
+ %object = alloca %pair, align 4
+ %value = load i32, ptr %object, align 4, !dbg !20
+ call void @consume(i32 %value)
+ ret void
+}
+; CHECK: warning: late.cpp:10:7: field is uninitialized when used here
+
+define void @heap_uninitialized() !dbg !6 {
+entry:
+ %object = call ptr @malloc(i64 8)
+ %value = load i32, ptr %object, align 4, !dbg !21
+ call void @consume(i32 %value)
+ ret void
+}
+; CHECK: warning: late.cpp:20:7: field is uninitialized when used here
+
+define void @heap_sibling_initialized() !dbg !7 {
+entry:
+ %object = call ptr @malloc(i64 8)
+ %sibling = getelementptr i8, ptr %object, i64 4
+ store i32 1, ptr %sibling, align 4
+ %value = load i32, ptr %object, align 4, !dbg !22
+ call void @consume(i32 %value)
+ ret void
+}
+; CHECK: warning: late.cpp:30:7: field is uninitialized when used here
+
+define void @heap_pointer_spill() !dbg !8 {
+entry:
+ %slot = alloca ptr, align 8
+ %allocation = call ptr @malloc(i64 24)
+ %object = getelementptr i8, ptr %allocation, i64 16
+ store ptr %object, ptr %slot, align 8
+ %restored = load ptr, ptr %slot, align 8
+ %value = load i32, ptr %restored, align 4, !dbg !23
+ call void @consume(i32 %value)
+ ret void
+}
+; CHECK: warning: late.cpp:40:7: field is uninitialized when used here
+
+define void @heap_initialized() !dbg !9 {
+entry:
+ %object = call ptr @malloc(i64 8)
+ store i32 1, ptr %object, align 4
+ %value = load i32, ptr %object, align 4, !dbg !24
+ ret void
+}
+; CHECK-NOT: late.cpp:50:7
+
+define void @heap_conditionally_initialized(i1 %condition) !dbg !10 {
+entry:
+ %object = call ptr @malloc(i64 8)
+ br i1 %condition, label %initialize, label %merge
+initialize:
+ store i32 1, ptr %object, align 4
+ br label %merge
+merge:
+ %value = load i32, ptr %object, align 4, !dbg !25
+ call void @consume(i32 %value)
+ ret void
+}
+; CHECK: warning: late.cpp:60:7: field may be uninitialized when used here
+
+define void @heap_unknown_call() !dbg !11 {
+entry:
+ %object = call ptr @malloc(i64 8)
+ call void @opaque(ptr %object)
+ %value = load i32, ptr %object, align 4, !dbg !26
+ ret void
+}
+; CHECK-NOT: late.cpp:70:7
+
+define void @zeroed_allocation() !dbg !12 {
+entry:
+ %object = call ptr @calloc(i64 1, i64 8)
+ %value = load i32, ptr %object, align 4, !dbg !27
+ ret void
+}
+; CHECK-NOT: late.cpp:80:7
+
+define void @not_inlined() !dbg !13 {
+entry:
+ %object = call ptr @malloc(i64 8)
+ %value = load i32, ptr %object, align 4, !dbg !28
+ ret void
+}
+; CHECK-NOT: late.cpp:90:7
+
+define void @unreachable_block() !dbg !15 {
+entry:
+ %object = call ptr @malloc(i64 8)
+ ret void
+
+dead:
+ %value = load i32, ptr %object, align 4, !dbg !40
+ call void @consume(i32 %value)
+ ret void
+}
+; CHECK-NOT: late.cpp:100:7
+
+define void @bitfield_read_modify_write() !dbg !16 {
+entry:
+ %object = call ptr @malloc(i64 8)
+ %old = load i32, ptr %object, align 4, !dbg !41
+ %preserved = and i32 %old, -16
+ %new = or i32 %preserved, 7
+ store i32 %new, ptr %object, align 4
+ ret void
+}
+; CHECK-NOT: late.cpp:110:7
+
+define void @masked_load() !dbg !17 {
+entry:
+ %object = call ptr @malloc(i64 8)
+ %value = load i32, ptr %object, align 4, !dbg !42
+ %selected = select i1 false, i32 %value, i32 0
+ call void @consume(i32 %selected)
+ ret void
+}
+; CHECK-NOT: late.cpp:120:7
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "test", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly)
+!1 = !DIFile(filename: "late.cpp", directory: "")
+!2 = !DISubroutineType(types: !4)
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{}
+!5 = distinct !DISubprogram(name: "stack_uninitialized", scope: !1, file: !1, line: 1, type: !2, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!6 = distinct !DISubprogram(name: "heap_uninitialized", scope: !1, file: !1, line: 2, type: !2, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+!7 = distinct !DISubprogram(name: "heap_sibling_initialized", scope: !1, file: !1, line: 3, type: !2, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !0)
+!8 = distinct !DISubprogram(name: "heap_pointer_spill", scope: !1, file: !1, line: 4, type: !2, scopeLine: 4, spFlags: DISPFlagDefinition, unit: !0)
+!9 = distinct !DISubprogram(name: "heap_initialized", scope: !1, file: !1, line: 5, type: !2, scopeLine: 5, spFlags: DISPFlagDefinition, unit: !0)
+!10 = distinct !DISubprogram(name: "heap_conditionally_initialized", scope: !1, file: !1, line: 6, type: !2, scopeLine: 6, spFlags: DISPFlagDefinition, unit: !0)
+!11 = distinct !DISubprogram(name: "heap_unknown_call", scope: !1, file: !1, line: 7, type: !2, scopeLine: 7, spFlags: DISPFlagDefinition, unit: !0)
+!12 = distinct !DISubprogram(name: "zeroed_allocation", scope: !1, file: !1, line: 8, type: !2, scopeLine: 8, spFlags: DISPFlagDefinition, unit: !0)
+!13 = distinct !DISubprogram(name: "not_inlined", scope: !1, file: !1, line: 9, type: !2, scopeLine: 9, spFlags: DISPFlagDefinition, unit: !0)
+!14 = distinct !DISubprogram(name: "use", scope: !1, file: !1, line: 100, type: !2, scopeLine: 100, spFlags: DISPFlagDefinition, unit: !0)
+!15 = distinct !DISubprogram(name: "unreachable_block", scope: !1, file: !1, line: 10, type: !2, scopeLine: 10, spFlags: DISPFlagDefinition, unit: !0)
+!16 = distinct !DISubprogram(name: "bitfield_read_modify_write", scope: !1, file: !1, line: 11, type: !2, scopeLine: 11, spFlags: DISPFlagDefinition, unit: !0)
+!17 = distinct !DISubprogram(name: "masked_load", scope: !1, file: !1, line: 12, type: !2, scopeLine: 12, spFlags: DISPFlagDefinition, unit: !0)
+!20 = !DILocation(line: 10, column: 7, scope: !14, inlinedAt: !30)
+!21 = !DILocation(line: 20, column: 7, scope: !14, inlinedAt: !31)
+!22 = !DILocation(line: 30, column: 7, scope: !14, inlinedAt: !32)
+!23 = !DILocation(line: 40, column: 7, scope: !14, inlinedAt: !33)
+!24 = !DILocation(line: 50, column: 7, scope: !14, inlinedAt: !34)
+!25 = !DILocation(line: 60, column: 7, scope: !14, inlinedAt: !35)
+!26 = !DILocation(line: 70, column: 7, scope: !14, inlinedAt: !36)
+!27 = !DILocation(line: 80, column: 7, scope: !14, inlinedAt: !37)
+!28 = !DILocation(line: 90, column: 7, scope: !13)
+!30 = !DILocation(line: 1, column: 1, scope: !5)
+!31 = !DILocation(line: 2, column: 1, scope: !6)
+!32 = !DILocation(line: 3, column: 1, scope: !7)
+!33 = !DILocation(line: 4, column: 1, scope: !8)
+!34 = !DILocation(line: 5, column: 1, scope: !9)
+!35 = !DILocation(line: 6, column: 1, scope: !10)
+!36 = !DILocation(line: 7, column: 1, scope: !11)
+!37 = !DILocation(line: 8, column: 1, scope: !12)
+!40 = !DILocation(line: 100, column: 7, scope: !14, inlinedAt: !43)
+!41 = !DILocation(line: 110, column: 7, scope: !14, inlinedAt: !44)
+!42 = !DILocation(line: 120, column: 7, scope: !14, inlinedAt: !45)
+!43 = !DILocation(line: 10, column: 1, scope: !15)
+!44 = !DILocation(line: 11, column: 1, scope: !16)
+!45 = !DILocation(line: 12, column: 1, scope: !17)
More information about the llvm-commits
mailing list