This is an archive of the discontinued LLVM Phabricator instance.

[clang] Do not crash on use of a variadic overloaded operator
ClosedPublic

Authored by Fznamznon on Jul 25 2023, 8:18 AM.

Diff Detail

Event Timeline

Fznamznon created this revision.Jul 25 2023, 8:18 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 25 2023, 8:19 AM
Fznamznon requested review of this revision.Jul 25 2023, 8:19 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 25 2023, 8:19 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
shafik added inline comments.Jul 25 2023, 9:20 AM
clang/lib/Sema/SemaOverload.cpp
14006

It feels a bit weird that we are succeeding in finding the best viable function and what we find is invalid.

Fznamznon added inline comments.Jul 25 2023, 9:23 AM
clang/lib/Sema/SemaOverload.cpp
14006
shafik added inline comments.Jul 25 2023, 9:24 AM
clang/lib/Sema/SemaOverload.cpp
14006

It looks like we have a couple of test that generate the cannot be variadic diagnostic for overloads e.g. clang/test/SemaCXX/overloaded-operator-decl.cpp it might be worth looking into why they don't crash and this case does.

shafik added inline comments.Jul 25 2023, 9:29 AM
clang/lib/Sema/SemaOverload.cpp
14006

I see that but that diagnostic looks like it is generated by the call to CheckMemberOperatorAccess(...) which is not really the same situation we have here.

Fznamznon added inline comments.Jul 25 2023, 9:34 AM
clang/lib/Sema/SemaOverload.cpp
14006

We crash when ask functiondecl its second parameter and it doesn't have it when the call expression is formed.
The cases from tests do not crash either because the operators are not used or there is at least two parameters defined in declaration of operator.

Fznamznon added inline comments.Jul 25 2023, 9:37 AM
clang/lib/Sema/SemaOverload.cpp
14006

I see that but that diagnostic looks like it is generated by the call to CheckMemberOperatorAccess(...) which is not really the same situation we have here.

Is it? For me it seems there is a similar exit inside of BuildCallToObjectOfClassType function whose description says:

/// BuildCallToObjectOfClassType - Build a call to an object of class
/// type (C++ [over.call.object]), which can end up invoking an
/// overloaded function call operator (@c operator()) or performing a
/// user-defined conversion on the object argument.

CheckMemberOperatorAccess is just a call above.

aaron.ballman added inline comments.Jul 25 2023, 9:51 AM
clang/lib/Sema/SemaOverload.cpp
14006

It feels a bit weird that we are succeeding in finding the best viable function and what we find is invalid.

I don't think it's all that weird. Consider:

void overloaded(int);
void overloaded(float) noexcept(error());

int main() {
  overloaded(1.0f);
}

the best viable function is definitely the one taking a float, but the declaration itself is still invalid.

clang/test/SemaCXX/overloaded-operator-decl.cpp
65

I think it might make sense to extend the test coverage for the other operators you can overload, just to demonstrate we diagnose them all consistently. WDYT?

shafik added inline comments.Jul 25 2023, 9:57 AM
clang/lib/Sema/SemaOverload.cpp
14006

It feels a bit weird that we are succeeding in finding the best viable function and what we find is invalid.

I don't think it's all that weird.

I think the fact that we have cases for which this diagnostic fires but does not crash deserved some digging into to see why those cases avoid this situation. If it ends up they are doing something similar then we can feel more confident that this is the right approach.

aaron.ballman added inline comments.Jul 25 2023, 10:50 AM
clang/lib/Sema/SemaOverload.cpp
14006

I thought we already knew what was going on there?

The cases from tests do not crash either because the operators are not used or there is at least two parameters defined in declaration of operator.

Fznamznon added inline comments.Jul 28 2023, 7:04 AM
clang/test/SemaCXX/overloaded-operator-decl.cpp
65

Okay, while trying to add more test cases I discovered that following

class E {};
bool operator<(const E& lhs, ...);
auto operator<=>(const E& lhs, ...);

void d() {
  E() < E();
}

crashes even with the patch since there is code searching for best overload candidate that doesn't consider possibility for them making variadic.
The code around overloading is actually pretty inconsistent, somewhere invalid candidates are considered, and somewhere not, so I spent some time not knowing what to do.
I'm now inclined that we just shouldn't consider invalid candidates like @shafik
suggests. WDYY?

aaron.ballman added inline comments.
clang/test/SemaCXX/overloaded-operator-decl.cpp
65

Overload resolution doesn't need to produce a candidate that's viable to call; C++ lets you resolve to the "best viable function" only to say "and that one wasn't good enough either." e.g., http://eel.is/c++draft/over#match.general-3

I've not yet spotted anything in http://eel.is/c++draft/over that says invalid declarations should/should not be added to the initial candidate set. I *think* the intention is that if name lookup can find the name, it goes into the candidate set. Then that set is processed to remove functions that are not viable (http://eel.is/c++draft/over.match.viable). Then we find the best viable function from that set.

I think we should be keeping the function in the candidate set so long as it matches the rules in http://eel.is/c++draft/over.match.viable even if the function is otherwise not viable. Otherwise, correcting an unrelated issue might change overload resolution to find a completely different function. e.g., in my example above, we'd select void overloaded(int); as the best viable function, but when the user corrects the float function, we'd change to call that instead. I think it's easier to understand what's going on when picking the float overload to begin with and saying "but we can't call that because it's busted".

CC @cor3ntin @hubert.reinterpretcast @rsmith for some extra opinions, as I'm not certain if I'm interpreting the standard correctly or not.

cor3ntin added inline comments.Aug 1 2023, 1:08 AM
clang/test/SemaCXX/overloaded-operator-decl.cpp
65

I'm not sure how much the standardese matter here as the declaration of the operators are ill-formed anyway.
But from a QOI perspective, i think keeping in the set everything the users might reasonably expect to be considered makes sense to me, as it *should* lead to better diagnostics

aaron.ballman added inline comments.Aug 1 2023, 6:57 AM
clang/test/SemaCXX/overloaded-operator-decl.cpp
65

Thanks for the extra perspective! Yeah, I think that's what I've been convincing myself of. Effectively:

int overloaded(int);
float overloaded(float) noexcept(error()); // error: invalid declaration

int main() {
  (void)overloaded(1.0f); // #1
}

We're always going to get the invalid declaration diagnostic. The question is whether we want a diagnostic at #1 that says "can't call this overload" or whether we want #1 to have no diagnostic (because it picked the int overload). From a purely diagnostic perspective, I think I can see arguments either way. But if we change it slightly:

template <typename Ty>
constexpr int func() { return 0; }

template <>
constexpr int func<int>() { return 1; }

template <>
constexpr int func<float>() { return 2; }

int overloaded(int);
float overloaded(float) noexcept(error()); // error: invalid declaration

static_assert(func(overloaded(1.0f)) == 2); // #1

we still get the invalid declaration diagnostic, but now we get a failing static assertion because we picked the int overload rather than the float overload. I think this is clearly worse behavior than if we issued an additional diagnostic at #1 saying that the overload we picked (float) was invalid.

https://godbolt.org/z/34aGMY43n

WDYT?

cor3ntin added inline comments.Aug 1 2023, 7:20 AM
clang/test/SemaCXX/overloaded-operator-decl.cpp
65

Agreed. If i write an overload i presumably want the compiler to consider it, even if I botched it. And apparently, the issue is not that we don't do that but rather than we don't always do that. We should strive to be consistent.

It's not critical because even in the static_assert case, the only thing a user can do is fix the declaration, at which point they would get the ambiguity diag or the correct best match correctly picked. But it's nicer to have more diags, when we can

Fznamznon added inline comments.Aug 1 2023, 7:38 AM
clang/test/SemaCXX/overloaded-operator-decl.cpp
65

I see, thank you both for the extensive explanation.

Fznamznon updated this revision to Diff 546445.Aug 2 2023, 6:54 AM

Rebase, add more test cases, fix assertion on variadic functions inside of
haveSameParameterTypes

Fznamznon marked an inline comment as done.Aug 2 2023, 6:56 AM
Fznamznon added inline comments.
clang/test/SemaCXX/overloaded-operator-decl.cpp
65

Ok, added a bit more test cases.

This revision is now accepted and ready to land.Aug 3 2023, 9:16 AM

Hmm, clang-format reports failure, but applying git-clang-format to the commit doesn't give me anything. It seems the whole SemaOverload.cpp file has broken formatting, maybe that is the reason for the failure.

This revision was landed with ongoing or failed builds.Aug 4 2023, 1:45 AM
This revision was automatically updated to reflect the committed changes.