Skip to content

Commit 732f90d

Browse files
authored
Merge pull request #10822 from benlangmuir/cache-release-4
Fix sys::Cache for ref-counted values which caused memory leaks in SourceKit
2 parents 11d5471 + 61d27ff commit 732f90d

File tree

5 files changed

+221
-27
lines changed

5 files changed

+221
-27
lines changed

include/swift/Basic/Cache.h

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,6 @@
2121
namespace swift {
2222
namespace sys {
2323

24-
template <typename T>
25-
struct CacheTypeMgmtInfo {
26-
static void *enterCache(const T &Val) { return new T(Val); }
27-
static void exitCache(void *Ptr) { delete static_cast<T*>(Ptr); }
28-
static const T &getFromCache(void *Ptr) { return *static_cast<T*>(Ptr); }
29-
};
30-
3124
template <typename T>
3225
struct CacheKeyHashInfo {
3326
static uintptr_t getHashValue(const T &Val) {
@@ -40,19 +33,24 @@ struct CacheKeyHashInfo {
4033
};
4134

4235
template <typename T>
43-
struct CacheValueCostInfo {
44-
static size_t getCost(const T &Val) { return sizeof(Val); }
36+
struct CacheKeyInfo : public CacheKeyHashInfo<T> {
37+
static void *enterCache(const T &Val) { return new T(Val); }
38+
static void exitCache(void *Ptr) { delete static_cast<T*>(Ptr); }
39+
static const void *getLookupKey(const T *Val) { return Val; }
40+
static const T &getFromCache(void *Ptr) { return *static_cast<T*>(Ptr); }
4541
};
4642

4743
template <typename T>
48-
struct CacheKeyInfo : public CacheKeyHashInfo<T>,
49-
public CacheTypeMgmtInfo<T> {
50-
static const void *getLookupKey(const T *Val) { return Val; }
44+
struct CacheValueCostInfo {
45+
static size_t getCost(const T &Val) { return sizeof(Val); }
5146
};
5247

5348
template <typename T>
54-
struct CacheValueInfo : public CacheValueCostInfo<T>,
55-
public CacheTypeMgmtInfo<T> {
49+
struct CacheValueInfo : public CacheValueCostInfo<T> {
50+
static void *enterCache(const T &Val) { return new T(Val); }
51+
static void retain(void *Ptr) {}
52+
static void release(void *Ptr) { delete static_cast<T *>(Ptr); }
53+
static const T &getFromCache(void *Ptr) { return *static_cast<T *>(Ptr); }
5654
};
5755

5856
/// The underlying implementation of the caching mechanism.
@@ -68,7 +66,8 @@ class CacheImpl {
6866
bool (*keyIsEqualCB)(void *Key1, void *Key2, void *UserData);
6967

7068
void (*keyDestroyCB)(void *Key, void *UserData);
71-
void (*valueDestroyCB)(void *Value, void *UserData);
69+
void (*valueRetainCB)(void *Value, void *UserData);
70+
void (*valueReleaseCB)(void *Value, void *UserData);
7271
};
7372

7473
protected:
@@ -154,7 +153,8 @@ class Cache : CacheImpl {
154153
keyHash,
155154
keyIsEqual,
156155
keyDestroy,
157-
valueDestroy
156+
valueRetain,
157+
valueRelease,
158158
};
159159
Impl = create(Name, CBs);
160160
}
@@ -203,19 +203,23 @@ class Cache : CacheImpl {
203203
static void keyDestroy(void *Key, void *UserData) {
204204
KeyInfoT::exitCache(Key);
205205
}
206-
static void valueDestroy(void *Value, void *UserData) {
207-
ValueInfoT::exitCache(Value);
206+
static void valueRetain(void *Value, void *UserData) {
207+
ValueInfoT::retain(Value);
208+
}
209+
static void valueRelease(void *Value, void *UserData) {
210+
ValueInfoT::release(Value);
208211
}
209212
};
210213

211214
template <typename T>
212215
struct CacheValueInfo<llvm::IntrusiveRefCntPtr<T>>{
213216
static void *enterCache(const llvm::IntrusiveRefCntPtr<T> &Val) {
214-
T *Ptr = Val.get();
215-
Ptr->Retain();
216-
return Ptr;
217+
return const_cast<T *>(Val.get());
218+
}
219+
static void retain(void *Ptr) {
220+
static_cast<T*>(Ptr)->Retain();
217221
}
218-
static void exitCache(void *Ptr) {
222+
static void release(void *Ptr) {
219223
static_cast<T*>(Ptr)->Release();
220224
}
221225
static llvm::IntrusiveRefCntPtr<T> getFromCache(void *Ptr) {

lib/Basic/Cache.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,14 @@ void CacheImpl::setAndRetain(void *Key, void *Value, size_t Cost) {
7878
DefaultCacheKey CKey(Key, &DCache.CBs);
7979
auto Entry = DCache.Entries.find(CKey);
8080
if (Entry != DCache.Entries.end()) {
81+
if (Entry->second == Value)
82+
return;
8183
DCache.CBs.keyDestroyCB(Entry->first.Key, nullptr);
82-
DCache.CBs.valueDestroyCB(Entry->second, nullptr);
84+
DCache.CBs.valueReleaseCB(Entry->second, nullptr);
8385
DCache.Entries.erase(Entry);
8486
}
8587

88+
DCache.CBs.valueRetainCB(Value, nullptr);
8689
DCache.Entries[CKey] = Value;
8790

8891
// FIXME: Not thread-safe! It should avoid deleting the value until
@@ -116,7 +119,7 @@ bool CacheImpl::remove(const void *Key) {
116119
auto Entry = DCache.Entries.find(CKey);
117120
if (Entry != DCache.Entries.end()) {
118121
DCache.CBs.keyDestroyCB(Entry->first.Key, nullptr);
119-
DCache.CBs.valueDestroyCB(Entry->second, nullptr);
122+
DCache.CBs.valueReleaseCB(Entry->second, nullptr);
120123
DCache.Entries.erase(Entry);
121124
return true;
122125
}
@@ -129,7 +132,7 @@ void CacheImpl::removeAll() {
129132

130133
for (auto Entry : DCache.Entries) {
131134
DCache.CBs.keyDestroyCB(Entry.first.Key, nullptr);
132-
DCache.CBs.valueDestroyCB(Entry.second, nullptr);
135+
DCache.CBs.valueReleaseCB(Entry.second, nullptr);
133136
}
134137
DCache.Entries.clear();
135138
}

lib/Basic/Darwin/Cache-Mac.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ CacheImpl::ImplTy CacheImpl::create(StringRef Name, const CallBacks &CBs) {
2929
CBs.keyIsEqualCB,
3030
nullptr,
3131
CBs.keyDestroyCB,
32-
CBs.valueDestroyCB,
32+
CBs.valueReleaseCB,
3333
nullptr,
3434
nullptr,
3535
CBs.UserData,
36-
nullptr
36+
CBs.valueRetainCB,
3737
};
3838

3939
cache_t *cache_out = nullptr;

unittests/Basic/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ handle_gyb_sources(
77

88
add_swift_unittest(SwiftBasicTests
99
BlotMapVectorTest.cpp
10+
CacheTest.cpp
1011
ClusteredBitVectorTest.cpp
1112
DemangleTest.cpp
1213
DiverseStackTest.cpp

unittests/Basic/CacheTest.cpp

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
//===--- CacheTest.cpp ----------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "swift/Basic/Cache.h"
14+
#include "gtest/gtest.h"
15+
16+
#if defined(__APPLE__)
17+
#define USES_LIBCACHE 1
18+
#else
19+
#define USES_LIBCACHE 0
20+
#endif
21+
22+
namespace {
23+
struct Counter {
24+
mutable int enter = 0;
25+
mutable int exit = 0;
26+
};
27+
}
28+
29+
namespace swift {
30+
namespace sys {
31+
template <>
32+
struct CacheValueInfo<Counter>{
33+
static void *enterCache(const Counter &value) {
34+
return const_cast<Counter *>(&value);
35+
}
36+
static void retain(void *ptr) {
37+
static_cast<Counter*>(ptr)->enter+= 1;
38+
}
39+
static void release(void *ptr) {
40+
static_cast<Counter*>(ptr)->exit += 1;
41+
}
42+
static const Counter &getFromCache(void *ptr) {
43+
return *static_cast<Counter *>(ptr);
44+
}
45+
static size_t getCost(const Counter &value) {
46+
return 0;
47+
}
48+
};
49+
}
50+
}
51+
52+
namespace {
53+
struct RefCntToken : llvm::RefCountedBase<RefCntToken> {
54+
bool &freed;
55+
RefCntToken(bool &freed) : freed(freed) {}
56+
~RefCntToken() { freed = true; }
57+
};
58+
}
59+
60+
TEST(Cache, sameKey) {
61+
Counter c1, c2;
62+
swift::sys::Cache<const char *, Counter> cache(__func__);
63+
cache.set("a", c1);
64+
EXPECT_EQ(1, c1.enter);
65+
EXPECT_EQ(0, c1.exit);
66+
67+
cache.set("a", c2);
68+
EXPECT_EQ(1, c1.enter);
69+
EXPECT_EQ(1, c1.exit);
70+
EXPECT_EQ(1, c2.enter);
71+
EXPECT_EQ(0, c2.exit);
72+
}
73+
74+
TEST(Cache, sameValue) {
75+
Counter c;
76+
swift::sys::Cache<const char *, Counter> cache(__func__);
77+
cache.set("a", c);
78+
EXPECT_EQ(1, c.enter);
79+
EXPECT_EQ(0, c.exit);
80+
81+
cache.set("b", c);
82+
#if USES_LIBCACHE
83+
EXPECT_EQ(1, c.enter); // value is shared.
84+
#else
85+
EXPECT_EQ(2, c.enter);
86+
#endif
87+
EXPECT_EQ(0, c.exit);
88+
89+
cache.remove("a");
90+
#if USES_LIBCACHE
91+
EXPECT_EQ(1, c.enter); // value is shared.
92+
EXPECT_EQ(0, c.exit);
93+
#else
94+
EXPECT_EQ(2, c.enter);
95+
EXPECT_EQ(1, c.exit);
96+
#endif
97+
98+
cache.remove("b");
99+
EXPECT_EQ(c.enter, c.exit);
100+
}
101+
102+
TEST(Cache, sameKeyValue) {
103+
Counter c;
104+
swift::sys::Cache<const char *, Counter> cache(__func__);
105+
cache.set("a", c);
106+
EXPECT_EQ(1, c.enter);
107+
EXPECT_EQ(0, c.exit);
108+
109+
cache.set("a", c);
110+
EXPECT_EQ(1, c.enter);
111+
EXPECT_EQ(0, c.exit);
112+
113+
cache.remove("a");
114+
EXPECT_EQ(1, c.enter);
115+
EXPECT_EQ(1, c.exit);
116+
}
117+
118+
TEST(Cache, sameKeyIntrusiveRefCountPter) {
119+
bool freed1 = false;
120+
bool freed2 = false;
121+
swift::sys::Cache<const char *, llvm::IntrusiveRefCntPtr<RefCntToken>> cache(__func__);
122+
{
123+
llvm::IntrusiveRefCntPtr<RefCntToken> c1(new RefCntToken(freed1));
124+
llvm::IntrusiveRefCntPtr<RefCntToken> c2(new RefCntToken(freed2));
125+
cache.set("a", c1);
126+
cache.set("a", c2);
127+
}
128+
EXPECT_TRUE(freed1);
129+
EXPECT_FALSE(freed2);
130+
cache.remove("a");
131+
EXPECT_TRUE(freed2);
132+
}
133+
134+
TEST(Cache, sameValueIntrusiveRefCountPter) {
135+
bool freed = false;
136+
swift::sys::Cache<const char *, llvm::IntrusiveRefCntPtr<RefCntToken>> cache(__func__);
137+
{
138+
llvm::IntrusiveRefCntPtr<RefCntToken> c(new RefCntToken(freed));
139+
cache.set("a", c);
140+
EXPECT_FALSE(freed);
141+
142+
cache.set("b", c);
143+
EXPECT_FALSE(freed);
144+
145+
cache.remove("a");
146+
EXPECT_FALSE(freed);
147+
148+
cache.remove("b");
149+
EXPECT_FALSE(freed);
150+
}
151+
EXPECT_TRUE(freed);
152+
}
153+
154+
TEST(Cache, sameKeyValueIntrusiveRefCountPter) {
155+
bool freed = false;
156+
swift::sys::Cache<const char *, llvm::IntrusiveRefCntPtr<RefCntToken>> cache(__func__);
157+
{
158+
llvm::IntrusiveRefCntPtr<RefCntToken> c(new RefCntToken(freed));
159+
cache.set("a", c);
160+
EXPECT_FALSE(freed);
161+
cache.set("a", c);
162+
EXPECT_FALSE(freed);
163+
}
164+
EXPECT_FALSE(freed);
165+
cache.remove("a");
166+
EXPECT_TRUE(freed);
167+
}
168+
169+
TEST(Cache, copyValue) {
170+
struct S {
171+
int ident, copy;
172+
S(int ident) : ident(ident), copy(0) {}
173+
S(const S &other) : ident(other.ident), copy(other.copy+1) {}
174+
S(S &&other) : ident(other.ident), copy(other.copy) {}
175+
};
176+
swift::sys::Cache<const char *, struct S> cache(__func__);
177+
S s{0};
178+
EXPECT_EQ(0, s.ident);
179+
EXPECT_EQ(0, s.copy);
180+
cache.set("a", s);
181+
EXPECT_EQ(0, cache.get("a")->ident);
182+
EXPECT_EQ(2, cache.get("a")->copy); // return by value causes 2nd copy
183+
cache.set("b", *cache.get("a"));
184+
EXPECT_EQ(0, cache.get("b")->ident);
185+
EXPECT_EQ(4, cache.get("b")->copy); // return by value causes 2nd copy
186+
}

0 commit comments

Comments
 (0)