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 <[email protected]> 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@+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@+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@+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@+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@+1 {{field is uninitialized when used here}} + consume(c.i); +} + +struct B { + int i; + int j; +}; + +struct F { + int padding; + B b; +}; + +// warn-warning@+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@+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@+2 4 {{field is uninitialized when used here}} + // maybe-warning@+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 } + +@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) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
