Skip to content

[Sema] Support negation/parens with __builtin_available #111439

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 9, 2024

Conversation

gburgessiv
Copy link
Member

At present, __builtin_available is really restrictive with its use. Overall, this seems like a good thing, since the analyses behind it are not very expensive.

That said, it's very straightforward to support these two cases:

if ((__builtin_available(foo, *))) {
  // ...
}

and

if (!__builtin_available(foo, *)) {
  // ...
} else {
  // ...
}

Seems nice to do so.

At present, `__builtin_available` is really restrictive with its use.
Overall, this seems like a good thing, since the analyses behind it are
not very expensive.

That said, it's very straightforward to support these two cases:

```
if ((__builtin_available(foo, *))) {
  // ...
}
```

and

```
if (!__builtin_available(foo, *)) {
  // ...
} else {
  // ...
}
```

Seems nice to do so.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Oct 7, 2024
@llvmbot
Copy link
Member

llvmbot commented Oct 7, 2024

@llvm/pr-subscribers-clang

Author: George Burgess IV (gburgessiv)

Changes

At present, __builtin_available is really restrictive with its use. Overall, this seems like a good thing, since the analyses behind it are not very expensive.

That said, it's very straightforward to support these two cases:

if ((__builtin_available(foo, *))) {
  // ...
}

and

if (!__builtin_available(foo, *)) {
  // ...
} else {
  // ...
}

Seems nice to do so.


Full diff: https://github.com/llvm/llvm-project/pull/111439.diff

2 Files Affected:

  • (modified) clang/lib/Sema/SemaAvailability.cpp (+40-11)
  • (modified) clang/test/Sema/attr-availability.c (+24-1)
diff --git a/clang/lib/Sema/SemaAvailability.cpp b/clang/lib/Sema/SemaAvailability.cpp
index e04cbeec165552..798cabaa31a476 100644
--- a/clang/lib/Sema/SemaAvailability.cpp
+++ b/clang/lib/Sema/SemaAvailability.cpp
@@ -1005,25 +1005,54 @@ bool DiagnoseUnguardedAvailability::VisitTypeLoc(TypeLoc Ty) {
   return true;
 }
 
+struct ExtractedAvailabilityExpr {
+  const ObjCAvailabilityCheckExpr *E = nullptr;
+  bool isNegated = false;
+};
+
+ExtractedAvailabilityExpr extractAvailabilityExpr(const Expr *IfCond) {
+  const auto *E = IfCond;
+  bool IsNegated = false;
+  while (true) {
+    E = E->IgnoreParens();
+    if (const auto *AE = dyn_cast<ObjCAvailabilityCheckExpr>(E)) {
+      return ExtractedAvailabilityExpr{AE, IsNegated};
+    }
+
+    const auto *UO = dyn_cast<UnaryOperator>(E);
+    if (!UO || UO->getOpcode() != UO_LNot) {
+      return ExtractedAvailabilityExpr{};
+    }
+    E = UO->getSubExpr();
+    IsNegated = !IsNegated;
+  }
+}
+
 bool DiagnoseUnguardedAvailability::TraverseIfStmt(IfStmt *If) {
-  VersionTuple CondVersion;
-  if (auto *E = dyn_cast<ObjCAvailabilityCheckExpr>(If->getCond())) {
-    CondVersion = E->getVersion();
-
-    // If we're using the '*' case here or if this check is redundant, then we
-    // use the enclosing version to check both branches.
-    if (CondVersion.empty() || CondVersion <= AvailabilityStack.back())
-      return TraverseStmt(If->getThen()) && TraverseStmt(If->getElse());
-  } else {
+  ExtractedAvailabilityExpr IfCond = extractAvailabilityExpr(If->getCond());
+  if (!IfCond.E) {
     // This isn't an availability checking 'if', we can just continue.
     return Base::TraverseIfStmt(If);
   }
 
+  VersionTuple CondVersion = IfCond.E->getVersion();
+  // If we're using the '*' case here or if this check is redundant, then we
+  // use the enclosing version to check both branches.
+  if (CondVersion.empty() || CondVersion <= AvailabilityStack.back()) {
+    return TraverseStmt(If->getThen()) && TraverseStmt(If->getElse());
+  }
+
+  auto *Guarded = If->getThen();
+  auto *Unguarded = If->getElse();
+  if (IfCond.isNegated) {
+    std::swap(Guarded, Unguarded);
+  }
+
   AvailabilityStack.push_back(CondVersion);
-  bool ShouldContinue = TraverseStmt(If->getThen());
+  bool ShouldContinue = TraverseStmt(Guarded);
   AvailabilityStack.pop_back();
 
-  return ShouldContinue && TraverseStmt(If->getElse());
+  return ShouldContinue && TraverseStmt(Unguarded);
 }
 
 } // end anonymous namespace
diff --git a/clang/test/Sema/attr-availability.c b/clang/test/Sema/attr-availability.c
index a5cc602a8fa9d4..a496c5271f2a3d 100644
--- a/clang/test/Sema/attr-availability.c
+++ b/clang/test/Sema/attr-availability.c
@@ -40,7 +40,7 @@ void test_10095131(void) {
 #ifdef WARN_PARTIAL
 // FIXME: This note should point to the declaration with the availability
 // attribute.
-// expected-note@+2 {{'PartiallyAvailable' has been marked as being introduced in macOS 10.8 here, but the deployment target is macOS 10.5}}
+// expected-note@+2 5 {{'PartiallyAvailable' has been marked as being introduced in macOS 10.8 here, but the deployment target is macOS 10.5}}
 #endif
 extern void PartiallyAvailable(void) ;
 void with_redeclaration(void) {
@@ -53,6 +53,29 @@ void with_redeclaration(void) {
   enum PartialEnum p = kPartialEnumConstant;
 }
 
+#ifdef WARN_PARTIAL
+void conditional_warnings() {
+  if (__builtin_available(macos 10.8, *)) {
+    PartiallyAvailable();
+  } else {
+    PartiallyAvailable(); // expected-warning {{only available on macOS 10.8 or newer}} expected-note {{enclose 'PartiallyAvailable'}}
+  }
+  if (!__builtin_available(macos 10.8, *)) {
+    PartiallyAvailable(); // expected-warning {{only available on macOS 10.8 or newer}} expected-note {{enclose 'PartiallyAvailable'}}
+  } else {
+    PartiallyAvailable();
+  }
+  if (!!!(!__builtin_available(macos 10.8, *))) {
+    PartiallyAvailable();
+  } else {
+    PartiallyAvailable(); // expected-warning {{only available on macOS 10.8 or newer}} expected-note {{enclose 'PartiallyAvailable'}}
+  }
+  if (~__builtin_available(macos 10.8, *)) { // expected-warning {{does not guard availability here}}
+    PartiallyAvailable(); // expected-warning {{only available on macOS 10.8 or newer}} expected-note {{enclose 'PartiallyAvailable'}}
+  }
+}
+#endif
+
 __attribute__((availability(macos, unavailable))) // expected-warning {{attribute 'availability' is ignored}}
 enum {
     NSDataWritingFileProtectionWriteOnly = 0x30000000,

@gburgessiv gburgessiv requested a review from damyanp October 7, 2024 21:10
@gburgessiv
Copy link
Member Author

Hey Damyan, GH suggested you as a reviewer for this. Would you be able to TAL? Happy to find someone else if not :)

@damyanp
Copy link
Contributor

damyanp commented Oct 7, 2024

Hey Damyan, GH suggested you as a reviewer for this. Would you be able to TAL? Happy to find someone else if not :)

I'm not sure why GH would have done that :). I'm afraid I wouldn't be able to give an informed review here. (I might have a quick look anyway).

@gburgessiv
Copy link
Member Author

OK, thanks! I'll try to find someone with more context later today :)

@gburgessiv gburgessiv removed the request for review from damyanp October 8, 2024 13:56
Copy link
Member

@hekota hekota left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@gburgessiv
Copy link
Member Author

(...Or a reviewer will find me - thank you!)

@gburgessiv gburgessiv merged commit 1553cb5 into llvm:main Oct 9, 2024
12 checks passed
@gburgessiv gburgessiv deleted the builtin_available_lnot branch October 9, 2024 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants