Skip to content

Commit 9b41a4b

Browse files
ivanmurashkofacebook-github-bot
authored andcommitted
Use hash based USR for ObjC/C++
Summary: The diff implements support for non-Swift USRs by using the `clangUSRToDefinition` thrift call with hashed USRs, following the same approach as the C++ glass_clangd_client. Reviewed By: Wilfred Differential Revision: D78981088 fbshipit-source-id: 6887e0592c3dc367993da7a03e2c9bb5588e8e6d
1 parent f2f9054 commit 9b41a4b

5 files changed

Lines changed: 324 additions & 3 deletions

File tree

glean/client/swift/GlassAccess.cpp

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <memory>
1616
#include <stdexcept>
1717
#include "glean/client/swift/Clock.h"
18+
#include "glean/client/swift/hash.h"
1819

1920
using namespace facebook;
2021
using apache::thrift::RpcOptions;
@@ -28,6 +29,22 @@ std::optional<protocol::LocationList> GlassAccess::usrToDefinition(
2829
facebook::glean::swift::Clock clock;
2930
std::string msg;
3031

32+
// Check if this is a Swift USR (starts with "s:")
33+
if (usr.length() >= 2 && usr.substr(0, 2) == "s:") {
34+
// Handle Swift USR using usrToDefinition
35+
return handleUSR(usr, msg);
36+
} else {
37+
// Handle non-Swift USR using clangUSRToDefinition with hash
38+
return handleUSRHash(usr, msg);
39+
}
40+
41+
auto duration = clock.duration();
42+
LOG(INFO) << "usrToDefinition request took " << duration << " milliseconds";
43+
}
44+
45+
std::optional<protocol::LocationList> GlassAccess::handleUSR(
46+
const std::string& usr,
47+
std::string& msg) {
3148
// Create USRToDefinitionRequest
3249
::glean::USRToDefinitionRequest request;
3350
request.usr_ref() = usr;
@@ -44,16 +61,42 @@ std::optional<protocol::LocationList> GlassAccess::usrToDefinition(
4461
co_return co_await client->co_usrToDefinition(rpcOptions, request, ro);
4562
});
4663

47-
auto duration = clock.duration();
48-
LOG(INFO) << "usrToDefinition request took " << duration << " milliseconds";
64+
if (!result.has_value()) {
65+
return std::nullopt;
66+
}
67+
68+
return convertUSRSymbolDefinitionToLocations(result.value());
69+
}
70+
71+
std::optional<protocol::LocationList> GlassAccess::handleUSRHash(
72+
const std::string& usr,
73+
std::string& msg) {
74+
// Hash the USR for clang USR lookup
75+
std::string hashedUSR = facebook::glean::clangx::hash::hash(usr);
76+
77+
// Call the Glass service using clangUSRToDefinition
78+
const auto result = this->runGlassMethod<::glean::USRSymbolDefinition>(
79+
"clangUSRToDefinition",
80+
msg,
81+
[&]() -> folly::coro::Task<::glean::USRSymbolDefinition> {
82+
::glean::RequestOptions ro;
83+
RpcOptions rpcOptions;
84+
rpcOptions.setTimeout(std::chrono::milliseconds(GlassTimeoutMs));
85+
co_return co_await client->co_clangUSRToDefinition(
86+
rpcOptions, hashedUSR, ro);
87+
});
4988

5089
if (!result.has_value()) {
5190
return std::nullopt;
5291
}
5392

93+
return convertUSRSymbolDefinitionToLocations(result.value());
94+
}
95+
96+
protocol::LocationList GlassAccess::convertUSRSymbolDefinitionToLocations(
97+
const ::glean::USRSymbolDefinition& definition) const {
5498
// Convert Glass result to protocol::LocationList
5599
protocol::LocationList locations;
56-
const auto& definition = result.value();
57100

58101
// Extract location from USRSymbolDefinition
59102
const auto& gleanLocation = definition.location_ref().value();

glean/client/swift/GlassAccess.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ class GlassAccess : public IGlassAccess {
2222
const std::string& usr) override;
2323

2424
protected:
25+
std::optional<protocol::LocationList> handleUSR(
26+
const std::string& usr,
27+
std::string& msg);
28+
std::optional<protocol::LocationList> handleUSRHash(
29+
const std::string& usr,
30+
std::string& msg);
31+
protocol::LocationList convertUSRSymbolDefinitionToLocations(
32+
const ::glean::USRSymbolDefinition& definition) const;
2533
template <typename T>
2634
std::optional<T> runGlassMethod(
2735
const std::string& method,

glean/client/swift/hash.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include "glean/client/swift/hash.h"
10+
#include <folly/String.h>
11+
#include <folly/ssl/OpenSSLHash.h>
12+
#include <algorithm>
13+
#include <array>
14+
#include <cctype>
15+
#include <string>
16+
17+
namespace facebook::glean::clangx::hash {
18+
19+
std::string hash(const std::string& input) {
20+
// Use folly's SHA1 implementation
21+
std::array<uint8_t, SHA_DIGEST_LENGTH> digest{};
22+
folly::ssl::OpenSSLHash::sha1(
23+
folly::MutableByteRange(digest.data(), digest.size()),
24+
folly::ByteRange(
25+
reinterpret_cast<const uint8_t*>(input.data()), input.size()));
26+
27+
// Convert first 8 bytes to hex string (16 hex characters)
28+
std::string result = folly::hexlify(folly::ByteRange(digest.data(), 8));
29+
30+
// Convert to uppercase to match LLVM's toHex behavior
31+
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
32+
33+
return result;
34+
}
35+
36+
} // namespace facebook::glean::clangx::hash

glean/client/swift/hash.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#pragma once
10+
11+
#include <string>
12+
13+
namespace facebook::glean::clangx::hash {
14+
15+
std::string hash(const std::string& input);
16+
17+
} // namespace facebook::glean::clangx::hash
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include "glean/client/swift/hash.h"
10+
#include <gtest/gtest.h>
11+
#include <string>
12+
13+
class HashTest : public ::testing::Test {
14+
protected:
15+
void SetUp() override {}
16+
void TearDown() override {}
17+
};
18+
19+
TEST_F(HashTest, EmptyString) {
20+
// Test hash of empty string
21+
std::string input;
22+
std::string result = facebook::glean::clangx::hash::hash(input);
23+
24+
// SHA1 of empty string should be DA39A3EE5E6B4B0D3255BFEF95601890AFD80709
25+
// First 8 bytes (16 hex chars) should be: DA39A3EE5E6B4B0D
26+
EXPECT_EQ(result.length(), 16);
27+
EXPECT_EQ(result, "DA39A3EE5E6B4B0D");
28+
}
29+
30+
TEST_F(HashTest, SingleCharacter) {
31+
// Test hash of single character
32+
std::string input = "a";
33+
std::string result = facebook::glean::clangx::hash::hash(input);
34+
35+
// SHA1 of "a" should be 86F7E437FAA5A7FCE15D1DDCB9EAEAEA377667B8
36+
// First 8 bytes (16 hex chars) should be: 86F7E437FAA5A7FC
37+
EXPECT_EQ(result.length(), 16);
38+
EXPECT_EQ(result, "86F7E437FAA5A7FC");
39+
}
40+
41+
TEST_F(HashTest, SimpleString) {
42+
// Test hash of simple string
43+
std::string input = "abc";
44+
std::string result = facebook::glean::clangx::hash::hash(input);
45+
46+
// SHA1 of "abc" should be A9993E364706816ABA3E25717850C26C9CD0D89D
47+
// First 8 bytes (16 hex chars) should be: A9993E364706816A
48+
EXPECT_EQ(result.length(), 16);
49+
EXPECT_EQ(result, "A9993E364706816A");
50+
}
51+
52+
TEST_F(HashTest, LongerString) {
53+
// Test hash of longer string
54+
std::string input = "The quick brown fox jumps over the lazy dog";
55+
std::string result = facebook::glean::clangx::hash::hash(input);
56+
57+
// SHA1 of this string should be 2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12
58+
// First 8 bytes (16 hex chars) should be: 2FD4E1C67A2D28FC
59+
EXPECT_EQ(result.length(), 16);
60+
EXPECT_EQ(result, "2FD4E1C67A2D28FC");
61+
}
62+
63+
TEST_F(HashTest, SwiftUSRExample) {
64+
// Test hash of a typical Swift USR
65+
std::string input =
66+
"s:12IGFriendsMap0aB4ViewC03mapC0So05MKMapC0CvgAFyXEfU_ADL_AFvp";
67+
std::string result = facebook::glean::clangx::hash::hash(input);
68+
69+
// Verify it produces a valid 16-character uppercase hex string
70+
EXPECT_EQ(result.length(), 16);
71+
72+
// Verify all characters are valid uppercase hex
73+
for (char c : result) {
74+
EXPECT_TRUE((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))
75+
<< "Invalid hex character: " << c;
76+
}
77+
}
78+
79+
TEST_F(HashTest, CppUSRExample) {
80+
// Test hash of a typical C++ USR
81+
std::string input = "c:@N@std@S@vector>#T#$@N@std@S@allocator>#T";
82+
std::string result = facebook::glean::clangx::hash::hash(input);
83+
84+
// Verify it produces a valid 16-character uppercase hex string
85+
EXPECT_EQ(result.length(), 16);
86+
87+
// Verify all characters are valid uppercase hex
88+
for (char c : result) {
89+
EXPECT_TRUE((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))
90+
<< "Invalid hex character: " << c;
91+
}
92+
}
93+
94+
TEST_F(HashTest, SpecialCharacters) {
95+
// Test hash with special characters
96+
std::string input = "!@#$%^&*()_+-=[]{}|;':\",./<>?";
97+
std::string result = facebook::glean::clangx::hash::hash(input);
98+
99+
// Verify it produces a valid 16-character uppercase hex string
100+
EXPECT_EQ(result.length(), 16);
101+
102+
// Verify all characters are valid uppercase hex
103+
for (char c : result) {
104+
EXPECT_TRUE((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))
105+
<< "Invalid hex character: " << c;
106+
}
107+
}
108+
109+
TEST_F(HashTest, UnicodeCharacters) {
110+
// Test hash with Unicode characters
111+
std::string input = "Hello 世界 🌍";
112+
std::string result = facebook::glean::clangx::hash::hash(input);
113+
114+
// Verify it produces a valid 16-character uppercase hex string
115+
EXPECT_EQ(result.length(), 16);
116+
117+
// Verify all characters are valid uppercase hex
118+
for (char c : result) {
119+
EXPECT_TRUE((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))
120+
<< "Invalid hex character: " << c;
121+
}
122+
}
123+
124+
TEST_F(HashTest, LongString) {
125+
// Test hash of very long string (> 64 bytes to test multi-block processing)
126+
std::string input = std::string(1000, 'x'); // 1000 'x' characters
127+
std::string result = facebook::glean::clangx::hash::hash(input);
128+
129+
// Verify it produces a valid 16-character uppercase hex string
130+
EXPECT_EQ(result.length(), 16);
131+
132+
// Verify all characters are valid uppercase hex
133+
for (char c : result) {
134+
EXPECT_TRUE((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))
135+
<< "Invalid hex character: " << c;
136+
}
137+
}
138+
139+
TEST_F(HashTest, Consistency) {
140+
// Test that the same input always produces the same hash
141+
std::string input = "consistency_test_string";
142+
std::string result1 = facebook::glean::clangx::hash::hash(input);
143+
std::string result2 = facebook::glean::clangx::hash::hash(input);
144+
std::string result3 = facebook::glean::clangx::hash::hash(input);
145+
146+
EXPECT_EQ(result1, result2);
147+
EXPECT_EQ(result2, result3);
148+
EXPECT_EQ(result1, result3);
149+
}
150+
151+
TEST_F(HashTest, DifferentInputsDifferentHashes) {
152+
// Test that different inputs produce different hashes
153+
std::string input1 = "test1";
154+
std::string input2 = "test2";
155+
std::string input3 = "test11"; // Similar but different
156+
157+
std::string result1 = facebook::glean::clangx::hash::hash(input1);
158+
std::string result2 = facebook::glean::clangx::hash::hash(input2);
159+
std::string result3 = facebook::glean::clangx::hash::hash(input3);
160+
161+
EXPECT_NE(result1, result2);
162+
EXPECT_NE(result2, result3);
163+
EXPECT_NE(result1, result3);
164+
}
165+
166+
TEST_F(HashTest, CaseSensitivity) {
167+
// Test that hash is case sensitive
168+
std::string input1 = "Test";
169+
std::string input2 = "test";
170+
std::string input3 = "TEST";
171+
172+
std::string result1 = facebook::glean::clangx::hash::hash(input1);
173+
std::string result2 = facebook::glean::clangx::hash::hash(input2);
174+
std::string result3 = facebook::glean::clangx::hash::hash(input3);
175+
176+
EXPECT_NE(result1, result2);
177+
EXPECT_NE(result2, result3);
178+
EXPECT_NE(result1, result3);
179+
}
180+
181+
TEST_F(HashTest, OutputFormat) {
182+
// Test that output is always uppercase hex
183+
std::string input = "format_test";
184+
std::string result = facebook::glean::clangx::hash::hash(input);
185+
186+
// Should be exactly 16 characters
187+
EXPECT_EQ(result.length(), 16);
188+
189+
// Should contain only uppercase hex characters
190+
for (char c : result) {
191+
EXPECT_TRUE((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))
192+
<< "Invalid hex character: " << c;
193+
}
194+
}
195+
196+
TEST_F(HashTest, KnownVectorValidation) {
197+
// Test against known SHA1 test vectors to ensure correctness
198+
199+
// Test vector 1: "abc"
200+
EXPECT_EQ(facebook::glean::clangx::hash::hash("abc"), "A9993E364706816A");
201+
202+
// Test vector 2: ""
203+
EXPECT_EQ(facebook::glean::clangx::hash::hash(""), "DA39A3EE5E6B4B0D");
204+
205+
// Test vector 3: "a"
206+
EXPECT_EQ(facebook::glean::clangx::hash::hash("a"), "86F7E437FAA5A7FC");
207+
208+
// Test vector 4: "message digest"
209+
EXPECT_EQ(
210+
facebook::glean::clangx::hash::hash("message digest"),
211+
"C12252CEDA8BE899");
212+
213+
// Test vector 5: "abcdefghijklmnopqrstuvwxyz"
214+
EXPECT_EQ(
215+
facebook::glean::clangx::hash::hash("abcdefghijklmnopqrstuvwxyz"),
216+
"32D10C7B8CF96570");
217+
}

0 commit comments

Comments
 (0)