Skip to content

Commit 0cb7e7c

Browse files
committed
Make iteration over the DeclContext::lookup_result safe.
The idiom: ``` DeclContext::lookup_result R = DeclContext::lookup(Name); for (auto *D : R) {...} ``` is not safe when in the loop body we trigger deserialization from an AST file. The deserialization can insert new declarations in the StoredDeclsList whose underlying type is a vector. When the vector decides to reallocate its storage the pointer we hold becomes invalid. This patch replaces a SmallVector with an singly-linked list. The current approach stores a SmallVector<NamedDecl*, 4> which is around 8 pointers. The linked list is 3, 5, or 7. We do better in terms of memory usage for small cases (and worse in terms of locality -- the linked list entries won't be near each other, but will be near their corresponding declarations, and we were going to fetch those memory pages anyway). For larger cases: the vector uses a doubling strategy for reallocation, so will generally be between half-full and full. Let's say it's 75% full on average, so there's N * 4/3 + 4 pointers' worth of space allocated currently and will be 2N pointers with the linked list. So we break even when there are N=6 entries and slightly lose in terms of memory usage after that. We suspect that's still a win on average. Thanks to @rsmith! Differential revision: https://reviews.llvm.org/D91524
1 parent 3b8b5d1 commit 0cb7e7c

30 files changed

+458
-384
lines changed

clang-tools-extra/clangd/unittests/TestTU.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ const NamedDecl &findDecl(ParsedAST &AST, llvm::StringRef QName) {
195195
llvm::StringRef Name) -> const NamedDecl & {
196196
auto LookupRes = Scope.lookup(DeclarationName(&Ctx.Idents.get(Name)));
197197
assert(!LookupRes.empty() && "Lookup failed");
198-
assert(LookupRes.size() == 1 && "Lookup returned multiple results");
198+
assert(LookupRes.isSingleResult() && "Lookup returned multiple results");
199199
return *LookupRes.front();
200200
};
201201

clang/include/clang/AST/ASTContext.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
604604
std::unique_ptr<interp::Context> InterpContext;
605605
std::unique_ptr<ParentMapContext> ParentMapCtx;
606606

607+
/// Keeps track of the deallocated DeclListNodes for future reuse.
608+
DeclListNode *ListNodeFreeList = nullptr;
609+
607610
public:
608611
IdentifierTable &Idents;
609612
SelectorTable &Selectors;
@@ -655,6 +658,24 @@ class ASTContext : public RefCountedBase<ASTContext> {
655658
}
656659
void Deallocate(void *Ptr) const {}
657660

661+
/// Allocates a \c DeclListNode or returns one from the \c ListNodeFreeList
662+
/// pool.
663+
DeclListNode *AllocateDeclListNode(clang::NamedDecl *ND) {
664+
if (DeclListNode *Alloc = ListNodeFreeList) {
665+
ListNodeFreeList = Alloc->Rest.dyn_cast<DeclListNode*>();
666+
Alloc->D = ND;
667+
Alloc->Rest = nullptr;
668+
return Alloc;
669+
}
670+
return new (*this) DeclListNode(ND);
671+
}
672+
/// Deallcates a \c DeclListNode by returning it to the \c ListNodeFreeList
673+
/// pool.
674+
void DeallocateDeclListNode(DeclListNode *N) {
675+
N->Rest = ListNodeFreeList;
676+
ListNodeFreeList = N;
677+
}
678+
658679
/// Return the total amount of physical memory allocated for representing
659680
/// AST nodes and type information.
660681
size_t getASTAllocatedMemory() const {

clang/include/clang/AST/CXXInheritance.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,8 @@ class CXXBasePath : public SmallVector<CXXBasePathElement, 4> {
7676

7777
CXXBasePath() = default;
7878

79-
/// The set of declarations found inside this base class
80-
/// subobject.
81-
DeclContext::lookup_result Decls;
79+
/// The declarations found inside this base class subobject.
80+
DeclContext::lookup_iterator Decls;
8281

8382
void clear() {
8483
SmallVectorImpl<CXXBasePathElement>::clear();

clang/include/clang/AST/Decl.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,16 @@ class NamespaceDecl : public NamedDecl, public DeclContext,
579579
AnonOrFirstNamespaceAndInline.setInt(Inline);
580580
}
581581

582+
/// Returns true if the inline qualifier for \c Name is redundant.
583+
bool isRedundantInlineQualifierFor(DeclarationName Name) const {
584+
if (!isInline())
585+
return false;
586+
auto X = lookup(Name);
587+
auto Y = getParent()->lookup(Name);
588+
return std::distance(X.begin(), X.end()) ==
589+
std::distance(Y.begin(), Y.end());
590+
}
591+
582592
/// Get the original (first) namespace declaration.
583593
NamespaceDecl *getOriginalNamespace();
584594

clang/include/clang/AST/DeclBase.h

Lines changed: 91 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,65 +1220,110 @@ class PrettyStackTraceDecl : public llvm::PrettyStackTraceEntry {
12201220

12211221
void print(raw_ostream &OS) const override;
12221222
};
1223+
} // namespace clang
12231224

1224-
/// The results of name lookup within a DeclContext. This is either a
1225-
/// single result (with no stable storage) or a collection of results (with
1226-
/// stable storage provided by the lookup table).
1227-
class DeclContextLookupResult {
1228-
using ResultTy = ArrayRef<NamedDecl *>;
1229-
1230-
ResultTy Result;
1231-
1232-
// If there is only one lookup result, it would be invalidated by
1233-
// reallocations of the name table, so store it separately.
1234-
NamedDecl *Single = nullptr;
1235-
1236-
static NamedDecl *const SingleElementDummyList;
1225+
// Required to determine the layout of the PointerUnion<NamedDecl*> before
1226+
// seeing the NamedDecl definition being first used in DeclListNode::operator*.
1227+
namespace llvm {
1228+
template <> struct PointerLikeTypeTraits<::clang::NamedDecl *> {
1229+
static inline void *getAsVoidPointer(::clang::NamedDecl *P) { return P; }
1230+
static inline ::clang::NamedDecl *getFromVoidPointer(void *P) {
1231+
return static_cast<::clang::NamedDecl *>(P);
1232+
}
1233+
static constexpr int NumLowBitsAvailable = 3;
1234+
};
1235+
}
12371236

1237+
namespace clang {
1238+
/// A list storing NamedDecls in the lookup tables.
1239+
class DeclListNode {
1240+
friend class ASTContext; // allocate, deallocate nodes.
1241+
friend class StoredDeclsList;
12381242
public:
1239-
DeclContextLookupResult() = default;
1240-
DeclContextLookupResult(ArrayRef<NamedDecl *> Result)
1241-
: Result(Result) {}
1242-
DeclContextLookupResult(NamedDecl *Single)
1243-
: Result(SingleElementDummyList), Single(Single) {}
1244-
1245-
class iterator;
1246-
1247-
using IteratorBase =
1248-
llvm::iterator_adaptor_base<iterator, ResultTy::iterator,
1249-
std::random_access_iterator_tag, NamedDecl *>;
1250-
1251-
class iterator : public IteratorBase {
1252-
value_type SingleElement;
1243+
using Decls = llvm::PointerUnion<NamedDecl*, DeclListNode*>;
1244+
class iterator {
1245+
friend class DeclContextLookupResult;
1246+
friend class StoredDeclsList;
12531247

1248+
Decls Ptr;
1249+
iterator(Decls Node) : Ptr(Node) { }
12541250
public:
1255-
explicit iterator(pointer Pos, value_type Single = nullptr)
1256-
: IteratorBase(Pos), SingleElement(Single) {}
1251+
using difference_type = ptrdiff_t;
1252+
using value_type = NamedDecl*;
1253+
using pointer = void;
1254+
using reference = value_type;
1255+
using iterator_category = std::forward_iterator_tag;
1256+
1257+
iterator() = default;
12571258

12581259
reference operator*() const {
1259-
return SingleElement ? SingleElement : IteratorBase::operator*();
1260+
assert(Ptr && "dereferencing end() iterator");
1261+
if (DeclListNode *CurNode = Ptr.dyn_cast<DeclListNode*>())
1262+
return CurNode->D;
1263+
return Ptr.get<NamedDecl*>();
12601264
}
1265+
void operator->() const { } // Unsupported.
1266+
bool operator==(const iterator &X) const { return Ptr == X.Ptr; }
1267+
bool operator!=(const iterator &X) const { return Ptr != X.Ptr; }
1268+
inline iterator &operator++() { // ++It
1269+
assert(!Ptr.isNull() && "Advancing empty iterator");
1270+
1271+
if (DeclListNode *CurNode = Ptr.dyn_cast<DeclListNode*>())
1272+
Ptr = CurNode->Rest;
1273+
else
1274+
Ptr = nullptr;
1275+
return *this;
1276+
}
1277+
iterator operator++(int) { // It++
1278+
iterator temp = *this;
1279+
++(*this);
1280+
return temp;
1281+
}
1282+
// Enables the pattern for (iterator I =..., E = I.end(); I != E; ++I)
1283+
iterator end() { return iterator(); }
12611284
};
1285+
private:
1286+
NamedDecl *D = nullptr;
1287+
Decls Rest = nullptr;
1288+
DeclListNode(NamedDecl *ND) : D(ND) {}
1289+
};
1290+
1291+
/// The results of name lookup within a DeclContext.
1292+
class DeclContextLookupResult {
1293+
using Decls = DeclListNode::Decls;
12621294

1295+
/// When in collection form, this is what the Data pointer points to.
1296+
Decls Result;
1297+
1298+
public:
1299+
DeclContextLookupResult() = default;
1300+
DeclContextLookupResult(Decls Result) : Result(Result) {}
1301+
1302+
using iterator = DeclListNode::iterator;
12631303
using const_iterator = iterator;
1264-
using pointer = iterator::pointer;
12651304
using reference = iterator::reference;
12661305

1267-
iterator begin() const { return iterator(Result.begin(), Single); }
1268-
iterator end() const { return iterator(Result.end(), Single); }
1269-
1270-
bool empty() const { return Result.empty(); }
1271-
pointer data() const { return Single ? &Single : Result.data(); }
1272-
size_t size() const { return Single ? 1 : Result.size(); }
1273-
reference front() const { return Single ? Single : Result.front(); }
1274-
reference back() const { return Single ? Single : Result.back(); }
1275-
reference operator[](size_t N) const { return Single ? Single : Result[N]; }
1276-
1277-
// FIXME: Remove this from the interface
1278-
DeclContextLookupResult slice(size_t N) const {
1279-
DeclContextLookupResult Sliced = Result.slice(N);
1280-
Sliced.Single = Single;
1281-
return Sliced;
1306+
iterator begin() { return iterator(Result); }
1307+
iterator end() { return iterator(); }
1308+
const_iterator begin() const {
1309+
return const_cast<DeclContextLookupResult*>(this)->begin();
1310+
}
1311+
const_iterator end() const { return iterator(); }
1312+
1313+
bool empty() const { return Result.isNull(); }
1314+
bool isSingleResult() const { return Result.dyn_cast<NamedDecl*>(); }
1315+
reference front() const { return *begin(); }
1316+
1317+
// Find the first declaration of the given type in the list. Note that this
1318+
// is not in general the earliest-declared declaration, and should only be
1319+
// used when it's not possible for there to be more than one match or where
1320+
// it doesn't matter which one is found.
1321+
template<class T> T *find_first() const {
1322+
for (auto *D : *this)
1323+
if (T *Decl = dyn_cast<T>(D))
1324+
return Decl;
1325+
1326+
return nullptr;
12821327
}
12831328
};
12841329

0 commit comments

Comments
 (0)