logan-5 created this revision.
logan-5 added reviewers: erik.pilkington, rsmith, arphaman.
logan-5 added a project: clang.
logan-5 requested review of this revision.
Herald added a subscriber: cfe-commits.

Always search the full function scope context if a potential availability 
violation is encountered. This fixes both 
https://bugs.llvm.org/show_bug.cgi?id=50309 and 
https://bugs.llvm.org/show_bug.cgi?id=50310.

Previously, lambdas inside functions would mark their own bodies for later 
analysis when encountering a potentially unavailable decl, without taking into 
consideration that the entire lambda itself might be correctly guarded inside 
an `@available` check. The same applied to inner class member functions. Blocks 
happened to work as expected already, since `Sema::getEnclosingFunction()` 
skips through block scopes.

This patch instead simply and conservatively marks the entire outermost 
function scope for search, and removes some special-case logic that prevented 
`DiagnoseUnguardedAvailabilityViolations` from traversing down into lambdas and 
nested functions. This correctly accounts for arbitrarily nested lambdas, inner 
classes, and blocks that may be inside appropriate `@available` checks at any 
ancestor level. It also treats all potential availability violations inside 
functions consistently, without being overly sensitive to the current 
DeclContext, which previously caused issues where e.g. nested struct members 
were warned about twice.

`DiagnoseUnguardedAvailabilityViolations` now has more work to do in some 
cases, particularly in functions with many (possibly deeply) nested lambdas and 
classes, but the big-O is the same, and the simplicity of the approach and the 
fact that it fixes at least two bugs feels like a strong win IMO.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D102338

Files:
  clang/include/clang/Sema/Sema.h
  clang/lib/Sema/SemaAvailability.cpp
  clang/lib/Sema/SemaExpr.cpp
  clang/test/SemaObjC/unguarded-availability.m

Index: clang/test/SemaObjC/unguarded-availability.m
===================================================================
--- clang/test/SemaObjC/unguarded-availability.m
+++ clang/test/SemaObjC/unguarded-availability.m
@@ -125,6 +125,14 @@
   (void) ^{
     func_10_12(); // expected-warning{{'func_10_12' is only available on macOS 10.12 or newer}} expected-note{{@available}}
   };
+
+  if (@available(macos 10.12, *))
+    (void) ^{
+      func_10_12();
+      (void) ^{
+        func_10_12();
+      };
+    };
 }
 
 void test_params(int_10_12 x); // expected-warning {{'int_10_12' is only available on macOS 10.12 or newer}} expected-note{{annotate 'test_params' with an availability attribute to silence this warning}}
@@ -233,6 +241,36 @@
   (void)(^ {
     func_10_12(); // expected-warning{{'func_10_12' is only available on macOS 10.12 or newer}} expected-note{{@available}}
   });
+
+  if (@available(macos 10.12, *)) {
+    void([]() {
+      func_10_12();
+      void([] () {
+        func_10_12();
+      });
+      struct DontWarn {
+        void f() {
+          func_10_12();
+        }
+      };
+    });
+  }
+
+  if (@available(macos 10.12, *)) {
+    struct DontWarn {
+      void f() {
+        func_10_12();
+        void([] () {
+          func_10_12();
+        });
+        struct DontWarn2 {
+          void f() {
+            func_10_12();
+          }
+        };
+      }
+    };
+  }
 }
 
 #endif
@@ -259,9 +297,14 @@
 @end
 
 void with_local_struct() {
-  struct local { // expected-note{{annotate 'local' with an availability attribute}}
-    new_int x; // expected-warning{{'new_int' is only available}}
+  struct local { 
+    new_int x; // expected-warning{{'new_int' is only available}} expected-note{{enclose 'new_int' in an @available check}}
   };
+  if (@available(macos 10.12, *)) {
+    struct DontWarn {
+      new_int x;
+    };
+  }
 }
 
 // rdar://33156429:
Index: clang/lib/Sema/SemaExpr.cpp
===================================================================
--- clang/lib/Sema/SemaExpr.cpp
+++ clang/lib/Sema/SemaExpr.cpp
@@ -19653,12 +19653,10 @@
   if (Spec != AvailSpecs.end())
     Version = Spec->getVersion();
 
-  // The use of `@available` in the enclosing function should be analyzed to
+  // The use of `@available` in the enclosing context should be analyzed to
   // warn when it's used inappropriately (i.e. not if(@available)).
-  if (getCurFunctionOrMethodDecl())
-    getEnclosingFunction()->HasPotentialAvailabilityViolations = true;
-  else if (getCurBlock() || getCurLambda())
-    getCurFunction()->HasPotentialAvailabilityViolations = true;
+  if (FunctionScopeInfo *Context = getCurFunctionAvailabilityContext())
+    Context->HasPotentialAvailabilityViolations = true;
 
   return new (Context)
       ObjCAvailabilityCheckExpr(Version, AtLoc, RParen, Context.BoolTy);
Index: clang/lib/Sema/SemaAvailability.cpp
===================================================================
--- clang/lib/Sema/SemaAvailability.cpp
+++ clang/lib/Sema/SemaAvailability.cpp
@@ -666,13 +666,6 @@
         SemaRef.Context.getTargetInfo().getPlatformMinVersion());
   }
 
-  bool TraverseDecl(Decl *D) {
-    // Avoid visiting nested functions to prevent duplicate warnings.
-    if (!D || isa<FunctionDecl>(D))
-      return true;
-    return Base::TraverseDecl(D);
-  }
-
   bool TraverseStmt(Stmt *S) {
     if (!S)
       return true;
@@ -686,8 +679,6 @@
 
   bool TraverseIfStmt(IfStmt *If);
 
-  bool TraverseLambdaExpr(LambdaExpr *E) { return true; }
-
   // for 'case X:' statements, don't bother looking at the 'X'; it can't lead
   // to any useful diagnostics.
   bool TraverseCaseStmt(CaseStmt *CS) { return TraverseStmt(CS->getSubStmt()); }
@@ -919,6 +910,17 @@
   DiagnoseUnguardedAvailability(*this, D).IssueDiagnostics(Body);
 }
 
+FunctionScopeInfo *Sema::getCurFunctionAvailabilityContext() {
+  if (FunctionScopes.empty())
+    return nullptr;
+
+  // Conservatively search the entire current function scope context for
+  // availability violations. This ensures we always correctly analyze nested
+  // classes, blocks, lambdas, etc. that may or may not be inside if(@available)
+  // checks themselves.
+  return FunctionScopes.front();
+}
+
 void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D,
                                       ArrayRef<SourceLocation> Locs,
                                       const ObjCInterfaceDecl *UnknownObjCClass,
@@ -941,11 +943,8 @@
     // We need to know the @available context in the current function to
     // diagnose this use, let DiagnoseUnguardedAvailabilityViolations do that
     // when we're done parsing the current function.
-    if (getCurFunctionOrMethodDecl()) {
-      getEnclosingFunction()->HasPotentialAvailabilityViolations = true;
-      return;
-    } else if (getCurBlock() || getCurLambda()) {
-      getCurFunction()->HasPotentialAvailabilityViolations = true;
+    if (FunctionScopeInfo *Context = getCurFunctionAvailabilityContext()) {
+      Context->HasPotentialAvailabilityViolations = true;
       return;
     }
   }
Index: clang/include/clang/Sema/Sema.h
===================================================================
--- clang/include/clang/Sema/Sema.h
+++ clang/include/clang/Sema/Sema.h
@@ -1882,6 +1882,10 @@
   /// Retrieve the current captured region, if any.
   sema::CapturedRegionScopeInfo *getCurCapturedRegion();
 
+  /// Retrieve the current function, if any, that should be analyzed for
+  /// potential availability violations.
+  sema::FunctionScopeInfo *getCurFunctionAvailabilityContext();
+
   /// WeakTopLevelDeclDecls - access to \#pragma weak-generated Decls
   SmallVectorImpl<Decl *> &WeakTopLevelDecls() { return WeakTopLevelDecl; }
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to