Skip to content

[Clang] [Sema] Handle placeholders in '.*' expressions #83103

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 5 commits into from
Feb 27, 2024

Conversation

Sirraide
Copy link
Member

When analysing whether we should handle a binary expression as an overloaded operator call or a builtin operator, we were calling checkPlaceholderForOverload(), which takes care of any placeholders that are not overload sets—which would usually make sense since those need to be handled as part of overload resolution.

Unfortunately, we were also doing that for .*, which is not overloadable, and then proceeding to create a builtin operator anyway, which would crash if the RHS happened to be an unresolved overload set (due hitting an assertion in CreateBuiltinBinOp()—specifically, in one of its callees—in the .* case that makes sure its arguments aren’t placeholders).

This pr instead makes it so we check for all placeholders early if the operator is .*.

It’s worth noting that,

  1. In the .* case, we now additionally also check for any placeholders (not just non-overload-sets) in the LHS; this shouldn’t make a difference, however—at least I couldn’t think of a way to trigger the assertion with an overload set as the LHS of .*; it is worth noting that the assertion in question would also complain if the LHS happened to be of placeholder type, though.
  2. There is another case in which we also don’t perform overload resolution—namely = if the LHS is not of class or enumeration type after handling non-overload-set placeholders—as in the .* case, but similarly to 1., I first couldn’t think of a way of getting this case to crash, and secondly, CreateBuiltinBinOp() doesn’t seem to care about placeholders in the LHS or RHS in the = case (from what I can tell, it, or rather one of its callees, only checks that the LHS is not a pseudo-object type, but those will have already been handled by the call to checkPlaceholderForOverload() by the time we get to this function), so I don’t think this case suffers from the same problem.

This fixes #53815.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Feb 27, 2024
@llvmbot
Copy link
Member

llvmbot commented Feb 27, 2024

@llvm/pr-subscribers-clang

Author: None (Sirraide)

Changes

When analysing whether we should handle a binary expression as an overloaded operator call or a builtin operator, we were calling checkPlaceholderForOverload(), which takes care of any placeholders that are not overload sets—which would usually make sense since those need to be handled as part of overload resolution.

Unfortunately, we were also doing that for .*, which is not overloadable, and then proceeding to create a builtin operator anyway, which would crash if the RHS happened to be an unresolved overload set (due hitting an assertion in CreateBuiltinBinOp()—specifically, in one of its callees—in the .* case that makes sure its arguments aren’t placeholders).

This pr instead makes it so we check for all placeholders early if the operator is .*.

It’s worth noting that,

  1. In the .* case, we now additionally also check for any placeholders (not just non-overload-sets) in the LHS; this shouldn’t make a difference, however—at least I couldn’t think of a way to trigger the assertion with an overload set as the LHS of .*; it is worth noting that the assertion in question would also complain if the LHS happened to be of placeholder type, though.
  2. There is another case in which we also don’t perform overload resolution—namely = if the LHS is not of class or enumeration type after handling non-overload-set placeholders—as in the .* case, but similarly to 1., I first couldn’t think of a way of getting this case to crash, and secondly, CreateBuiltinBinOp() doesn’t seem to care about placeholders in the LHS or RHS in the = case (from what I can tell, it, or rather one of its callees, only checks that the LHS is not a pseudo-object type, but those will have already been handled by the call to checkPlaceholderForOverload() by the time we get to this function), so I don’t think this case suffers from the same problem.

This fixes #53815.


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

2 Files Affected:

  • (modified) clang/lib/Sema/SemaOverload.cpp (+17-5)
  • (added) clang/test/SemaCXX/gh53815.cpp (+21)
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index c46f6338a5a125..f4b67e7b469418 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -14474,6 +14474,23 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
                                        CurFPFeatureOverrides());
   }
 
+  // If this is the .* operator, which is not overloadable, just
+  // create a built-in binary operator.
+  if (Opc == BO_PtrMemD) {
+    auto CheckPlaceholder = [&](Expr *&Arg) {
+      ExprResult Res = CheckPlaceholderExpr(Arg);
+      if (!Res.isInvalid())
+        Arg = Res.get();
+      return Res.isInvalid();
+    };
+
+    // CreateBuiltinBinOp() doesn't like it if we tell it to create a '.*'
+    // expression that contains placeholders (in either the LHS or RHS).
+    if (CheckPlaceholder(Args[0]) || CheckPlaceholder(Args[1]))
+      return ExprError();
+    return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
+  }
+
   // Always do placeholder-like conversions on the RHS.
   if (checkPlaceholderForOverload(*this, Args[1]))
     return ExprError();
@@ -14493,11 +14510,6 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
   if (Opc == BO_Assign && !Args[0]->getType()->isOverloadableType())
     return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
 
-  // If this is the .* operator, which is not overloadable, just
-  // create a built-in binary operator.
-  if (Opc == BO_PtrMemD)
-    return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
-
   // Build the overload set.
   OverloadCandidateSet CandidateSet(OpLoc, OverloadCandidateSet::CSK_Operator,
                                     OverloadCandidateSet::OperatorRewriteInfo(
diff --git a/clang/test/SemaCXX/gh53815.cpp b/clang/test/SemaCXX/gh53815.cpp
new file mode 100644
index 00000000000000..326c911c7bfaf5
--- /dev/null
+++ b/clang/test/SemaCXX/gh53815.cpp
@@ -0,0 +1,21 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s
+// expected-no-diagnostics
+
+// Check that we don't crash due to forgetting to check for placeholders
+// in the RHS of '.*'.
+
+template <typename Fn>
+static bool has_explicitly_named_overload() {
+  return requires { Fn().*&Fn::operator(); };
+}
+
+int main() {
+  has_explicitly_named_overload<decltype([](auto){})>();
+}
+
+template <typename Fn>
+constexpr bool has_explicitly_named_overload_2() {
+  return requires { Fn().*&Fn::operator(); };
+}
+
+static_assert(!has_explicitly_named_overload_2<decltype([](auto){})>());

@Sirraide
Copy link
Member Author

CC @AaronBallman, @cor3ntin

@Sirraide
Copy link
Member Author

One more thing: this code seems to not crash and issue a diagnostic just fine if we simply remove the assertion, so that would also be an option, but I didn’t simply want to remove an assertion without fully knowing why it’s there, so I’ve gone with this as an alternative for now (it should be noted that the code path here is shared with ->*, which of course is overloadable).

Alternatively, we could also handle placeholders then and there instead of asserting on them. Thinking about this now, I’m not entirely sure what the best solution would be.

@Sirraide
Copy link
Member Author

It seems like the assertion has been in Clang since 2011, and back then, we were checking for placeholders in CreateBuiltinBinOp(), so at that point it made sense, but this check seems to have been removed since then.

@Sirraide
Copy link
Member Author

Yeah, it seems the change that ultimately caused this to break was made in 2011, which moved the handling of placeholders for this code path up into SemaOverload.cpp, and from what I can tell, the case of either operand of .* potentially being an overload set when .* is not overloadable was simply overlooked (i.e. the assert() is there because this case was being handled before, but this commit removed that):
526ab47#diff-3c28567b5e0c77d68f174541a0b77f5a85d093f58b89cd3675ee04a550a44880L7784-L7800

@Sirraide
Copy link
Member Author

In my opinion, we ought to get .* ought of the way early—as I’m currently doing in this pr—as it makes little sense to do overloading-specific placeholder handling on an operator that isn’t even overloadable—we should instead just handle all placeholders immediately.

@Sirraide
Copy link
Member Author

Lastly, it also seems weird to me that a function called CreateOverloadedBinOp() is called to handle .*—an operator that can’t be overloaded—but seeing as this function has apparently been handling this case for over a decade now, I’m probably not going to question this any further.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

Thank you for the fix! Please be sure to add a release note to clang/docs/ReleaseNotes.rst so users know about the fix (and be sure to mention the issue # in the release note).

@Sirraide
Copy link
Member Author

Thank you for the fix! Please be sure to add a release note to clang/docs/ReleaseNotes.rst so users know about the fix (and be sure to mention the issue # in the release note).

Ah yes, I keep forgetting about that

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

LGTM!

@Sirraide
Copy link
Member Author

LGTM!

Thanks. Just so you know, I still don’t have commit access, so you’d have to merge it for me—I should really look into obtaining commit access at this point perhaps

@AaronBallman
Copy link
Collaborator

LGTM!

Thanks. Just so you know, I still don’t have commit access, so you’d have to merge it for me—I should really look into obtaining commit access at this point perhaps

Thank you for mentioning that! It turns out that GitHub is confusing:
Capture

I read that to mean you had commit privs. :-D

I support you getting commit privs, if you'd like to follow the process outlined here: https://llvm.org/docs/DeveloperPolicy.html#obtaining-commit-access but in the meantime, I'll push the button.

@AaronBallman AaronBallman merged commit d23ef9e into llvm:main Feb 27, 2024
@Sirraide Sirraide deleted the 53815 branch February 27, 2024 19:27
@ADKaster
Copy link
Contributor

ADKaster commented Mar 1, 2024

@AaronBallman @Sirraide would this patch be reasonable to backport to the 18.x release branch? It fixes a problem with our application on the FreeBSD-CURRENT branch. SerenityOS/serenity#23365

@AaronBallman
Copy link
Collaborator

@AaronBallman @Sirraide would this patch be reasonable to backport to the 18.x release branch? It fixes a problem with our application on the FreeBSD-CURRENT branch. SerenityOS/serenity#23365

Yeah, I think the changes are sufficiently safe and small enough that we could consider backporting. We have instructions on how to get that going here: https://www.llvm.org/docs/GitHub.html#backporting-fixes-to-the-release-branches

@Sirraide Sirraide added this to the LLVM 18.X Release milestone Mar 4, 2024
@Sirraide
Copy link
Member Author

Sirraide commented Mar 4, 2024

/cherry-pick d23ef9e

@llvmbot
Copy link
Member

llvmbot commented Mar 4, 2024

Failed to cherry-pick: d23ef9e

https://github.com/llvm/llvm-project/actions/runs/8146857249

Please manually backport the fix and push it to your github fork. Once this is done, please create a pull request

Sirraide added a commit to Sirraide/llvm-project that referenced this pull request Mar 4, 2024
When analysing whether we should handle a binary expression as an
overloaded operator call or a builtin operator, we were calling
`checkPlaceholderForOverload()`, which takes care of any placeholders
that are not overload sets—which would usually make sense since those
need to be handled as part of overload resolution.

Unfortunately, we were also doing that for `.*`, which is not
overloadable, and then proceeding to create a builtin operator anyway,
which would crash if the RHS happened to be an unresolved overload set
(due hitting an assertion in `CreateBuiltinBinOp()`—specifically, in one
of its callees—in the `.*` case that makes sure its arguments aren’t
placeholders).

This pr instead makes it so we check for *all* placeholders early if the
operator is `.*`.

It’s worth noting that,
1. In the `.*` case, we now additionally also check for *any*
placeholders (not just non-overload-sets) in the LHS; this shouldn’t
make a difference, however—at least I couldn’t think of a way to trigger
the assertion with an overload set as the LHS of `.*`; it is worth
noting that the assertion in question would also complain if the LHS
happened to be of placeholder type, though.
2. There is another case in which we also don’t perform overload
resolution—namely `=` if the LHS is not of class or enumeration type
after handling non-overload-set placeholders—as in the `.*` case, but
similarly to 1., I first couldn’t think of a way of getting this case to
crash, and secondly, `CreateBuiltinBinOp()` doesn’t seem to care about
placeholders in the LHS or RHS in the `=` case (from what I can tell,
it, or rather one of its callees, only checks that the LHS is not a
pseudo-object type, but those will have already been handled by the call
to `checkPlaceholderForOverload()` by the time we get to this function),
so I don’t think this case suffers from the same problem.

This fixes llvm#53815.

---------

Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
@Sirraide
Copy link
Member Author

Sirraide commented Mar 4, 2024

/cherry-pick Sirraide@74fa05d

@llvmbot
Copy link
Member

llvmbot commented Mar 4, 2024

Failed to cherry-pick: Sirraide@74fa05d

https://github.com/llvm/llvm-project/actions/runs/8147057328

Please manually backport the fix and push it to your github fork. Once this is done, please create a pull request

@Sirraide
Copy link
Member Author

Sirraide commented Mar 4, 2024

Hmm, I’m not sure I’m doing this properly; I’ll try and see if I can open a pr manually.

tstellar pushed a commit that referenced this pull request Mar 13, 2024
When analysing whether we should handle a binary expression as an
overloaded operator call or a builtin operator, we were calling
`checkPlaceholderForOverload()`, which takes care of any placeholders
that are not overload sets—which would usually make sense since those
need to be handled as part of overload resolution.

Unfortunately, we were also doing that for `.*`, which is not
overloadable, and then proceeding to create a builtin operator anyway,
which would crash if the RHS happened to be an unresolved overload set
(due hitting an assertion in `CreateBuiltinBinOp()`—specifically, in one
of its callees—in the `.*` case that makes sure its arguments aren’t
placeholders).

This pr instead makes it so we check for *all* placeholders early if the
operator is `.*`.

It’s worth noting that,
1. In the `.*` case, we now additionally also check for *any*
placeholders (not just non-overload-sets) in the LHS; this shouldn’t
make a difference, however—at least I couldn’t think of a way to trigger
the assertion with an overload set as the LHS of `.*`; it is worth
noting that the assertion in question would also complain if the LHS
happened to be of placeholder type, though.
2. There is another case in which we also don’t perform overload
resolution—namely `=` if the LHS is not of class or enumeration type
after handling non-overload-set placeholders—as in the `.*` case, but
similarly to 1., I first couldn’t think of a way of getting this case to
crash, and secondly, `CreateBuiltinBinOp()` doesn’t seem to care about
placeholders in the LHS or RHS in the `=` case (from what I can tell,
it, or rather one of its callees, only checks that the LHS is not a
pseudo-object type, but those will have already been handled by the call
to `checkPlaceholderForOverload()` by the time we get to this function),
so I don’t think this case suffers from the same problem.

This fixes #53815.

---------

Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
@pointhex pointhex mentioned this pull request May 7, 2024
compnerd pushed a commit to compnerd/llvm-project that referenced this pull request Aug 10, 2024
When analysing whether we should handle a binary expression as an
overloaded operator call or a builtin operator, we were calling
`checkPlaceholderForOverload()`, which takes care of any placeholders
that are not overload sets—which would usually make sense since those
need to be handled as part of overload resolution.

Unfortunately, we were also doing that for `.*`, which is not
overloadable, and then proceeding to create a builtin operator anyway,
which would crash if the RHS happened to be an unresolved overload set
(due hitting an assertion in `CreateBuiltinBinOp()`—specifically, in one
of its callees—in the `.*` case that makes sure its arguments aren’t
placeholders).

This pr instead makes it so we check for *all* placeholders early if the
operator is `.*`.

It’s worth noting that,
1. In the `.*` case, we now additionally also check for *any*
placeholders (not just non-overload-sets) in the LHS; this shouldn’t
make a difference, however—at least I couldn’t think of a way to trigger
the assertion with an overload set as the LHS of `.*`; it is worth
noting that the assertion in question would also complain if the LHS
happened to be of placeholder type, though.
2. There is another case in which we also don’t perform overload
resolution—namely `=` if the LHS is not of class or enumeration type
after handling non-overload-set placeholders—as in the `.*` case, but
similarly to 1., I first couldn’t think of a way of getting this case to
crash, and secondly, `CreateBuiltinBinOp()` doesn’t seem to care about
placeholders in the LHS or RHS in the `=` case (from what I can tell,
it, or rather one of its callees, only checks that the LHS is not a
pseudo-object type, but those will have already been handled by the call
to `checkPlaceholderForOverload()` by the time we get to this function),
so I don’t think this case suffers from the same problem.

This fixes llvm#53815.

---------

Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
(cherry picked from commit d23ef9e)
compnerd added a commit to swiftlang/llvm-project that referenced this pull request Aug 12, 2024
[Clang] [Sema] Handle placeholders in '.*' expressions (llvm#83103)
ADKaster pushed a commit to ADKaster/llvm-project that referenced this pull request Aug 19, 2024
When analysing whether we should handle a binary expression as an
overloaded operator call or a builtin operator, we were calling
`checkPlaceholderForOverload()`, which takes care of any placeholders
that are not overload sets—which would usually make sense since those
need to be handled as part of overload resolution.

Unfortunately, we were also doing that for `.*`, which is not
overloadable, and then proceeding to create a builtin operator anyway,
which would crash if the RHS happened to be an unresolved overload set
(due hitting an assertion in `CreateBuiltinBinOp()`—specifically, in one
of its callees—in the `.*` case that makes sure its arguments aren’t
placeholders).

This pr instead makes it so we check for *all* placeholders early if the
operator is `.*`.

It’s worth noting that,
1. In the `.*` case, we now additionally also check for *any*
placeholders (not just non-overload-sets) in the LHS; this shouldn’t
make a difference, however—at least I couldn’t think of a way to trigger
the assertion with an overload set as the LHS of `.*`; it is worth
noting that the assertion in question would also complain if the LHS
happened to be of placeholder type, though.
2. There is another case in which we also don’t perform overload
resolution—namely `=` if the LHS is not of class or enumeration type
after handling non-overload-set placeholders—as in the `.*` case, but
similarly to 1., I first couldn’t think of a way of getting this case to
crash, and secondly, `CreateBuiltinBinOp()` doesn’t seem to care about
placeholders in the LHS or RHS in the `=` case (from what I can tell,
it, or rather one of its callees, only checks that the LHS is not a
pseudo-object type, but those will have already been handled by the call
to `checkPlaceholderForOverload()` by the time we get to this function),
so I don’t think this case suffers from the same problem.

This fixes llvm#53815.

---------

Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
(cherry picked from commit d23ef9e)
DougGregor added a commit to swiftlang/llvm-project that referenced this pull request Aug 20, 2024
🍒 [Clang] [Sema] Handle placeholders in '.*' expressions (llvm#83103)
xgupta pushed a commit to xgupta/llvm-project that referenced this pull request Sep 9, 2024
When analysing whether we should handle a binary expression as an
overloaded operator call or a builtin operator, we were calling
`checkPlaceholderForOverload()`, which takes care of any placeholders
that are not overload sets—which would usually make sense since those
need to be handled as part of overload resolution.

Unfortunately, we were also doing that for `.*`, which is not
overloadable, and then proceeding to create a builtin operator anyway,
which would crash if the RHS happened to be an unresolved overload set
(due hitting an assertion in `CreateBuiltinBinOp()`—specifically, in one
of its callees—in the `.*` case that makes sure its arguments aren’t
placeholders).

This pr instead makes it so we check for *all* placeholders early if the
operator is `.*`.

It’s worth noting that,
1. In the `.*` case, we now additionally also check for *any*
placeholders (not just non-overload-sets) in the LHS; this shouldn’t
make a difference, however—at least I couldn’t think of a way to trigger
the assertion with an overload set as the LHS of `.*`; it is worth
noting that the assertion in question would also complain if the LHS
happened to be of placeholder type, though.
2. There is another case in which we also don’t perform overload
resolution—namely `=` if the LHS is not of class or enumeration type
after handling non-overload-set placeholders—as in the `.*` case, but
similarly to 1., I first couldn’t think of a way of getting this case to
crash, and secondly, `CreateBuiltinBinOp()` doesn’t seem to care about
placeholders in the LHS or RHS in the `=` case (from what I can tell,
it, or rather one of its callees, only checks that the LHS is not a
pseudo-object type, but those will have already been handled by the call
to `checkPlaceholderForOverload()` by the time we get to this function),
so I don’t think this case suffers from the same problem.

This fixes llvm#53815.

---------

Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
xgupta pushed a commit to xgupta/llvm-project that referenced this pull request Oct 10, 2024
When analysing whether we should handle a binary expression as an
overloaded operator call or a builtin operator, we were calling
`checkPlaceholderForOverload()`, which takes care of any placeholders
that are not overload sets—which would usually make sense since those
need to be handled as part of overload resolution.

Unfortunately, we were also doing that for `.*`, which is not
overloadable, and then proceeding to create a builtin operator anyway,
which would crash if the RHS happened to be an unresolved overload set
(due hitting an assertion in `CreateBuiltinBinOp()`—specifically, in one
of its callees—in the `.*` case that makes sure its arguments aren’t
placeholders).

This pr instead makes it so we check for *all* placeholders early if the
operator is `.*`.

It’s worth noting that,
1. In the `.*` case, we now additionally also check for *any*
placeholders (not just non-overload-sets) in the LHS; this shouldn’t
make a difference, however—at least I couldn’t think of a way to trigger
the assertion with an overload set as the LHS of `.*`; it is worth
noting that the assertion in question would also complain if the LHS
happened to be of placeholder type, though.
2. There is another case in which we also don’t perform overload
resolution—namely `=` if the LHS is not of class or enumeration type
after handling non-overload-set placeholders—as in the `.*` case, but
similarly to 1., I first couldn’t think of a way of getting this case to
crash, and secondly, `CreateBuiltinBinOp()` doesn’t seem to care about
placeholders in the LHS or RHS in the `=` case (from what I can tell,
it, or rather one of its callees, only checks that the LHS is not a
pseudo-object type, but those will have already been handled by the call
to `checkPlaceholderForOverload()` by the time we get to this function),
so I don’t think this case suffers from the same problem.

This fixes llvm#53815.

---------

Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
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 release:cherry-pick-failed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Clang] ICE in CheckPointerToMemberOperands passing decltype of lambda
4 participants