Page MenuHomePhabricator

[analyzer] Fix Objective-C accessor body farms after D68108.
ClosedPublic

Authored by NoQ on Nov 12 2019, 7:10 PM.

Details

Summary

In the affected test D68108 causes stubs of getter and setter methods for property 'x' appear in both ObjCInterfaceDecl and ObjCImplementationDecl for our toy ClassWithProperties. Previously they were only present in ObjCInterfaceDecl. I guess that's the intended behavior.

ObjCInterfaceDecl::lookupPrivateMethod() preferes looking up the method in the implementation. CallEvent::getRuntimeDefinition() trusts it and starts using the implementation stub instead of the interface stub. This explains the disappearance of "executed line" 10: implementation stubs, unlike interface stubs, have invalid source locations.

On the other hand, bodyFarm's createObjCPropertyGetter() function queries ObjCPropertyDecl when it is looking for the declaration of variable 'self' within the method. This is no longer valid, because the accessor declaration for ObjCPropertyDecl is the interface stub, while the newly farmed body is going to be attached to the implementation stub. This explains the change in dead symbol elimination: basically, the 'self' variable is from a different Decl, so liveness analysis becomes confused. If we are to keep inlining and farming the implementation stub, we should use the 'self' from the implementation stub, not the 'self' from the interface stub.

In this patch i basically change CallEvent back to use the interface stub. Generally, i believe that CallEvent::getRuntimeDefinition() should always return either the declaration that has a body (if the body is at all present), or the canonical declaration (in all other cases), but i didn't verify that. In any case, body farms always farm the body for the canonical declaration.

I think i made the opposite choice in my previous patch on the subject, D60899. I guess i'll need to make up my mind. I'll think a bit more about this.

Another useful assertion to add would be to always check that the farmed body only refers to ParmVarDecls (or ImplicitParamDecls) that belong to the correct function/method decl.

Diff Detail

Event Timeline

NoQ created this revision.Nov 12 2019, 7:10 PM
Herald added a project: Restricted Project. · View Herald TranscriptNov 12 2019, 7:10 PM
NoQ marked 2 inline comments as done.Nov 12 2019, 7:13 PM
NoQ added inline comments.
clang/test/Analysis/Inputs/expected-plists/nullability-notes.m.plist
225–229

26 is the new 10.

clang/test/Analysis/nullability-notes.m
31

D68108 caused this symbol to never die because in the inlined stack frame of the accessor it was bound to implicit parameter variable self in the Store, and that variable was put into UnknownSpaceRegion due to not being part of its stack frame's declaration, so it was held alive forever by the Store as if it's a static variable.

In the affected test D68108 causes stubs of getter and setter methods for property 'x' appear in both ObjCInterfaceDecl and ObjCImplementationDecl for our toy ClassWithProperties. Previously they were only present in ObjCInterfaceDecl. I guess that's the intended behavior.

The "stub" is really only a forward declaration marked as isPropertyAccessorStub() and it's injected into the various implementations to get more precise debug info attribution for it. The forward decl in the interface is still there, thus the method appearing twice now.

NoQ updated this revision to Diff 229437.Nov 14 2019, 7:11 PM

I discovered another problem related to this patch, which turned out to be a crash (serious!), so here's an update.

We're crashing on roughly the following idiom:

@interface I : NSObject
@property NSObject *o;
@end

@implementation I
@end

@interface J : I
@end

@implementation J
@synthesize o;
@end

void foo(I *i) {
  i.o; // crash
}

In this code we're using @synthesize to synthesize a property that was already declared in our superclass, without re-declaring it in our class.

Here's the respective AST:

ObjCInterfaceDecl 0x115653598 <test.m:3:1, line:5:2> line:3:12 I
|-super ObjCInterface 0x7fa75fb54f78 'NSObject'
|-ObjCImplementation 0x1156538c8 'I'
|-ObjCPropertyDecl 0x1156536c8 <line:4:1, col:21> col:21 o 'NSObject *' assign readwrite atomic unsafe_unretained
|-ObjCMethodDecl 0x115653748 <col:21> col:21 implicit - o 'NSObject *'
`-ObjCMethodDecl 0x1156537d0 <col:21> col:21 implicit - setO: 'void'
  `-ParmVarDecl 0x115653858 <col:21> col:21 o 'NSObject *'
ObjCImplementationDecl 0x1156538c8 <line:7:1, line:8:1> line:7:17 I
|-ObjCInterface 0x115653598 'I'
|-ObjCIvarDecl 0x115653950 <line:4:21> col:21 implicit _o 'NSObject *' synthesize private
`-ObjCPropertyImplDecl 0x1156539b0 <<invalid sloc>, col:21> <invalid sloc> o synthesize
  |-ObjCProperty 0x1156536c8 'o'
  `-ObjCIvar 0x115653950 '_o' 'NSObject *'
ObjCInterfaceDecl 0x115653bc8 <line:10:1, line:11:2> line:10:12 J
|-super ObjCInterface 0x115653598 'I'
`-ObjCImplementation 0x115653ce0 'J'
ObjCImplementationDecl 0x115653ce0 <line:13:1, line:15:1> line:13:17 J
|-ObjCInterface 0x115653bc8 'J'
|-ObjCIvarDecl 0x115653d68 <line:14:13> col:13 o 'NSObject *' synthesize private
`-ObjCPropertyImplDecl 0x115653dc8 <col:1, col:13> col:13 o synthesize
  |-ObjCProperty 0x1156536c8 'o'
  `-ObjCIvar 0x115653d68 'o' 'NSObject *'
  • In the interface I there is an ObjCPropertyDecl for o and a pair of accessors. Nothing new here.
  • In the implementation I there's an ObjCIvarDecl for _o. Nothing new here either.
  • In the interface J there's basically nothing apart from the inheritance from I. In particular, there's no ObjCPropertyDecl.
  • In the implementation J there's:
    • An ObjCPropertyImplDecl for o
    • An ObjCIvarDecl for o which seems to be the new ivar backing the synthesized property (btw why not _o?), and
    • A pair of accessor stubs (introduced in D68108).

So, basically, 2 pairs of accessors, 2 ivars, but only one property.

This roughly corresponds to the run-time behavior of this code: you can still access the old ivar with the ivar syntax from a method in I, but you cannot access the old ivar with property syntax even in a method of I - it'll redirect you to the new ivar instead.

In particular, invoking ObjCMethodDecl::findPropertyDecl() on the new stab causes a crash, because it's a property accessor that is not backed by a property decl.

I changed the code to try to avoid relying on the existence of a property decl for synthesized accessor stubs, instead attempting a brute-force lookup by name first.

The question still stands about how exactly do we want the ObjCMethodDecl::findPropertyDecl() method to behave in this scenario (as opposed to crashing). I could easily modify it to try to find the old property decl in the superclass, but it isn't particularly helpful for me, because it wouldn't allow me to find the ivar i'm looking for. I would prefer it to give me the ObjCPropertyImplDecl but it is unfortunately not an ObjCPropertyDecl. It looks like a similar problem has also arised here, do we want a more principled solution?

P.S.

Moving that code from the static analyzer into Sema may be a good idea though.

Note that the analyzer's body farm doesn't intend to be precise; instead, it's expected to be a simplified AST that's easy for the static analyzer to understand. For example, the current body farm ignores atomicity because the static analyzer doesn't care about mutli-threaded environments; even if the body farm is improved, the analyzer will need to be separately taught to understand that more complicated AST, but the body farm was our way to avoid teaching the analyzer about the more complicated ASTs to begin with ("atomics are just functions, let's farm simpler bodies for them").

NoQ added a comment.Nov 21 2019, 6:13 PM

I'll commit in order to get rid of the crash, but i'm open for discussions :)

This revision was not accepted when it landed; it landed in state Needs Review.Nov 21 2019, 7:03 PM
This revision was automatically updated to reflect the committed changes.