-
Notifications
You must be signed in to change notification settings - Fork 172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Trying to intern a string multiple times may lead to duplicates #5196
Changes from 7 commits
7d09942
5634669
c1d56fa
1ad3c95
c38bfac
f58b725
36b58e2
10e2602
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -224,12 +224,14 @@ void ChangesetEncoder::operator()(const Instruction::SetErase& instr) | |
|
||
InternString ChangesetEncoder::intern_string(StringData str) | ||
{ | ||
auto it = m_intern_strings_rev.find(str); | ||
auto it = m_intern_strings_rev.find(static_cast<std::string_view>(str)); | ||
if (it == m_intern_strings_rev.end()) { | ||
size_t index = m_intern_strings_rev.size(); | ||
// FIXME: Assert might be able to be removed after refactoring of changeset_parser types? | ||
REALM_ASSERT_RELEASE_EX(index <= std::numeric_limits<uint32_t>::max(), index); | ||
it = m_intern_strings_rev.insert({std::string{str}, uint32_t(index)}).first; | ||
bool inserted; | ||
std::tie(it, inserted) = m_intern_strings_rev.insert({std::string{str}, uint32_t(index)}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In other places we just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. structured bindings won't work if one of the bindings has already been defined as a variable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of course. |
||
REALM_ASSERT_RELEASE_EX(inserted, str); | ||
|
||
StringBufferRange range = add_string_range(str); | ||
set_intern_string(uint32_t(index), range); | ||
|
@@ -248,7 +250,7 @@ void ChangesetEncoder::set_intern_string(uint32_t index, StringBufferRange range | |
|
||
StringBufferRange ChangesetEncoder::add_string_range(StringData data) | ||
{ | ||
m_string_range = data; | ||
m_string_range = static_cast<std::string_view>(data); | ||
REALM_ASSERT(data.size() <= std::numeric_limits<uint32_t>::max()); | ||
return StringBufferRange{0, uint32_t(data.size())}; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,9 @@ struct ChangesetParser::State { | |
|
||
StringBuffer m_buffer; | ||
util::metered::set<uint32_t> m_valid_interned_strings; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want this PR to go on for forever, but at this point it's probably cheaper to just make change these two util::metered::set's into a Something like:
Anyways, I know this PR has been going on for forever, so we don't need to do this here/now - just something that came to mind while I was reading this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure how having the vector sorted by the intern index will help. We don't really need to have them sorted in any way so I would normally use an |
||
// Cannot use StringData as key type since m_input_begin may start pointing | ||
// to a new chunk of memory. | ||
util::metered::set<std::string> m_intern_strings; | ||
|
||
|
||
void parse_one(); // Throws | ||
|
@@ -290,9 +293,18 @@ void ChangesetParser::State::parse_one() | |
if (t == InstrTypeInternString) { | ||
uint32_t index = read_int<uint32_t>(); | ||
StringData str = read_string(); | ||
auto it1 = m_intern_strings.find(static_cast<std::string_view>(str)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is def a nit, but you can clean this iterator use up a little bit by doing:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My reason was to avoid long/multi-line if statements. I agree it looks cleaner though. I'll make the change. |
||
if (it1 != m_intern_strings.end()) { | ||
parser_error("Unexpected intern string"); | ||
} | ||
auto it2 = m_valid_interned_strings.find(index); | ||
if (it2 != m_valid_interned_strings.end()) { | ||
parser_error("Unexpected intern index"); | ||
} | ||
StringBufferRange range = m_handler.add_string_range(str); | ||
m_handler.set_intern_string(index, range); | ||
m_valid_interned_strings.emplace(index); | ||
m_intern_strings.emplace(std::string{str}); | ||
return; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5733,6 +5733,56 @@ TEST(Sync_BadChangeset) | |
} | ||
|
||
|
||
TEST(Sync_GoodChangeset_AccentCharacterInFieldName) | ||
{ | ||
TEST_DIR(dir); | ||
TEST_CLIENT_DB(db); | ||
|
||
bool did_fail = false; | ||
{ | ||
ClientServerFixture::Config config; | ||
config.disable_upload_compaction = true; | ||
ClientServerFixture fixture(dir, test_context, std::move(config)); | ||
fixture.start(); | ||
|
||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to do this step where we wait for download complete? I think this should be a no-op. |
||
Session session = fixture.make_bound_session(db); | ||
session.wait_for_download_complete_or_client_stopped(); | ||
} | ||
|
||
{ | ||
WriteTransaction wt(db); | ||
TableRef table = wt.add_table("class_table"); | ||
table->add_column(type_Int, "prógram"); | ||
table->add_column(type_Int, "program"); | ||
auto obj = table->create_object(); | ||
obj.add_int("program", 42); | ||
wt.commit(); | ||
} | ||
|
||
auto listener = [&](ConnectionState state, const Session::ErrorInfo* error_info) { | ||
if (state != ConnectionState::disconnected) | ||
return; | ||
REALM_ASSERT(error_info); | ||
std::error_code ec = error_info->error_code; | ||
bool is_fatal = error_info->is_fatal; | ||
CHECK_EQUAL(sync::ProtocolError::bad_changeset, ec); | ||
CHECK(is_fatal); | ||
fixture.stop(); | ||
did_fail = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd move There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems some tests were inconsistent so I modified them as well. |
||
}; | ||
|
||
Session session = fixture.make_session(db); | ||
session.set_connection_state_change_listener(listener); | ||
fixture.bind_session(session, "/test"); | ||
|
||
session.wait_for_upload_complete_or_client_stopped(); | ||
session.wait_for_download_complete_or_client_stopped(); | ||
} | ||
CHECK_NOT(did_fail); | ||
} | ||
|
||
|
||
namespace issue2104 { | ||
|
||
class IntegrationReporter : public _impl::ServerHistory::IntegrationReporter { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯