SExprBuilder::translateDeclRefExpr was only looking at FunctionDecl and not also looking at ObjCMethodDecl. It should consider both because the attributes can be used on Objective-C as well.
rdar://problem/48941331
Differential D59523
Thread Safety: also look at ObjC methods jfb on Mar 18 2019, 4:59 PM. Authored by
Details SExprBuilder::translateDeclRefExpr was only looking at FunctionDecl and not also looking at ObjCMethodDecl. It should consider both because the attributes can be used on Objective-C as well. rdar://problem/48941331
Diff Detail
Event TimelineComment Actions It seems weird how both do pretty similar things and e.g. duplicate getParamDecl. The repro I have is 20M, creduce chokes on it, and manually reducing failed to repro... ☹️ Comment Actions Agreed that it has getParamDecl as well, and the context would fit. It just seems weird how stuff is repeated, Iw as hoping someone would have some insight I failed at gaining through inspection of the code! Comment Actions
Well, it's not really supported that well though. There are known bugs like https://bugs.llvm.org/show_bug.cgi?id=38892, and I don't really have the time to fix that. (You're free to jump in of course.)
I have no idea about ObjC at all, but I believe that ObjC methods are substantially different from ordinary functions. (Unlike C++ methods, which just have an additional implicit parameter.) You could do something like SExprBuilder::enterCFG: auto Parms = isa<ObjCMethodDecl>(D) ? cast<ObjCMethodDecl>(D)->parameters() : cast<FunctionDecl>(D)->parameters();
You could try declaring an ObjC method with a thread safety attribute that refers to a parameter, then somewhere else call that function. Again, I don't know ObjC, but for a C function you would do: struct Data { Mutex mu; } void f(struct Data *data) __attribute__ ((exclusive_locks_required(data->mu))); void g() { struct Data d; Lock(&d.mu); f(&d); // Should only work with your change. Unlock(&d.mu); } Then the SExprBuilder should have to replace the reference to the parameter (here data) by the value it is called with (here &d). I'll have to admit I don't find it easy to wrap my head around this logic either, so maybe I'm missing something. The tests for ObjC[++] are very brief, but you can look at the C++ tests for hints. (See test/Sema{ObjC,ObjCXX,CXX}/warn-thread-safety-analysis.{m,mm,cpp}.) Comment Actions I reduced the code I had to this: struct __attribute__((capability("mutex"))) MyLock { void lock() __attribute__((acquire_capability())) {} void unlock() __attribute__((release_capability())) {} }; template <class T> struct __attribute__((scoped_lockable)) Locker { T &_l; Locker(T &l) __attribute__((acquire_capability(l))) : _l(l) { _l.lock(); } ~Locker() __attribute__((release_capability())) { _l.unlock(); } }; struct MyLockable { MyLock lock; }; @protocol MyProtocolBase; @protocol MyProtocol <MyProtocolBase> @end @interface MyProtocol @end @interface MyProtocol () - (void)doIt:(struct MyLockable *)myLockable; @end @implementation MyProtocol - (void)doIt:(struct MyLockable *)myLockable { Locker<MyLock> lock(myLockable->lock); } @end But now it doesn't repro... I probably missed something important :)
Comment Actions Adding Delesley in case he has input on design.
Comment Actions Creating a new test makes sense to me if it tests things across components. We have such tests for modules, PCH, and templates. There are also separate tests for the attribute parsing, which doesn't work terribly well in ObjC either. I would agree to making a new test for that when someone fixes PR38892. (Edit: got the bug number wrong.)
Comment Actions
Comment Actions
But the test also verifies that no diagnostics are omitted (// expected-no-diagnostics), so it isn't just a "this doesn't crash" test. Which is why I think it's a nice seed to build more systematic tests around it. I'd generally like to test this more systematically, but that isn't made easier if we're sprinkling the test cases over the code base.
Isn't every test about exactly that? That there was a similar bug in the past is at best informative, but not necessarily relevant. And if a functional test crashes, isn't that just as bad? I don't understand why the historical reason for a test needs to have an influence on where the test is placed. It makes much more sense to me to group tests by the code they test. If there is a unit test for a class and you find a bug in it that makes it crash, would you write a complete new unit test? Or would you add a test case to the existing test? These files are our “poor man's unit test” for warnings.
Comment Actions No, I wrote the test purely to check that the crash is gone. These tests *require* a diagnostic check, or // expected-no-diagnostics, so I put the later. Agreed that ObjC needs to be tested more systematically, but this patch isn't doing this. It's fixing a crash, and adding a test to make sure the crash is gone.
This is very concrete: this specific code used to cause a crash. This test has exactly this purpose, nothing more. What I'm doing is extremely normal for LLVM. Comment Actions The if logic does not enhance readability, but I suppose it can't be helped. Looks good to me. Comment Actions They only require // expected-no-diagnostics because you pass in -verify on the RUN line. That isn't needed to test the crashing regression, which may be part of what's causing confusion.
Agreed, this is a normal practice for tests verifying that a crash no longer happens. Comment Actions It certainly makes sense in many components, and I'm not questioning their judgement. But in the seven years prior to my involvement, no one thought it necessary to create a separate test for crashes in the thread safety analysis, and it's not because there haven't been any. Warnings are in a different situations than the parser, optimizer passes or the backend, as they only operate read-only on the AST (or CFG). Looking at the commit log for test/SemaCXX, the majority of recent commits that fix crashes (and say so in the commit message) have been extending existing test cases instead of creating new ones. (Take for example rC337766, rC338089, rC342192, rC352047.) The thread safety analysis only looks at a single function's source-level CFG at a time. So we can reasonably consider functions as sufficiently isolated test cases and declarations as their setup. That this doesn't work for a crash in the backend or parser is completely obvious to me. When working there, I would probably do the same and create a new file to reproduce my case in isolation. But what makes sense and is established practice in one component might not always make sense in another. I'll leave the judgement to you, I'm just not convinced that this needs to be done. |