This is an archive of the discontinued LLVM Phabricator instance.

Reinstate CWG1607 restrictions on lambdas appearing inside certain constant-expressions
Needs ReviewPublic

Authored by faisalv on Jan 9 2017, 8:12 PM.

Details

Reviewers
rsmith
Summary

This patch disables lambda expressions (especially Immediately Invoked Lambda Expressions (IILEs, to borrow a term from the all-mighty ecmascript ;)) from appearing within potentially mangled contexts.

The approach is a rather primitive/inelegant one. Instead of enumerating all the various syntactic constant-expression contexts at a finer level of granularity and then passing that information through to each call of ParseConstantExpression, we simply set a flag that forbids lambda expressions (when we know we are in the forbidden context). There is probably an opportunity here to streamline the machinery that prohibits lambdas in certain contexts across the various versions of C++ further - but that could be re-engineered if/when Louis Dionne's paper on Lambdas in Unevaluated contexts gets incorporated into the working draft.

Would appreciate some feedback here - thanks!

Diff Detail

Event Timeline

faisalv updated this revision to Diff 83771.Jan 9 2017, 8:12 PM
faisalv retitled this revision from to Reinstate CWG1607 restrictions on lambdas appearing inside certain constant-expressions.
faisalv updated this object.
faisalv added a reviewer: rsmith.
faisalv set the repository for this revision to rL LLVM.
faisalv added a project: Restricted Project.
faisalv added a subscriber: cfe-commits.
rsmith added inline comments.Jan 9 2017, 11:54 PM
include/clang/Parse/Parser.h
1432

I would call this IsInSignature.

include/clang/Sema/Sema.h
894–895

Rather than adding a flag, how about we have two different kinds of ExpressionEvaluationContext for constant expressions: a generic "constant expression" context and a "constant expression in signature" context?

faisalv added inline comments.Jan 10 2017, 4:01 AM
include/clang/Sema/Sema.h
894–895

But not all lambdas that appear in template arguments are in signatures - so should we split it into ConstantEvaluated, ConstantEvaluatedInSignatureOrTemplateArg ?

lib/Parse/ParseDecl.cpp
6254

I suppose we should forbid these in a lambda parameter declaration clause too?

Yes - I'll modify the patch to reflect that approach. Any thoughts on whether they should also be forbidden in Lamda parameter declaration clauses?
Thanks!

faisalv updated this revision to Diff 84476.Jan 14 2017, 7:37 PM

The updated patch adds two additional enumerators to ExpressionEvaluationContext: ConstantEvaluatedInTemplateArgument and ConstantEvaluatedInFunctionSignature and sets them appropriately (as opposed to our previous approach of setting the IsLambdaExpressionForbidden flag in those locations).

When popping off the EvaluationContext, instead of checking the flag, we now check the EvaluationContext directly to determine if the Lambda Expression is valid.

rsmith requested changes to this revision.Jan 23 2017, 11:56 AM

I don't think it's possible to check this in the way you're doing so here. In general, there's no way to know whether a constant expression will be part of a typedef declaration or function declaration until you've finished parsing it (when you're parsing the decl-specifiers in a declaration you don't know whether you're declaring a function or a variable, and the typedef keyword might appear later).

So I think you need a different approach here. How about tracking the set of contained lambdas on the Declarator and DeclSpec objects, and diagnose from ActOnFunctionDeclarator / ActOnTypedefDeclarator if the current expression evaluation context contains any lambdas? (Maybe when entering an expression evaluation context, pass an optional SmallVectorImpl<Expr*>* to Sema to collect the lambdas contained within the expression.)

There are some particularly "fun" cases to watch out for here:

decltype([]{})
  a, // ok
  f(); // ill-formed

... that require us to track the lambdas in the DeclSpec separately from the lambdas in the Declarator.

This revision now requires changes to proceed.Jan 23 2017, 11:56 AM

I don't think it's possible to check this in the way you're doing so here. In general, there's no way to know whether a constant expression will be part of a typedef declaration or function declaration until you've finished parsing it (when you're parsing the decl-specifiers in a declaration you don't know whether you're declaring a function or a variable, and the typedef keyword might appear later).

I see. The issue is that the current approach would forbid valid variable declarations such as:

void (*f)(int [([]{return 5;}())]) = 0;

... where lambdas can appear within the array declarators (I believe that's the only syntactic neighborhood that can cause this problem, right?).

So I think you need a different approach here. How about tracking the set of contained lambdas on the Declarator and DeclSpec objects, and diagnose from ActOnFunctionDeclarator / ActOnTypedefDeclarator if the current expression evaluation context contains any lambdas? (Maybe when entering an expression evaluation context, pass an optional SmallVectorImpl<Expr*>* to Sema to collect the lambdas contained within the expression.)

Yes - I can see something along these lines working well...

There are some particularly "fun" cases to watch out for here:

decltype([]{})
  a, // ok
  f(); // ill-formed

... that require us to track the lambdas in the DeclSpec separately from the lambdas in the Declarator.

Well lambda's can't appear in unevaluated operands yet, so your example would be ill-formed? If so, we don't have to worry about them showing up in decl-specifiers?
The only Declarator where they could be well formed is within an array declarator of a variable or a parameter of a function pointer variable (but no where else, i.e typedefs and function declarations), right?

Thanks!

faisalv updated this revision to Diff 85927.Jan 26 2017, 9:14 AM
faisalv edited edge metadata.

I tried a different approach, not because I was convinced it was better, but because it seemed (at the time) a simpler tweak - I'm not sure it is robust enough - but would appreciate your thoughts on it.

The approach is predicated on the following:

  • lambda expressions can only appear in types in a non-unevaluated context, within array brackets
  • if we track within each parameter declarator, its parent function declarator, we can always get to the outer most declarator and if the outermost declarator has the form (ptr-op f)(...) it can not be a function declarator (but is a variable) - we can determine this without waiting for the recursion to complete (I set a special token on the declarator once we start parsing a parens declarator and the token is a ptr-operator)
  • additionally we can check the context of the outermost declarator to ensure it is not within an alias-declaration or a template-argument

Based on the test cases, it seems to work well - but not sure if the test cases are exhaustive.
Also the patch needs some cleanup if we agree that this direction is worth pursuing (and not too fragile an approach).

Thanks!

Would appreciate feedback!

I don't think it's possible to check this in the way you're doing so here. In general, there's no way to know whether a constant expression will be part of a typedef declaration or function declaration until you've finished parsing it (when you're parsing the decl-specifiers in a declaration you don't know whether you're declaring a function or a variable, and the typedef keyword might appear later).

I see. The issue is that the current approach would forbid valid variable declarations such as:

void (*f)(int [([]{return 5;}())]) = 0;

... where lambdas can appear within the array declarators (I believe that's the only syntactic neighborhood that can cause this problem, right?).

I think so, at least until Louis removes the restriction on lambdas in unevaluated operands. Then we have a whole host of new problems:

decltype([]{}()) typedef x; // error, lambda in a typedef
template<typename T> decltype([]{}()) f(); // error, lambda in a function declaration
template<typename T> decltype([]{}()) x; // ok

If we want a forward-looking change which prepares us for that, we should be thinking about how to deal with deferring the check until we get to the end of the declaration and find out whether we declared a function or a typedef.

Well lambda's can't appear in unevaluated operands yet, so your example would be ill-formed? If so, we don't have to worry about them showing up in decl-specifiers?
The only Declarator where they could be well formed is within an array declarator of a variable or a parameter of a function pointer variable (but no where else, i.e typedefs and function declarations), right?

Right. But we should at least keep in mind the upcoming removal of the unevaluated operands restriction. Ideally, we would get some implementation experience with that now, so we can make sure that we standardize a reasonable set of rules.