Skip to content
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

return statements in statement expressions in unevaluated contexts are ignored #77808

Open
MitalAshok opened this issue Jan 11, 2024 · 9 comments
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" extension:gnu

Comments

@MitalAshok
Copy link
Contributor

MitalAshok commented Jan 11, 2024

Related: #77754

struct unrelated_type {};

int main() {
    void(* p)() noexcept = []() noexcept(({ return unrelated_type{}; true; })) {};
    bool(* q)() = []() -> decltype(({ return unrelated_type{}; true; })) { return true; };
    unrelated_type(* r)() = []() -> decltype(({ return 0; unrelated_type{}; })) { return {}; };
}

This compiles on Clang 18 (-std=c++17 -x c++ -fsyntax-only).

p and q should definitely not compile: Either the return applies to the new lambda scope (in which case, it is an incompatible type to void/bool), or it applies to int main() (where it is still incompatible with int). In either case, it should not be a constant expression for p's noexcept specifier.

r may or may not be correct, depending on what scope the return should return from.

@EugeneZelenko EugeneZelenko added clang:frontend Language frontend issues, e.g. anything involving "Sema" and removed new issue labels Jan 11, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Jan 11, 2024

@llvm/issue-subscribers-clang-frontend

Author: Mital Ashok (MitalAshok)

Related: #77754
struct unrelated_type {};

int main() {
    void(* p)() noexcept = []() noexcept(({ return unrelated_type{}; true; })) {};
    bool(* q)() = []() -> decltype(({ return unrelated_type{}; true; })) { return true; };
    unrelated_type(* r)() = []() -> decltype(({ return 0; unrelated_type{}; })) { return {}; };
}

This compiles on Clang 18 (-std=c++11 -x c++ -fsyntax-only).

p and q should definitely not compile: Either the return applies to the new lambda scope (in which case, it is an incompatible type to void/bool), or it applies to int main() (where it is still incompatible with int). In either case, it should not be a constant expression for p's noexcept specifier.

r may or may not be correct, depending on what scope the return should return from.

@shafik
Copy link
Collaborator

shafik commented Jan 11, 2024

So the gcc documentation does not cover this case at all: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html

In general, I think we presume that gcc's implementation is the documentation for a feature but maybe it is worth asking if this is the intent or not.

CC @AaronBallman

@pinskia
Copy link

pinskia commented Jan 11, 2024

Note statement expressions for C++ for GCC is underdocumented and even has many issues reported too. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=772 for some of them.

@pinskia
Copy link

pinskia commented Jan 11, 2024

So reading https://gcc.gnu.org/bugzilla/show_bug.cgi?id=772, jumping into and out of a statement expression should be rejected (though it is not currently with GCC's C++ front-end but is with GCC's C front-end). So this basically means the testcase should be rejected for all cases.

@zygoloid
Copy link
Collaborator

Disallowing jumps out of statement expressions is going to break significant amounts of real world code. For example, error handling macros sometimes return from statement expressions, and Q_FOREACH at least in older versions of Qt break from a statement expression in the increment of a for loop. These things are absolutely awful and I wish they didn't exist, but they do, and I don't think we're likely to be able to get away with removing support for them. I also don't think it really reduces frontend complexity if we disallow jumps out of statement expressions, because both exceptions and co_await can effectively jump out of an arbitrary subexpression, performing proper cleanups for local variables on the way (exceptional cleanups in the former case and non-exceptional cleanups in the latter case), so a C++ frontend needs to be able to cope with that anyway.

Disallowing jumps into statement expressions seems like something we absolutely should do. I think it'd also be a good idea to warn on control flow out of a statement expression.

@AaronBallman
Copy link
Collaborator

So reading https://gcc.gnu.org/bugzilla/show_bug.cgi?id=772, jumping into and out of a statement expression should be rejected (though it is not currently with GCC's C++ front-end but is with GCC's C front-end). So this basically means the testcase should be rejected for all cases.

It doesn't seem to be the case that it's rejected by GCC's C frontend: https://godbolt.org/z/8YhPv9erb

@cor3ntin
Copy link
Contributor

Disallowing jumps into statement expressions seems like something we absolutely should do. I think it'd also be a good idea to warn on control flow out of a statement expression.

Disallowing jumps out of a statement expressions that are in a decltype/noexcept/typeof/sizeof/etc seems like a good idea too!

@AaronBallman
Copy link
Collaborator

Jumps out of statement expressions are deeply weird, part rand(): https://godbolt.org/z/s9qGW5xP6

I'm wary of breaking a bunch of real world code, but at the same time, I'm not super comfortable defining the behavior of jumping out of a statement expression and having to support that forever. There are so many weird edge cases to have to reason about and it makes adding new language features to C and C++ somewhat harder because it might restrict what designs we can support. Perhaps we could limit support for the "feature" to only the situations we believe are in common use and explicitly deprecate those uses (and make it an error in all other circumstances)?

@zygoloid
Copy link
Collaborator

I think it might make to base the restrictions here on the restrictions as for co_await:

An await-expression shall appear only in a potentially-evaluated expression within the compound-statement of a function-body outside of a handler ([except.pre]). In a declaration-statement or in the simple-declaration (if any) of an init-statement, an await-expression shall appear only in an initializer of that declaration-statement or simple-declaration. An await-expression shall not appear in a default argument ([dcl.fct.default]). An await-expression shall not appear in the initializer of a block variable with static or thread storage duration. A context within a function where an await-expression can appear is called a suspension context of the function.

If we need more restrictions than that, we presumably also need those restrictions for co_await too, because it too can implicitly return.

The "outside a handler" part seems unnecessary here (I don't think there are new challenges compared to those we already need to handle in [except.pre]/3, and I think the co_await restriction exists for a different reason). But I think it's very reasonable to say that a statement expression can't contain a jump to outside the statement expression if it appears in an unevaluated operand, type (including array bound), default argument, or an initializer of a non-automatic local variable.

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" extension:gnu
Projects
Status: No status
Development

No branches or pull requests

8 participants