瀏覽代碼

Add diagnostics for uninitialized `out` parameters (#5047)

This change adds diagnostic support for uninitialized `out` parameters.
With this change, `out` parameters are treated as uninitiailzed values
until they are initialized and use of the values produdces a diagnostic.

This change is a slightly hacky extension of Clang's uninitiailzed
values analysis which is achieved by adding ParamVarDecls for `out`
parameters to the list of variables to analyize. It also adds dummy
uses of the variables on `return` statements.

It also introduces the maybe_unused attribute, adopting C++ naming and
C++-like behavior.

When applied to an `out` parameter `maybe_unused` results in skipping
uninitialized value analysis on return and function block exits only
if the value is always uninitialized.

If the value is maybe uninitialized or if the value is used explicitly
the warnings are still emitted.
Chris B 2 年之前
父節點
當前提交
1380cf88ed

+ 6 - 0
tools/clang/include/clang/Basic/Attr.td

@@ -917,6 +917,12 @@ def HLSLWaveOpsIncludeHelperLanes : InheritableAttr {
   let Documentation = [Undocumented];
 }
 
+def HLSLMaybeUnused : InheritableAttr {
+  let Spellings = [CXX11<"", "maybe_unused", 2017>];
+  let Subjects = SubjectList<[ParmVar]>;
+  let Documentation = [Undocumented];
+}
+
 // This attribute has no spelling, it is attached to methods by the compiler for
 // methods that require C++ lookup rules.
 def HLSLCXXOverload : InheritableAttr {

+ 1 - 0
tools/clang/include/clang/Basic/DiagnosticGroups.td

@@ -799,4 +799,5 @@ def HLSLPayloadAccessQualifer: DiagGroup<"payload-access-qualifier", [
   ]>;
 def HLSLSemanticIdentifierCollision : DiagGroup<"semantic-identifier-collision">;
 def HLSLStructurizeExitsLifetimeMarkersConflict: DiagGroup<"structurize-exits-lifetime-markers-conflict">;
+def HLSLParameterUsage : DiagGroup<"parameter-usage">;
 // HLSL Change Ends

+ 16 - 0
tools/clang/include/clang/Basic/DiagnosticSemaKinds.td

@@ -1574,6 +1574,22 @@ def warn_sometimes_uninit_var : Warning<
   "its declaration is reached|"
   "%3 is called}2">,
   InGroup<UninitializedSometimes>, DefaultIgnore;
+// HLSL Change Begin - Add warning for uninitialized out param
+// These errors must match the format of the errors above.
+def warn_hlsl_uninit_out_param : Warning<
+  "parameter %0 is uninitialized when %select{used here|returned}1">,
+  InGroup<HLSLParameterUsage>;
+
+def warn_hlsl_sometimes_uninit_out_param : Warning<
+  "parameter %0 is %select{used|returned}1 uninitialized whenever "
+  "%select{'%3' condition is %select{true|false}4|"
+  "'%3' loop %select{is entered|exits because its condition is false}4|"
+  "'%3' loop %select{condition is true|exits because its condition is false}4|"
+  "switch %3 is taken|"
+  "its declaration is reached|"
+  "%3 is called}2">,
+  InGroup<HLSLParameterUsage>;
+// HLSL Change End - Add warning for uninitialized out param
 def warn_maybe_uninit_var : Warning<
   "variable %0 may be uninitialized when "
   "%select{used here|captured by block}1">,

+ 88 - 5
tools/clang/lib/Analysis/UninitializedValues.cpp

@@ -34,6 +34,10 @@ using namespace clang;
 #define DEBUG_LOGGING 0
 
 static bool isTrackedVar(const VarDecl *vd, const DeclContext *dc) {
+  // HLSL Change Begin - Treat `out` parameters as uninitialized values.
+  if (vd->hasAttr<HLSLOutAttr>() && !vd->hasAttr<HLSLInAttr>())
+    return true;
+  // HLSL Change End - Treat `out` parameters as uninitialized values.
   if (vd->isLocalVarDecl() && !vd->hasGlobalStorage() &&
       !vd->isExceptionVariable() && !vd->isInitCapture() &&
       !vd->isImplicit() && vd->getDeclContext() == dc) {
@@ -50,6 +54,10 @@ static bool isTrackedVar(const VarDecl *vd, const DeclContext *dc) {
 namespace {
 class DeclToIndex {
   llvm::DenseMap<const VarDecl *, unsigned> map;
+  
+  // HLSL Change Begin - Treat `out` parameters as uninitialized values.
+  llvm::SmallVector<const VarDecl *, 4> hlslOutParams;
+  // HLSL Change End - Treat `out` parameters as uninitialized values.
 public:
   DeclToIndex() {}
   
@@ -61,6 +69,12 @@ public:
   
   /// Returns the bit vector index for a given declaration.
   Optional<unsigned> getValueIndex(const VarDecl *d) const;
+
+  // HLSL Change Begin - Treat `out` parameters as uninitialized values.
+  const llvm::SmallVector<const VarDecl *, 4> &getHLSLOutParams() {
+    return hlslOutParams;
+  }
+  // HLSL Change End - Treat `out` parameters as uninitialized values.
 };
 }
 
@@ -72,6 +86,11 @@ void DeclToIndex::computeMap(const DeclContext &dc) {
     const VarDecl *vd = *I;
     if (isTrackedVar(vd, &dc))
       map[vd] = count++;
+    // HLSL Change Begin - Treat `out` parameters as uninitialized values.
+    // Keep HLSL parameters in a separate index.
+    if (vd->hasAttr<HLSLOutAttr>() && !vd->hasAttr<HLSLInAttr>())
+      hlslOutParams.push_back(vd);
+    // HLSL Change End - Treat `out` parameters as uninitialized values.
   }
 }
 
@@ -137,6 +156,12 @@ public:
     assert(idx.hasValue());
     return getValueVector(block)[idx.getValue()];
   }
+
+  // HLSL Change Begin - Treat `out` parameters as uninitialized values.
+  const llvm::SmallVector<const VarDecl *, 4> &getHLSLOutParams() {
+    return declToIndex.getHLSLOutParams();
+  }
+  // HLSL Change End - Treat `out` parameters as uninitialized values.
 };  
 } // end anonymous namespace
 
@@ -454,8 +479,23 @@ void ClassifyRefs::VisitCallExpr(CallExpr *CE) {
   // If a value is passed by const pointer or by const reference to a function,
   // we should not assume that it is initialized by the call, and we
   // conservatively do not assume that it is used.
+  unsigned ParamIdx = 0; // HLSL Change
   for (CallExpr::arg_iterator I = CE->arg_begin(), E = CE->arg_end();
-       I != E; ++I) {
+       I != E; ++I, ++ParamIdx) { // HLSL Change - add ParamIdx
+    // HLSL Change Begin - Treat `out` parameters as uninitialized values.
+    if (auto FD = CE->getDirectCallee()) {
+      if (FD->getNumParams() > ParamIdx) {
+        ParmVarDecl *PD = FD->getParamDecl(ParamIdx);
+        bool HasIn = PD->hasAttr<HLSLInAttr>();
+        bool HasOut = PD->hasAttr<HLSLOutAttr>();
+        bool HasInOut = PD->hasAttr<HLSLInOutAttr>();
+        // If we have an in annotation or no annotation (implcit in), this is a
+        // use not an initialization.
+        if(!HasOut || HasIn || HasInOut)
+          classify(*I, Use);
+      }
+    }
+    // HLSL Change End - Treat `out` parameters as uninitialized values.
     if ((*I)->isGLValue()) {
       if ((*I)->getType().isConstQualified())
         classify((*I), Ignore);
@@ -514,6 +554,8 @@ public:
   void VisitDeclStmt(DeclStmt *ds);
   void VisitObjCForCollectionStmt(ObjCForCollectionStmt *FS);
   void VisitObjCMessageExpr(ObjCMessageExpr *ME);
+  void VisitReturnStmt(ReturnStmt *RS); // HLSL Change
+  void HandleHLSLImplicitUse(SourceLocation Loc); // HLSL Change
 
   bool isTrackedVar(const VarDecl *vd) {
     return ::isTrackedVar(vd, cast<DeclContext>(ac.getDecl()));
@@ -794,6 +836,34 @@ void TransferFunctions::VisitObjCMessageExpr(ObjCMessageExpr *ME) {
   }
 }
 
+// HLSL Change Begin - Treat `out` parameters as uninitialized values.
+void TransferFunctions::VisitReturnStmt(ReturnStmt *RS) {
+  // Visit the statment normally first so that it's expression can be processed.
+  VisitStmt(RS);
+  HandleHLSLImplicitUse(RS->getLocStart());
+}
+
+void TransferFunctions::HandleHLSLImplicitUse(SourceLocation Loc) {
+  // Create a dummy use DeclRefExpr for all the `out` params.
+  for (auto *P : vals.getHLSLOutParams()) {
+    Value v = vals[P];
+    if (!isUninitialized(v))
+      continue;
+    // Skip diagnostics for always uninitialized values if they are marked maybe
+    // unused. This allows us to continue emitting other diagnostics for
+    // sometimes uninitialized values.
+    if (P->hasAttr<HLSLMaybeUnusedAttr>() && isAlwaysUninit(v))
+      continue;
+    auto *DRE = DeclRefExpr::Create(
+        P->getASTContext(), NestedNameSpecifierLoc(), SourceLocation(),
+        const_cast<VarDecl *>(P), false,
+        DeclarationNameInfo(P->getDeclName(), Loc),
+        P->getASTContext().VoidTy, ExprValueKind::VK_RValue);
+    reportUse(DRE, P);
+  }
+}
+// HLSL Change End - Treat `out` parameters as uninitialized values.
+
 //------------------------------------------------------------------------====//
 // High-level "driver" logic for uninitialized values analysis.
 //====------------------------------------------------------------------------//
@@ -802,7 +872,8 @@ static bool runOnBlock(const CFGBlock *block, const CFG &cfg,
                        AnalysisDeclContext &ac, CFGBlockValues &vals,
                        const ClassifyRefs &classification,
                        llvm::BitVector &wasAnalyzed,
-                       UninitVariablesHandler &handler) {
+                       UninitVariablesHandler &handler,
+                       const DeclContext &dc) { // HLSL Change - Add dc
   wasAnalyzed[block->getBlockID()] = true;
   vals.resetScratch();
   // Merge in values of predecessor blocks.
@@ -824,6 +895,13 @@ static bool runOnBlock(const CFGBlock *block, const CFG &cfg,
     if (Optional<CFGStmt> cs = I->getAs<CFGStmt>())
       tf.Visit(const_cast<Stmt*>(cs->getStmt()));
   }
+
+  // HLSL Change Begin - Treat `out` parameters as uninitialized values.
+  // If this block has no successors and does not have a terminator stmt which
+  // we would have handled above.
+  if (block->succ_size() == 0 && !block->getTerminator())
+    tf.HandleHLSLImplicitUse(cast<Decl>(&dc)->getBody()->getLocEnd());
+  // HLSL Change End - Treat `out` parameters as uninitialized values.
   return vals.updateValueVectorWithScratch(block);
 }
 
@@ -901,8 +979,10 @@ void clang::runUninitializedVariablesAnalysis(
     PBH.currentBlock = block->getBlockID();
 
     // Did the block change?
-    bool changed = runOnBlock(block, cfg, ac, vals,
-                              classification, wasAnalyzed, PBH);
+    // HLSL Change Begin - Add dc
+    bool changed = runOnBlock(block, cfg, ac, vals, classification, wasAnalyzed,
+                              PBH, dc);
+    // HLSL Change End - Add dc
     ++stats.NumBlockVisits;
     if (changed || !previouslyVisited[block->getBlockID()])
       worklist.enqueueSuccessors(block);    
@@ -916,7 +996,10 @@ void clang::runUninitializedVariablesAnalysis(
   for (CFG::const_iterator BI = cfg.begin(), BE = cfg.end(); BI != BE; ++BI) {
     const CFGBlock *block = *BI;
     if (PBH.hadUse[block->getBlockID()]) {
-      runOnBlock(block, cfg, ac, vals, classification, wasAnalyzed, handler);
+      // HLSL Change Begin - Add dc
+      runOnBlock(block, cfg, ac, vals, classification, wasAnalyzed, handler,
+                 dc);
+      // HLSL Change End - Add dc
       ++stats.NumBlockVisits;
     }
   }

+ 25 - 10
tools/clang/lib/Sema/AnalysisBasedWarnings.cpp

@@ -664,21 +664,28 @@ static void CreateIfFixit(Sema &S, const Stmt *If, const Stmt *Then,
 static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
                           bool IsCapturedByBlock) {
   bool Diagnosed = false;
+  // HLSL Change Start - Generate warnings for uninitialized out params.
+  bool HLSLOutParam = VD->hasAttr<HLSLOutAttr>();
 
   switch (Use.getKind()) {
   case UninitUse::Always:
-    S.Diag(Use.getUser()->getLocStart(), diag::warn_uninit_var)
+    S.Diag(Use.getUser()->getLocStart(), HLSLOutParam
+                                             ? diag::warn_hlsl_uninit_out_param
+                                             : diag::warn_uninit_var)
         << VD->getDeclName() << IsCapturedByBlock
         << Use.getUser()->getSourceRange();
     return;
 
   case UninitUse::AfterDecl:
   case UninitUse::AfterCall:
-    S.Diag(VD->getLocation(), diag::warn_sometimes_uninit_var)
-      << VD->getDeclName() << IsCapturedByBlock
-      << (Use.getKind() == UninitUse::AfterDecl ? 4 : 5)
-      << const_cast<DeclContext*>(VD->getLexicalDeclContext())
-      << VD->getSourceRange();
+    S.Diag(VD->getLocation(), HLSLOutParam
+                                  ? diag::warn_hlsl_sometimes_uninit_out_param
+                                  : diag::warn_sometimes_uninit_var)
+        << VD->getDeclName() << IsCapturedByBlock
+        << (Use.getKind() == UninitUse::AfterDecl ? 4 : 5)
+        << const_cast<DeclContext *>(VD->getLexicalDeclContext())
+        << VD->getSourceRange();
+    // HLSL Change End - Generate warnings for uninitialized out params.
     S.Diag(Use.getUser()->getLocStart(), diag::note_uninit_var_use)
       << IsCapturedByBlock << Use.getUser()->getSourceRange();
     return;
@@ -810,9 +817,13 @@ static void DiagUninitUse(Sema &S, const VarDecl *VD, const UninitUse &Use,
       break;
     }
 
-    S.Diag(Range.getBegin(), diag::warn_sometimes_uninit_var)
-      << VD->getDeclName() << IsCapturedByBlock << DiagKind
-      << Str << I->Output << Range;
+    // HLSL Change Start - Generate warnings for uninitialized out params.
+    S.Diag(Range.getBegin(), HLSLOutParam
+                                 ? diag::warn_hlsl_sometimes_uninit_out_param
+                                 : diag::warn_sometimes_uninit_var)
+        << VD->getDeclName() << IsCapturedByBlock << DiagKind << Str
+        << I->Output << Range;
+    // HLSL Change End - Generate warnings for uninitialized out params.
     S.Diag(User->getLocStart(), diag::note_uninit_var_use)
       << IsCapturedByBlock << User->getSourceRange();
     if (RemoveDiagKind != -1)
@@ -2006,9 +2017,13 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P,
     Analyzer.run(AC);
   }
 
+  // HLSL Change Start - Generate warnings for uninitialized out params.
   if (!Diags.isIgnored(diag::warn_uninit_var, D->getLocStart()) ||
       !Diags.isIgnored(diag::warn_sometimes_uninit_var, D->getLocStart()) ||
-      !Diags.isIgnored(diag::warn_maybe_uninit_var, D->getLocStart())) {
+      !Diags.isIgnored(diag::warn_maybe_uninit_var, D->getLocStart()) ||
+      !Diags.isIgnored(diag::warn_hlsl_uninit_out_param, D->getLocStart()) ||
+      !Diags.isIgnored(diag::warn_hlsl_sometimes_uninit_out_param, D->getLocStart())) {
+    // HLSL Change End  - Generate warnings for uninitialized out params.
     if (CFG *cfg = AC.getCFG()) {
       UninitValsDiagReporter reporter(S);
       UninitVariablesAnalysisStats stats;

+ 4 - 0
tools/clang/lib/Sema/SemaHLSL.cpp

@@ -12181,6 +12181,10 @@ void hlsl::HandleDeclAttributeForHLSL(Sema &S, Decl *D, const AttributeList &A,
     declAttr = ::new (S.Context) HLSLInOutAttr(A.getRange(), S.Context,
       A.getAttributeSpellingListIndex());
     break;
+  case AttributeList::AT_HLSLMaybeUnused:
+    declAttr = ::new (S.Context) HLSLMaybeUnusedAttr(A.getRange(), S.Context,
+      A.getAttributeSpellingListIndex());
+    break;
 
   case AttributeList::AT_HLSLNoInterpolation:
     declAttr = ::new (S.Context) HLSLNoInterpolationAttr(A.getRange(), S.Context,

+ 1 - 1
tools/clang/test/HLSL/cxx11-attributes.hlsl

@@ -13,7 +13,7 @@ struct S {
 [[vk::binding(5, 3)]] // expected-warning {{'binding' attribute ignored}}
 ConstantBuffer<S> myConstantBuffer;
 
-[[maybe_unused]] // expected-warning {{unknown attribute 'maybe_unused' ignored}}
+[[maybe_unused]] // expected-warning {{'maybe_unused' attribute only applies to parameters}}
 float main([[scope::attr(0, "str")]] // expected-warning {{unknown attribute 'attr' ignored}}
            float m: B,
            S s) : C {

+ 1 - 1
tools/clang/test/HLSL/mintypes-promotion-warnings.hlsl

@@ -6,4 +6,4 @@ void main(
   min10float in_f10, // expected-warning {{'min10float' is promoted to 'min16float'}} fxc-pass {{}}
   min12int in_i12, // expected-warning {{'min12int' is promoted to 'min16int'}} fxc-pass {{}}
   out min10float out_f10, // expected-warning {{'min10float' is promoted to 'min16float'}} fxc-pass {{}}
-  out min12int out_i12) {} // expected-warning {{'min12int' is promoted to 'min16int'}} fxc-pass {{}}
+  out min12int out_i12) {} // expected-warning {{'min12int' is promoted to 'min16int'}} fxc-pass {{}} expected-warning{{parameter 'out_f10' is uninitialized when used here}} expected-warning{{parameter 'out_i12' is uninitialized when used here}}

+ 1 - 1
tools/clang/test/HLSL/more-operators.hlsl

@@ -42,7 +42,7 @@ float  i11_to_float(int1x1 v) { return v; }
 void into_out_i(out int i) { i = g_i11; }
 void into_out_i3(out int3 i3) { i3 = int3(1, 2, 3); } // expected-note {{candidate function}} expected-note {{passing argument to parameter 'i3' here}} fxc-pass {{}}
 void into_out_f(out float i) { i = g_i11; }
-void into_out_f3_s(out f3_s i) { }
+void into_out_f3_s(out f3_s i) { } // expected-warning{{parameter 'i' is uninitialized when used here}} expected-note{{initialize the variable 'i' to silence this warning}}
 void into_out_ss(out SamplerState ss) { ss = g_SamplerState; }
 
 float4 plain(float4 param4 /* : FOO */) /*: FOO */{

+ 176 - 0
tools/clang/test/HLSL/out-param-diagnostics.hlsl

@@ -0,0 +1,176 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+void UnusedEmpty(out int Val) {} // expected-warning{{parameter 'Val' is uninitialized when used here}} expected-note{{variable 'Val' is declared here}}
+
+// Neither of these should warn
+void UnusedInAndOut(in out int Val) {}
+void UnusedInOut(inout int Val) {}
+
+
+int Returned(out int Val) { // expected-note{{variable 'Val' is declared here}}
+  return Val; // expected-warning{{parameter 'Val' is uninitialized when used here}}
+}
+
+int ReturnedPassthrough(int Cond, out int Val) { // expected-note{{variable 'Val' is declared here}}
+  if (Cond % 3)
+    return Returned(Val);
+  else if (Cond % 2)
+    return Returned(Val);
+  return Val; // expected-warning{{parameter 'Val' is uninitialized when used here}}
+}
+
+// No disagnostic expected here because all paths to the exit return, and they
+// all initialize Val.
+int AllPathsReturn(int Cond, out int Val) {
+  if (Cond % 3)
+    return Returned(Val);
+  else
+    return Returned(Val);
+}
+
+void AllPathsReturnSwitch(int Cond, out int Val) {
+  switch(Cond % 3) {
+    case 0:
+      Val = 0;
+      return;
+    case 1:
+      Val = 1;
+      return;
+    case 2:
+      Val = 2;
+      return;
+  }
+}
+
+int ReturnedMaybePassthrough(int Cond, out int Val) { // expected-note{{variable 'Val' is declared here}}
+  if (Cond % 3)
+    UnusedEmpty(Val);
+  else if (Cond % 2) // expected-warning{{parameter 'Val' is used uninitialized whenever 'if' condition is false}} expected-note{{remove the 'if' if its condition is always true}}
+    UnusedEmpty(Val);
+  return Val; // expected-note{{uninitialized use occurs here}}
+}
+
+void SomePathsReturnSwitch(int Cond, out int Val) { // expected-note{{variable 'Val' is declared here}}
+  switch(Cond) {
+    case 0:
+      Val = 0;
+      return;
+    default: // expected-warning{{parameter 'Val' is used uninitialized whenever switch default is taken}}
+      break;
+  }
+}  // expected-note{{uninitialized use occurs here}}
+
+void SomePathsReturnSwitch2(int Cond, out int Val) { // expected-note{{variable 'Val' is declared here}}
+  switch(Cond) {
+    case 0:
+      Val = 0;
+      return;
+    case 1:
+      return; // expected-warning{{parameter 'Val' is uninitialized when used here}}
+    default:
+      Val = 0;
+      break;
+  }
+}
+
+int Dbl(int V) {
+  return V + V;
+}
+
+int UsedAsIn(out int Num) { // expected-note{{variable 'Num' is declared here}}
+  return Dbl(Num); // expected-warning{{parameter 'Num' is uninitialized when used here}}
+}
+
+// No diagnostic for this one either!
+int GetOne(out int O) {
+  return O = 1;
+}
+
+// Both of these functions should not produce diagnostics because inout and in +
+// out specifiers are ignored by the analysis.
+void DblInPlace(in out int V) {
+  V += V;
+}
+
+void DblInPlace2(inout int V) {
+  V += V;
+}
+
+void MaybePassthrough(int Cond, out int Val) { // expected-note{{variable 'Val' is declared here}}
+  if (Cond % 3)
+    UnusedEmpty(Val);
+  else if (Cond % 2) // expected-warning{{parameter 'Val' is used uninitialized whenever 'if' condition is false}} expected-note{{remove the 'if' if its condition is always true}}
+    UnusedEmpty(Val);
+} // expected-note{{uninitialized use occurs here}}
+
+void EarlyOut(int Cond, out int Val) { // expected-note{{variable 'Val' is declared here}}
+  if (Cond % 11)
+    return; // expected-warning {{parameter 'Val' is uninitialized when used here}}
+  Val = 1;
+}
+
+// In parameters are read from, so they should be treated as uninitialized
+// values. Out parameters are written to but not read from, so they are
+// initializers.
+
+void SomethingCalledOut(out int V) {
+  V = 1;
+}
+
+int Something1(out int Num) {
+  // no diagnostic since this writes Num but doesn't read it
+  SomethingCalledOut(Num);
+  return Num;
+}
+
+
+void SomethingCalledInAndOut(in out int V) {
+  V = 1;
+}
+
+int Something2(out int Num) { // expected-note {{variable 'Num' is declared here}}
+  SomethingCalledInAndOut(Num); // expected-warning {{parameter 'Num' is uninitialized when used here}}
+  return Num;
+}
+
+void SomethingCalledInOut(inout int V) {
+  V = 1;
+}
+
+int Something3(out int Num) { // expected-note {{variable 'Num' is declared here}}
+  SomethingCalledInOut(Num); // expected-warning {{parameter 'Num' is uninitialized when used here}}
+  return Num;
+}
+
+struct SomeObj {
+  int Integer;
+  int Float;
+};
+
+void UnusedObjectOut(out SomeObj V) {} // expected-warning {{parameter 'V' is uninitialized when used here}} expected-note {{initialize the variable 'V' to silence this warning}}
+
+// We don't have per-field analysis, so this will count as an initialization if
+// any field is initiailzed.
+void SomethingObjectOut(out SomeObj V) {
+  V.Integer = 1;
+}
+
+// This test case is copied from tools/clang/test/HLSL/functions.hlsl to verify
+// that the analysis does produce a diagnostic for this case. Because
+// analysis-based warnings require valid ASTs, they don't run in the presence of
+// errors. As a result that test doesn't produce these diagnostics.
+void fn_uint_oload3(uint u) { }
+void fn_uint_oload3(inout uint u) { }
+void fn_uint_oload3(out uint u) { } // expected-warning {{parameter 'u' is uninitialized when used here}} expected-note{{variable 'u' is declared here}}
+
+// Verify attribute annotation to opt out of uninitialized parameter analysis.
+void UnusedOutput([maybe_unused] out int Val) {}
+
+void UsedMaybeOutput([maybe_unused] out int Val) { // expected-note{{variable 'Val' is declared here}}
+  Val += Val; // expected-warning{{parameter 'Val' is uninitialized when used here}}
+}
+
+void MaybeUsedMaybeUnused([maybe_unused] out int Val, int Cnt) { // expected-note{{variable 'Val' is declared here}}
+  if (Cnt % 2) // expected-warning{{parameter 'Val' is used uninitialized whenever 'if' condition is fals}} expected-note{{remove the 'if' if its condition is always true}}
+    Val = 1;
+} // expected-note{{uninitialized use occurs here}}

+ 10 - 0
tools/clang/test/HLSLFileCheck/hlsl/functions/arguments/maybe_unused_out.hlsl

@@ -0,0 +1,10 @@
+// RUN: %dxc -T lib_6_4 %s -ast-dump | FileCheck %s
+// Verify attribute annotation to opt out of uninitialized parameter analysis.
+
+void UnusedOutput([maybe_unused] out int Val) {}
+
+
+// CHECK: FunctionDecl {{.*}} UnusedOutput 'void (int &__restrict)'
+// CHECK-NEXT: ParmVarDecl {{0x[0-9a-fA-F]+}} <col:34, col:42> col:42 Val 'int &__restrict'
+// CHECK-NEXT: HLSLOutAttr {{0x[0-9a-fA-F]+}} <col:34>
+// CHECK-NEXT: HLSLMaybeUnusedAttr {{0x[0-9a-fA-F]+}} <col:20>

+ 16 - 0
tools/clang/test/HLSLFileCheck/hlsl/functions/arguments/out-param-diagnostics-disabled.hlsl

@@ -0,0 +1,16 @@
+// RUN: %dxc -T lib_6_4 %s -Wno-parameter-usage | FileCheck %s
+
+// FIXME: This should be a `-verify` test but can't be until we move to lit
+
+// CHECK-NOT: warning
+// CHECK: target datalayout
+
+void UnusedEmpty(out int Val) {}
+
+int ReturnedMaybePassthrough(int Cond, out int Val) {
+  if (Cond % 3)
+    UnusedEmpty(Val);
+  else if (Cond % 2)
+    UnusedEmpty(Val);
+  return Val;
+}

+ 5 - 0
tools/clang/unittests/HLSL/VerifierTest.cpp

@@ -60,6 +60,7 @@ public:
   TEST_METHOD(RunMintypesPromotionWarnings)
   TEST_METHOD(RunMoreOperators)
   TEST_METHOD(RunObjectOperators)
+  TEST_METHOD(RunOutParamDiags)
   TEST_METHOD(RunPackReg)
   TEST_METHOD(RunRayTracings)
   TEST_METHOD(RunScalarAssignments)
@@ -278,6 +279,10 @@ TEST_F(VerifierTest, RunObjectOperators) {
   CheckVerifiesHLSL(L"object-operators.hlsl");
 }
 
+TEST_F(VerifierTest, RunOutParamDiags) {
+  CheckVerifiesHLSL(L"out-param-diagnostics.hlsl");
+}
+
 TEST_F(VerifierTest, RunPackReg) {
   CheckVerifiesHLSL(L"packreg.hlsl");
 }