On Sep 16, 2014, at 3:56 PM, jahanian <[email protected]> wrote:
> Hi John,
> 
> This test case:
> 
> id test_dict()
> {
>         return @{
>                 @"a": [](){},
>                 @"b": [](){}
>         };
> }
> 
> Crashes in IRGen because ExprWithCleanups for the dummy BlockDecl we use to 
> facilitate lambda to block conversion
> is at the wrong place.
> This happens because a lambda expression is considered a full expression so 
> it calls MaybeCreateExprWithCleanups
> (see Sema::BuildBlockForLambdaConversion).
> which ends up adding the clean up code for the BlockDecl of the first lambda 
> to the expression for the 2nd lambda!
> 
> Ideally, MaybeCreateExprWithCleanups should be called when the entire 
> dictionary literal is seen. But in this case,
> this does not happen. Attached patch fixes this by making sure that after 
> ‘every’ parse of the ‘value’ expression
> MaybeCreateExprWithCleanups is called. This, in effect, assumes that the 
> ‘value’ literal is a full expression.
> I don’t know the ramification of this change in the common case as couple of 
> non-lambda literal tests broke.
> (it now generates the none-exc version of call instead of exc versions in 
> couple of places).
> 
> Please review. Let me know if you need additional info.

Hmm.  We’ve had a number of similar bugs in the past, and usually the fix is to 
enter a new expression evaluation context in the right place.  The right place 
here would be around the emission of the copy-initialization expression, not 
around the array/dictionary elements.  Doing that should be good enough to fix 
this in general, I think, but I’d like Doug and Richard to weigh in because 
there’s weirdness afoot.

So, the interesting code here is in BuildBlockForLambdaConversion, at the 
bottom of SemaLambda.cpp.  This function is used to implement the conversion of 
a lambda to a block pointer, which occurs (1) within the implementation of the 
non-standard conversion operator to block-pointer type that we add to all 
lambda types and (2) as a special case of applying that conversion when the 
source is a LiteralExpr.  Let’s call case (2) the Literal Hack.  The Literal 
Hack solves some problems with block memory management which would otherwise 
bite us, but it’s also the code path that’s causing this bug.

  ExprResult Sema::BuildBlockForLambdaConversion(SourceLocation CurrentLocation,
                                                 SourceLocation ConvLocation,
                                                 CXXConversionDecl *Conv,
                                                 Expr *Src) {
    …
     ExprResult Init = PerformCopyInitialization(
                        InitializedEntity::InitializeBlock(ConvLocation, 
                                                           Src->getType(), 
                                                           /*NRVO=*/false),
                        CurrentLocation, Src);
    if (!Init.isInvalid())
      Init = ActOnFinishFullExpr(Init.get());
    ...

This Init expression will become the copy-init expression in the block's 
capture of the lambda (its own capture), which is actually used in two 
different places.  The first is that it’s emitted during the IR-generation of 
the block literal expression in order to initialize the corresponding capture 
field; the second is that it's emitted within the block’s copy-helper function 
in order to initialize the destination capture field.  In this latter 
situation, IRGen first binds the address of all the captured local variables to 
the corresponding field in the block.  It’s because of this second use that we 
generally want to enter a new expression evaluation context around emitting the 
copy-init expression of a capture, because an arbitrary class’s copy 
constructor might require arbitrary stuff in default arguments, including the 
killer cases that absolutely require proper tracking of cleanups.  (Ordinary 
C++ temporaries themselves are not problematic because we can conservatively 
say that any expression might have C++ cleanups without causing any lasting 
harm.)

Note that the Src expression parameter to BuildBlockForLambdaConversion becomes 
a part of this Init expression, and it’s already been emitted;  in the normal 
case, we could just pass in a std::function to generate it, but in the Literal 
Hack case we can’t, because we don’t know that we’re doing the Literal Hack 
until after we’ve fully parsed that expression and the enclosing expression and 
decided that we need to use the conversion operator.  So if Src could be an 
arbitrary expression, we’d be in deep trouble, and I think we’d need to split 
the two use cases and maintain two separate copy-init expressions.

Fortunately, it can’t.  In the normal case it’ll be a DeclRefExpr, and in the 
Literal Hack case it’ll be a LambdaExpr, which has some arbitrarily complex 
sub-expressions to do capturing but which wraps them all up as 
full-expressions.  So I think we can just wrap the construction in an 
EnterExpressionEvaluationContext, flag that there are cleanups in the new 
context if the lambda type has a non-trivial destructor, and then bind that up 
as its own full-expression.

This is slightly wrong from a modeling perspective because it will put the 
LambdaExpr inside an ExprWithCleanups, which in principle limits the lifetime 
of that temporary too much, but the generated code has the correct semantics 
because we’ll elide the lambda temporary directly into the block’s capture 
field, and the block's lifetime is at least as long as the lambda temporary’s.

So I think the right fix is basically:

  ExprResult Init;
  {
    EnterExpressionEvaluationContext evalContext(…);
    ExprHasCleanups = !Lambda->hasTrivialDestructor();
    Init = PerformCopyInitialization(…);
    if (!Init.isInvalid())
      Init = ActOnFinishFullExpr(Init.get());
  }

But I’d like to know if I’m missing some way in which arbitrary subexpression 
structure can show up in Src.

John.
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to