On Sep 16, 2014, at 7:20 PM, John McCall <[email protected]> wrote:

> 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.

Yeh. This change (with minor correction as attached) has the desired effect of 
postponing cleanup code for the Literal hack until the outer-most expression is 
seen. But, I wait if there are more comments.

        
Index: lib/Sema/SemaLambda.cpp
===================================================================
--- lib/Sema/SemaLambda.cpp     (revision 217960)
+++ lib/Sema/SemaLambda.cpp     (working copy)
@@ -1572,14 +1572,20 @@
   CallOperator->setReferenced();
   CallOperator->markUsed(Context);
 
-  ExprResult Init = PerformCopyInitialization(
-                      InitializedEntity::InitializeBlock(ConvLocation, 
-                                                         Src->getType(), 
-                                                         /*NRVO=*/false),
-                      CurrentLocation, Src);
-  if (!Init.isInvalid())
-    Init = ActOnFinishFullExpr(Init.get());
+  ExprResult Init;
+  {
+    EnterExpressionEvaluationContext evalContext(*this, 
Sema::PotentiallyEvaluated);
+    ExprNeedsCleanups = !Lambda->hasTrivialDestructor();
   
+    Init = PerformCopyInitialization(
+             InitializedEntity::InitializeBlock(ConvLocation,
+                                                Src->getType(),
+                                                /*NRVO=*/false),
+                                     CurrentLocation, Src);
+    if (!Init.isInvalid())
+      Init = ActOnFinishFullExpr(Init.get());
+  }
+  
   if (Init.isInvalid())
     return ExprError();
   
Index: test/CodeGenObjCXX/lambda-in-objc-literals.mm
===================================================================
--- test/CodeGenObjCXX/lambda-in-objc-literals.mm       (revision 0)
+++ test/CodeGenObjCXX/lambda-in-objc-literals.mm       (working copy)
@@ -0,0 +1,52 @@
+// RUN: %clang_cc1 -triple x86_64-apple-darwin10.0.0 -emit-llvm -o - %s 
-fexceptions -std=c++11 -fblocks
+// rdar://16879958
+
+@protocol NSCopying @end
+typedef unsigned long NSUInteger;
+typedef long NSInteger;
+
+@interface NSDictionary
++ (id)dictionaryWithObjects:(const id [])objects forKeys:(const id <NSCopying> 
[])keys count:(NSUInteger)cnt;
+- (void)setObject:(id)object forKeyedSubscript:(id)key;
+- (id)objectForKeyedSubscript:(id)key;
++ (id) alloc;
+- (id)initWithObjects:(const id [])objects forKeys:(const id [])keys 
count:(unsigned long)cnt;
+@end
+
+@interface NSString<NSCopying>
+@end
+
+
+@class NSFastEnumerationState;
+
+@protocol NSFastEnumeration
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state 
objects:(id [])buffer count:(NSUInteger)len;
+
+@end
+
+@interface NSNumber
++ (NSNumber *)numberWithInt:(int)value;
+@end
+
+@interface NSArray <NSFastEnumeration>
++ (id) alloc;
++ (id)arrayWithObjects:(const id [])objects count:(NSUInteger)cnt;
+@end
+
+id test_dict()
+{
+        return @{
+                @"a": [](){},
+                @"b": [](){},
+                @"c": [](){}
+        };
+}
+
+id test_arr()
+{
+        return @[
+                [](){},
+                [](){}
+        ];
+}

- Thanks, Fariborz

> 
> John.

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

Reply via email to