Skip to content

Commit f628cc6

Browse files
committed
[ObjC] Avoid leaking SymbolQueue
`SymbolQueue`'s lifetime was being managed via `new` / `delete`. This was error-prone in the face of code that can a) throw exceptions, or b) return early. Typically the solution would be to move to `std::unique_ptr` and call it a day, but nothing is ever easy. The `SymbolQueue`s created by `ObjCProcessor` are intended to live for the duration of `ProcessObjCData` and `ProcessCFStrings` methods. Since they are used multiple levels down the call tree, the active `SymbolQueue` is stored as a member variable on `ObjCProcessor`. Switching to `std::unique_ptr` would ensure that the `SymbolQueue` is destroyed, but would leave a dangling pointer in the member variable. To address this I'm introducing a `ScopedSingleton` class that provides a thread-local singleton whose lifetime is controlled by a guard object. `ProcessObjCData` / `ProcessCFStrings` call `ScopedSymbolQueue::Make` and store the returned guard object in a local. Code that accesses the symbol queue uses `ScopedSymbolQueue::Get` to retrieve the current instance. The guard object ensures the `SymbolQueue` is deleted and the `current` pointer is cleared no matter how the scope is exited. A better longer-term design is to introduce a class for processing the Objective-C runtime metadata and a class for processing constants such as `CFString`s. These could directly own the symbol queue so it would be accessible to any member functions that define symbols. This is a more involved refactoring than I have time for right now.
1 parent 3228382 commit f628cc6

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

objectivec/objc.cpp

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,60 @@
22
#include "inttypes.h"
33
#include <optional>
44

5+
#define RELEASE_ASSERT(condition) ((condition) ? (void)0 : (std::abort(), (void)0))
6+
57
using namespace BinaryNinja;
68

79
namespace {
810

11+
// ScopedSingleton is a thread-local singleton that allows for scoped
12+
// instantiation and destruction of an object. It is useful for managing
13+
// resources that should only exist during a specific scope, but where it
14+
// would be inconvenient to pass the object around explicitly.
15+
//
16+
// Calling `Make` initializes the thread-local singleton and returns a `Guard`
17+
// object. When the `Guard` object goes out of scope, the singleton is destroyed.
18+
template <typename T>
19+
class ScopedSingleton
20+
{
21+
static thread_local T* current;
22+
23+
public:
24+
class Guard
25+
{
26+
friend class ScopedSingleton;
27+
Guard() = default;
28+
29+
public:
30+
~Guard()
31+
{
32+
delete current;
33+
current = nullptr;
34+
}
35+
Guard(Guard&&) = default;
36+
Guard(const Guard&) = delete;
37+
Guard& operator=(const Guard&) = delete;
38+
};
39+
40+
static T& Get()
41+
{
42+
RELEASE_ASSERT(current);
43+
return *current;
44+
}
45+
46+
static Guard Make()
47+
{
48+
RELEASE_ASSERT(!current);
49+
current = new T();
50+
return Guard {};
51+
}
52+
};
53+
54+
template <typename T>
55+
thread_local T* ScopedSingleton<T>::current = nullptr;
56+
57+
using ScopedSymbolQueue = ScopedSingleton<SymbolQueue>;
58+
959
// Attempt to recover an Objective-C class name from the symbol's name.
1060
// Note: classes defined in the current image should be looked up in m_classes
1161
// rather than using this function.
@@ -56,7 +106,7 @@ namespace {
56106
return component;
57107
}
58108

59-
}
109+
} // namespace
60110

61111
Ref<Metadata> ObjCProcessor::SerializeMethod(uint64_t loc, const Method& method)
62112
{
@@ -369,7 +419,7 @@ void ObjCProcessor::DefineObjCSymbol(
369419

370420
if (!deferred)
371421
{
372-
m_symbolQueue->Append(process, defineSymbol);
422+
ScopedSymbolQueue::Get().Append(process, defineSymbol);
373423
}
374424
else
375425
{
@@ -1331,7 +1381,8 @@ Ref<Symbol> ObjCProcessor::GetSymbol(uint64_t address)
13311381

13321382
void ObjCProcessor::ProcessObjCData()
13331383
{
1334-
m_symbolQueue = new SymbolQueue();
1384+
auto guard = ScopedSymbolQueue::Make();
1385+
13351386
auto addrSize = m_data->GetAddressSize();
13361387

13371388
m_typeNames.id = defineTypedef(m_data, {"id"}, Type::PointerType(addrSize, Type::VoidType()));
@@ -1525,9 +1576,8 @@ void ObjCProcessor::ProcessObjCData()
15251576

15261577
PostProcessObjCSections(reader.get());
15271578

1528-
m_symbolQueue->Process();
1579+
ScopedSymbolQueue::Get().Process();
15291580
m_data->EndBulkModifySymbols();
1530-
delete m_symbolQueue;
15311581

15321582
auto meta = SerializeMetadata();
15331583
m_data->StoreMetadata("Objective-C", meta, true);
@@ -1547,7 +1597,8 @@ void ObjCProcessor::ProcessObjCLiterals()
15471597

15481598
void ObjCProcessor::ProcessCFStrings()
15491599
{
1550-
m_symbolQueue = new SymbolQueue();
1600+
auto guard = ScopedSymbolQueue::Make();
1601+
15511602
uint64_t ptrSize = m_data->GetAddressSize();
15521603
// https://github.com/apple/llvm-project/blob/next/clang/lib/CodeGen/CodeGenModule.cpp#L6129
15531604
// See also ASTContext.cpp ctrl+f __NSConstantString_tag
@@ -1654,9 +1705,9 @@ void ObjCProcessor::ProcessCFStrings()
16541705
DefineObjCSymbol(DataSymbol, Type::NamedType(m_data, m_typeNames.cfString), "cfstr_" + str, i, true);
16551706
}
16561707
}
1657-
m_symbolQueue->Process();
1708+
1709+
ScopedSymbolQueue::Get().Process();
16581710
m_data->EndBulkModifySymbols();
1659-
delete m_symbolQueue;
16601711
}
16611712
}
16621713

objectivec/objc.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,6 @@ namespace BinaryNinja {
286286
// TODO(WeiN76LQh): this is to avoid a bug with defining a classes protocol list in the DSC plugin. Remove once fixed
287287
bool m_skipClassBaseProtocols;
288288

289-
SymbolQueue* m_symbolQueue;
290289
std::map<uint64_t, Class> m_classes;
291290
std::map<uint64_t, Class> m_categories;
292291
std::map<uint64_t, Protocol> m_protocols;

0 commit comments

Comments
 (0)