This is an archive of the discontinued LLVM Phabricator instance.

Thread safety analysis: Handle ObjCIvarRefExpr in SExprBuilder::translate
ClosedPublic

Authored by aaronpuchert on Sep 17 2018, 5:27 PM.

Diff Detail

Repository
rC Clang

Event Timeline

aaronpuchert created this revision.Sep 17 2018, 5:27 PM

Adding a reviewer who knows more about ObjC than I do.

include/clang/Analysis/Analyses/ThreadSafetyCommon.h
400

ME is kind of a poor name; can you switch to IVRE like you used in the implementation?

lib/Analysis/ThreadSafetyCommon.cpp
375

I feel like this will always return false. However, I wonder if IVRE->getBase()->isArrow() is more appropriate here?

rjmccall added inline comments.Sep 18 2018, 8:22 AM
lib/Analysis/ThreadSafetyCommon.cpp
375

The base of an ObjCIvarRefExpr will never directly be a C pointer type. I don't know whether this data structure looks through casts, but it's certainly *possible* to cast arbitrarily weird C stuff to ObjC pointer type. On the other hand, by and large nobody actually ever does that, so I wouldn't make a special case for it here.

ObjCIvarRefExpr just has an isArrow() method directly if this is just supposed to be capturing the difference between . and ->. But then, so does MemberExpr, and in that case you're doing this odd analysis of the base instead of trusting isArrow(), so I don't know what to tell you to do.

Overall it is appropriate to treat an ObjC ivar reference as a perfectly ordinary projection. The only thing that's special about it is that the actual offset isn't necessarily statically known, but ivars still behave as if they're all independently declared, so I wouldn't worry about it.

aaronpuchert planned changes to this revision.Sep 18 2018, 12:37 PM

Thanks to both of you for the reviews. I'll see what I can do about the arrows. My gut feeling is that using {Member,ObjCIVarRef}Expr::isArrow is the right way, but it's not yet obvious to me how.

include/clang/Analysis/Analyses/ThreadSafetyCommon.h
400

Of course, that's a copy-and-paste error.

lib/Analysis/ThreadSafetyCommon.cpp
375

I had wondered about this as well, but when I changed hasCppPointerType(BE) to ME->isArrow() in the MemberExpr case, I got some test failures. For example:

struct Foo {
  int a __attribute__((guarded_by(mu)));
  Mutex mu;
};

void f() {
  Foo foo;
  foo.a = 5; // \
    // expected-warning{{writing variable 'a' requires holding mutex 'foo.mu' exclusively}}
}

Instead we warn writing variable 'a' requires holding mutex 'foo->mu' exclusively. That's not right.

The expression (ME) we are processing is mu from the attribute on a, which the compiler sees as this->mu. So we get ME->isArrow() == true and ME->getBase() is a CXXThisExpr.

Basically the translate* functions do a substitution. They try to derive foo.mu from this->mu on the a member and the expression foo.a. The annotation this->mu is the expression we get as parameter, and foo.a is encoded in the CallingContext.

The information whether foo.a is an arrow (it isn't) seems to be in the CallingContext's SelfArrow member.

I found something that would theoretically work:

P->setArrow((isa<CXXThisExpr>(ME->getBase()) && Ctx && Ctx->SelfArg) ? Ctx->SelfArrow : ME->isArrow());

So if we have this and a context that tells us we have to replace this by something else, then we check Ctx->SelfArrow, otherwise we take ME->isArrow().

But that doesn't work. When translating into the TIL (typed intermediate language), referencing and dereferencing operators are completely ignored.

struct Foo {
  Mutex mu_;
  void foo1(Foo *f_declared) EXCLUSIVE_LOCKS_REQUIRED(f_declared->mu_);
};

void test() {
  Foo myfoo;
  myfoo.foo1(&myfoo);  // \
    // expected-warning {{calling function 'foo1' requires holding mutex 'myfoo.mu_' exclusively}}
}

With the above change we warn that calling function 'foo1' requires holding mutex 'myfoo->mu_' exclusively. It should be (&myfoo)->mu_, but the & is lost. So we can't derive the information that we want from isArrow alone.

Now there is a reason why these operators are ignored — the TIL tries to "canonicalize" expressions, so that it detects that (&myfoo)->mu_ and myfoo.mu_ are the same thing. Changing that is probably possible, but beyond the scope of this change.

Short of that, we must be able to detect pointers. I think we could use Type::isAnyPointerType instead of Type::isPointerType in hasCppPointerType (and probably rename that).

For later I think we should consider a different canonicalization that doesn't just ignore & and *.

Detect ObjC pointer types as well as ordinary pointers.

aaronpuchert marked 2 inline comments as done.Sep 18 2018, 5:11 PM

I think it should be possible to get rid of self-> in the warning message if we want to, after all this-> is omitted in C++ as well.

test/SemaObjCXX/warn-thread-safety-analysis.mm
43

@rjmccall Would you rather write self->lock_ or lock_ in the warning message?

I think it should be possible to get rid of self-> in the warning message if we want to, after all this-> is omitted in C++ as well.

Hmm. It would be consistent to apply the same rule to both cases, and I don't have any serious concerns about dropping it. If anything, Objective-C pushes the ivar-decorating convention harder than C++ does, since it's officially blessed in the language.

delesley added inline comments.Sep 19 2018, 12:58 PM
lib/Analysis/ThreadSafetyCommon.cpp
375

This is a recurring problem. The fundamental issue is the analysis must compare expressions for equality, but many C++ expressions are syntactically different but semantically the same, like (&foo)->mu and foo.mu. It's particularly problematic because (as Aaron notes) we frequently substitute arguments when constructing expressions, which makes it easy to get things like (&foo)->mu instead of foo.mu, when substituting &foo for a pointer argument.

The purpose of the TIL is to translate C++ expressions into a simpler, lower-level IR, where syntactic equality in the IR corresponds to semantic equality between expressions. The TIL doesn't distinguish between pointers and references, doesn't distinguish between -> and ., and ignores * and & as no-ops. But then we have to recover the distinction between -> and . when we generate an error message.

In the presence of substitution, you can't go by whether the source syntax has an ->. You have to look at whether the type of the underlying argument (after stripping away * and &) requires an arrow.

I know nothing about objective C, so I don't know what the right answer is here.

delesley accepted this revision.Sep 19 2018, 12:59 PM
This revision is now accepted and ready to land.Sep 19 2018, 12:59 PM

It seems that self is an ordinary DeclRefExpr unlike this, which is a CXXThisExpr. Which means we'd have to make it dependent on the name whether we drop it, but self in C/C++ is just an ordinary variable. So I think I'll leave the self-> part for now. It certainly doesn't hurt.

This revision was automatically updated to reflect the committed changes.