File: test_thread_safety_annotations.cpp

package info (click to toggle)
ros2-rcpputils 2.13.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 524 kB
  • sloc: cpp: 5,160; xml: 31; ansic: 20; makefile: 4
file content (315 lines) | stat: -rw-r--r-- 6,554 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "gtest/gtest.h"

#include "rcpputils/thread_safety_annotations.hpp"

/*
Macros tested

Regular Capability
[X] CAPABILITY
[X] SCOPED_CAPABILITY
[X] GUARDED_BY
[X] REQUIRES
[X] ACQUIRE
[X] RELEASE
[X] ASSERT_CAPABILITY
[X] EXCLUDES

Ptr
[X] PT_GUARDED_BY

Shared Capability
[X] REQUIRES_SHARED
[X] ACQUIRE_SHARED
[X] RELEASE_SHARED
[X] ASSERT_SHARED_CAPABILITY

Other
[X] RETURN_CAPABILITY
[X] NO_THREAD_SAFETY_ANALYSIS

see note in test try_acquire
[ ] TRY_ACQUIRE
[ ] TRY_ACQUIRE_SHARED

see note in test acquire_ordering
[ ] ACQUIRED_BEFORE
[ ] ACQUIRED_AFTER
*/

struct StdGuarded
{
  std::mutex mu;
  int data RCPPUTILS_TSA_GUARDED_BY(mu);

  int incr() RCPPUTILS_TSA_REQUIRES(mu) {
    return ++data;
  }

  int incr_Unsafe() RCPPUTILS_TSA_NO_THREAD_SAFETY_ANALYSIS
  {
    return data++;
  }
};

struct RCPPUTILS_TSA_CAPABILITY ("mutex") FakeMutex
{
  bool locked = false;
  int readerLocks = 0;

  void lock() RCPPUTILS_TSA_ACQUIRE()
  {
    locked = true;
  }

  void unlock() RCPPUTILS_TSA_RELEASE()
  {
    locked = false;
  }

  void readerLock() RCPPUTILS_TSA_ACQUIRE_SHARED()
  {
    // NOTE: This is not even trying to pretend to be correct
    readerLocks++;
  }

  void readerUnlock() RCPPUTILS_TSA_RELEASE_SHARED()
  {
    // NOTE: This is not even trying to pretend to be correct
    readerLocks--;
  }

  void assertHeld() RCPPUTILS_TSA_ASSERT_CAPABILITY()
  {
    assert(locked);
  }

  void assertReaderHeld() RCPPUTILS_TSA_ASSERT_SHARED_CAPABILITY()
  {
    assert(readerLocks > 0);
  }
};

struct RCPPUTILS_TSA_SCOPED_CAPABILITY FakeLockGuard
{
  FakeMutex * mutex_;

  explicit FakeLockGuard(FakeMutex * mutex)
  RCPPUTILS_TSA_ACQUIRE(mutex)
  : mutex_(mutex)
  {
    if (!mutex_) {return;}
    mutex_->lock();
  }

  ~FakeLockGuard()
  RCPPUTILS_TSA_RELEASE()
  {
    if (!mutex_) {return;}
    mutex_->unlock();
  }
};

struct FakeGuarded
{
  FakeMutex mu;
  int data RCPPUTILS_TSA_GUARDED_BY(mu) = 0;
  int * pData RCPPUTILS_TSA_PT_GUARDED_BY(mu);

  FakeGuarded()
  {
    pData = new int;
    *pData = 0;
  }

  ~FakeGuarded()
  {
    delete pData;
  }

  int incr_HaveGuard() RCPPUTILS_TSA_REQUIRES(mu)
  {
    return data++;
  }

  int get_NoHaveGuard() RCPPUTILS_TSA_EXCLUDES(mu)
  {
    std::lock_guard<FakeMutex> lock(mu);
    return data;
  }

  int get_Shared() RCPPUTILS_TSA_REQUIRES_SHARED(mu)
  {
    return data;
  }
};

struct PrivateFakeGuarded
{
  FakeMutex * getMutex()
  RCPPUTILS_TSA_RETURN_CAPABILITY(mu)
  {
    return &mu;
  }

  FakeMutex * abuseGetMutex()
  RCPPUTILS_TSA_RETURN_CAPABILITY(mu)
  {
    return nullptr;
  }

  int data RCPPUTILS_TSA_GUARDED_BY(mu) = 0;

  void doSomething() RCPPUTILS_TSA_REQUIRES(getMutex())
  {
    data++;
  }

private:
  FakeMutex mu;
};

TEST(test_tsa, libcxx_types) {
  // Test all the annotations provided by libcxx currently
  StdGuarded guarded;

  // If you remove either following call you should get a build warning
  guarded.mu.lock();
  guarded.data++;
  guarded.mu.unlock();

  {
    // If you remove the lock_guard, you should get a build warning
    std::lock_guard<std::mutex> lock(guarded.mu);
    guarded.data++;
    guarded.incr();
    // ASSERT_EQ(guarded.data, 3);
  }

  // tests NO_THREAD_SAFETY_ANALYSIS
  guarded.incr_Unsafe();
}

TEST(test_tsa, capability) {
  // Test our macros

  // tests CAPABILITY
  FakeGuarded guarded;

  // tests ACQUIRE
  guarded.mu.lock();

  // tests GUARDED_BY
  guarded.data++;

  // tests REQUIRES
  guarded.incr_HaveGuard();

  // tests ASSERT_CAPABILITY
  guarded.mu.assertHeld();

  // tests RELEASE
  guarded.mu.unlock();

  // tests EXCLUDES
  // NOTE: ASSERT calls while lock is held will return
  // "mutex is not held on every path through here"
  // becaue the expansion has an early return
  ASSERT_EQ(guarded.get_NoHaveGuard(), 2);

  {
    // tests SCOPED_CAPABILITY
    FakeLockGuard lock(&guarded.mu);
    guarded.incr_HaveGuard();
  }
}

TEST(test_tsa, ptr_guard) {
  FakeGuarded guarded;

  // tests PT_GUARDED_BY
  guarded.mu.lock();
  *guarded.pData = 2;
  guarded.mu.unlock();

  int * old = guarded.pData;
  guarded.pData = new int;  // pData itself is not protected by the mutex
  delete old;
}

TEST(test_tsa, shared_capability) {
  FakeGuarded guarded;

  // tests ACQUIRE_SHARED
  guarded.mu.readerLock();
  // tests ASSERT_SHARED_CAPABILITY
  guarded.mu.assertReaderHeld();
  // tests REQUIRES_SHARED
  guarded.get_Shared();
  // tests RELEASE_SHARED
  guarded.mu.readerUnlock();
}

void doSomethingTwice(PrivateFakeGuarded & guarded) RCPPUTILS_TSA_REQUIRES(guarded.getMutex())
{
  guarded.doSomething();
  guarded.doSomething();
}

TEST(test_tsa, return_capability) {
  PrivateFakeGuarded guarded;
  // this pattern does not work - you can't give the returned capability a name
  {
    // auto mu = guarded.getMutex();
    // mu->lock();
    // guarded.clear();
    // mu->unlock();
  }

  // Normal pattern
  {
    // tests SCOPED_CAPABILITY and RETURN_CAPABILITY
    FakeLockGuard lock(guarded.getMutex());
    guarded.doSomething();
  }

  // Recommended abuse pattern recommended in the docs
  // https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#private-mutexes
  {
    FakeLockGuard lock(guarded.abuseGetMutex());
    doSomethingTwice(guarded);
  }
}

TEST(test_tsa, try_acquire) {
  /*
  TRY doesn't seem to be working and therefore untested. Timeline pieced together (possibly incorrect)
  - 03-2018 - LLVM 6.0.0 released
  - 04-2018 - TryLock bug reported
    - https://bugs.llvm.org/show_bug.cgi?id=32954
  - 04-2018 - TryLock bug fixed
    - svn info -r 329930 http://llvm.org/svn/llvm-project/llvm/
  - 05-2018 - Ubuntu Bionic clang6 package released
    - http://archive.ubuntu.com/ubuntu/ubuntu/pool/universe/l/llvm-defaults/
  - 07-2018 - LLVM 6.0.1 released
  */
}

TEST(test_tsa, acquire_ordering)
{
  // https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#acquired-before-and-acquired-after-are-currently-unimplemented
}