Skip to content

Commit f292775

Browse files
xuhdevmeta-codesync[bot]
authored andcommitted
Add group tracking to UniqueMethodTracker
Summary: Extends `UniqueMethodTracker` to track groups of methods with identical code. When `insert()` is called, the method is now also recorded in an internal `ConcurrentMap` under its representative method. A representative method is the first method with this code inserted to this group. A new `groups()` accessor returns a const reference to this map. Reviewed By: thezhangwei Differential Revision: D91266062 fbshipit-source-id: eaea68d40f2741681fddf8f154adbcb609d70ad4
1 parent 8d1b78a commit f292775

File tree

4 files changed

+180
-19
lines changed

4 files changed

+180
-19
lines changed

libredex/UniqueMethodTracker.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ std::pair<const DexMethod*, bool> UniqueMethodTracker::insert(
1919

2020
std::pair<const DexMethod*, bool> UniqueMethodTracker::insert(
2121
const DexMethod* method, size_t code_hash) {
22-
const auto [ptr, inserted] = m_unique_methods.insert(Key{code_hash, method});
23-
return {ptr->method, inserted};
22+
const DexMethod* representative = nullptr;
23+
bool was_new = false;
24+
25+
// The third parameter to the updater is true if the entry already existed,
26+
// false if it was newly created.
27+
m_groups.update(
28+
Key{code_hash, method},
29+
[method, &representative, &was_new](
30+
const Key& key, UnorderedSet<const DexMethod*>& group, bool existed) {
31+
was_new = !existed;
32+
representative = existed ? key.method : method;
33+
group.insert(method);
34+
});
35+
36+
return {representative, was_new};
2437
}

libredex/UniqueMethodTracker.h

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,12 @@
1111

1212
#include "ConcurrentContainers.h"
1313
#include "ControlFlow.h"
14+
#include "DeterministicContainers.h"
1415
#include "DexClass.h"
1516

16-
// Tracks unique method code patterns using hash + string equality.
17+
// Tracks unique method code patterns using hash + CFG equality.
1718
// Thread-safe for concurrent inserts.
1819
class UniqueMethodTracker {
19-
public:
20-
// Inserts a method and returns (representative, was_inserted).
21-
// - If the code is new, returns (method, true).
22-
// - If the code was seen before, returns (first_method_with_same_code,
23-
// false).
24-
// - If the method has no code or CFG, returns (nullptr, false).
25-
std::pair<const DexMethod*, bool> insert(const DexMethod* method);
26-
27-
size_t size() const { return m_unique_methods.size(); }
28-
29-
protected:
30-
// Insert with a specific hash value. Exposed for testing collision handling.
31-
std::pair<const DexMethod*, bool> insert(const DexMethod* method,
32-
size_t code_hash);
33-
3420
private:
3521
// Compare two CFGs instruction by instruction.
3622
static bool cfg_code_equals(const cfg::ControlFlowGraph& a,
@@ -43,6 +29,7 @@ class UniqueMethodTracker {
4329
});
4430
}
4531

32+
public:
4633
struct Key {
4734
size_t code_hash;
4835
const DexMethod* method;
@@ -64,9 +51,37 @@ class UniqueMethodTracker {
6451
}
6552
};
6653

54+
private:
6755
struct KeyHash {
6856
size_t operator()(const Key& key) const { return key.code_hash; }
6957
};
7058

71-
InsertOnlyConcurrentSet<Key, KeyHash> m_unique_methods;
59+
public:
60+
using GroupMap = ConcurrentMap<Key, UnorderedSet<const DexMethod*>, KeyHash>;
61+
62+
// Inserts a method and returns (representative, was_inserted).
63+
// - If the code is new, returns (method, true).
64+
// - If the code was seen before, returns (first_method_with_same_code,
65+
// false).
66+
// - If the method has no code or CFG, returns (nullptr, false).
67+
std::pair<const DexMethod*, bool> insert(const DexMethod* method);
68+
69+
size_t size() const { return m_groups.size(); }
70+
71+
// Returns the groups of methods with identical code.
72+
// Use UnorderedIterable to iterate over the map.
73+
// Each group's key.method is the representative, value is all methods with
74+
// that code (including the representative).
75+
const GroupMap& groups() const { return m_groups; }
76+
77+
protected:
78+
// Insert with a specific hash value. Exposed for testing collision handling.
79+
std::pair<const DexMethod*, bool> insert(const DexMethod* method,
80+
size_t code_hash);
81+
82+
private:
83+
// Maps code pattern (by Key) to all methods with that code.
84+
// The key.method is the representative (first method inserted with this
85+
// code).
86+
GroupMap m_groups;
7287
};

test/unit/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ type_system_test_LDADD = $(COMMON_MOCK_TEST_LIBS)
531531
type_util_test_SOURCES = TypeUtilTest.cpp
532532

533533
unique_method_tracker_test_SOURCES = UniqueMethodTrackerTest.cpp
534+
unique_method_tracker_test_LDADD = $(COMMON_MOCK_TEST_LIBS)
534535

535536
up_code_motion_test_SOURCES = UpCodeMotionTest.cpp
536537
up_code_motion_test_LDADD = $(COMMON_MOCK_TEST_LIBS)

test/unit/UniqueMethodTrackerTest.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99

1010
#include <gmock/gmock.h>
1111

12+
#include "DexHasher.h"
1213
#include "IRAssembler.h"
1314
#include "RedexTest.h"
1415

1516
using ::testing::IsNull;
17+
using ::testing::NotNull;
1618
using ::testing::SizeIs;
1719

1820
class UniqueMethodTrackerTest : public RedexTest, public UniqueMethodTracker {
@@ -51,6 +53,22 @@ class UniqueMethodTrackerTest : public RedexTest, public UniqueMethodTracker {
5153

5254
ASSERT_THAT(*this, SizeIs(2u));
5355
}
56+
57+
// Helper to find a group by its representative method.
58+
const UnorderedSet<const DexMethod*>* find_group(
59+
const DexMethod* representative) const {
60+
const auto* code = representative->get_code();
61+
if (code == nullptr || !code->cfg_built()) {
62+
return nullptr;
63+
}
64+
size_t hash = hashing::DexMethodHasher(representative).run().code_hash;
65+
Key key{hash, representative};
66+
auto it = this->groups().find(key);
67+
if (it == this->groups().end()) {
68+
return nullptr;
69+
}
70+
return &it->second;
71+
}
5472
};
5573

5674
TEST_F(UniqueMethodTrackerTest, UniqueMethodTrackerIdenticalCode) {
@@ -196,3 +214,117 @@ TEST_F(UniqueMethodTrackerTest, UniqueMethodTrackerHashCollision) {
196214

197215
EXPECT_THAT(*this, SizeIs(4u));
198216
}
217+
218+
TEST_F(UniqueMethodTrackerTest, GroupsTracksDuplicates) {
219+
// Verify that groups() correctly tracks methods with identical code.
220+
auto* method1 = assembler::method_from_string(R"(
221+
(method (public static) "LFoo;.dup1:()I"
222+
(
223+
(const v0 100)
224+
(return v0)
225+
)
226+
)
227+
)");
228+
229+
auto* method2 = assembler::method_from_string(R"(
230+
(method (public static) "LFoo;.dup2:()I"
231+
(
232+
(const v0 100)
233+
(return v0)
234+
)
235+
)
236+
)");
237+
238+
auto* method3 = assembler::method_from_string(R"(
239+
(method (public static) "LFoo;.dup3:()I"
240+
(
241+
(const v0 100)
242+
(return v0)
243+
)
244+
)
245+
)");
246+
247+
method1->get_code()->build_cfg();
248+
method2->get_code()->build_cfg();
249+
method3->get_code()->build_cfg();
250+
251+
this->insert(method1);
252+
this->insert(method2);
253+
this->insert(method3);
254+
255+
// Find the group for method1 (the representative).
256+
const auto* group = find_group(method1);
257+
ASSERT_THAT(group, NotNull()) << "Expected group for method1 to exist";
258+
259+
EXPECT_THAT(*group, SizeIs(3u)) << "Expected all 3 methods in the same group";
260+
EXPECT_NE(group->find(method1), group->end());
261+
EXPECT_NE(group->find(method2), group->end());
262+
EXPECT_NE(group->find(method3), group->end());
263+
}
264+
265+
TEST_F(UniqueMethodTrackerTest, GroupsDistinctForDifferentCode) {
266+
// Verify that methods with different code are in separate groups.
267+
auto* method1 = assembler::method_from_string(R"(
268+
(method (public static) "LFoo;.unique1:()I"
269+
(
270+
(const v0 200)
271+
(return v0)
272+
)
273+
)
274+
)");
275+
276+
auto* method2 = assembler::method_from_string(R"(
277+
(method (public static) "LFoo;.unique2:()I"
278+
(
279+
(const v0 201)
280+
(return v0)
281+
)
282+
)
283+
)");
284+
285+
method1->get_code()->build_cfg();
286+
method2->get_code()->build_cfg();
287+
288+
this->insert(method1);
289+
this->insert(method2);
290+
291+
// Each method should be in its own group.
292+
const auto* group1 = find_group(method1);
293+
ASSERT_THAT(group1, NotNull());
294+
EXPECT_THAT(*group1, SizeIs(1u));
295+
EXPECT_NE(group1->find(method1), group1->end());
296+
297+
const auto* group2 = find_group(method2);
298+
ASSERT_THAT(group2, NotNull());
299+
EXPECT_THAT(*group2, SizeIs(1u));
300+
EXPECT_NE(group2->find(method2), group2->end());
301+
}
302+
303+
TEST_F(UniqueMethodTrackerTest, DuplicateInsertionIgnored) {
304+
// Verify that inserting the same method twice does not add duplicates.
305+
auto* method = assembler::method_from_string(R"(
306+
(method (public static) "LFoo;.duplicate:()I"
307+
(
308+
(const v0 999)
309+
(return v0)
310+
)
311+
)
312+
)");
313+
314+
method->get_code()->build_cfg();
315+
316+
const auto [rep1, inserted1] = this->insert(method);
317+
ASSERT_TRUE(inserted1);
318+
ASSERT_EQ(rep1, method);
319+
320+
// Insert same method again.
321+
const auto [rep2, inserted2] = this->insert(method);
322+
EXPECT_FALSE(inserted2) << "Re-inserting same method should return false";
323+
EXPECT_EQ(rep2, method) << "Representative should still be the same method";
324+
325+
// The group should contain exactly one entry.
326+
const auto* group = find_group(method);
327+
ASSERT_THAT(group, NotNull());
328+
EXPECT_THAT(*group, SizeIs(1u))
329+
<< "Group should have exactly 1 method, not duplicates";
330+
}

0 commit comments

Comments
 (0)