Skip to content

Commit 53d48d0

Browse files
CXX-801 Use .terminated().data() to extract safe c-strings
1 parent 4c52f54 commit 53d48d0

File tree

6 files changed

+84
-29
lines changed

6 files changed

+84
-29
lines changed

src/bsoncxx/string/view_or_value.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,19 @@ namespace bsoncxx {
2020
BSONCXX_INLINE_NAMESPACE_BEGIN
2121
namespace string {
2222

23-
const char* view_or_value::c_str() {
23+
view_or_value view_or_value::terminated() const {
2424
// If we do not own our string, we cannot guarantee that it is null-terminated,
2525
// so make an owned copy.
26-
if (!_value) {
27-
_value = std::move(_view.to_string());
28-
_view = *_value;
26+
if (!is_owning()) {
27+
return view_or_value(std::move(view().to_string()));
2928
}
3029

31-
return _value->c_str();
30+
// If we are owning, return a view_or_value viewing our string
31+
return {view()};
32+
}
33+
34+
const char* view_or_value::data() const {
35+
return view().data();
3236
}
3337

3438
} // namespace string

src/bsoncxx/string/view_or_value.hpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,25 @@ class BSONCXX_API view_or_value : public bsoncxx::view_or_value<stdx::string_vie
6666
}
6767

6868
///
69-
/// Return a null-terminated string from this string::view_or_value.
69+
/// Return a string_view_or_value that is guaranteed to hold a null-terminated
70+
/// string. The lifetime of the returned object must be a subset of this object's
71+
/// lifetime, because the new view_or_value might hold a view into this one.
7072
///
71-
/// @note if we are non-owning, we cannot assume our string_view is null-terminated.
72-
/// In this case we must make a new, owning view_or_value with a null-terminated
73-
/// copy of our view.
73+
/// It is recommended that this method be used before calling .data() on a
74+
/// view_or_value, as that method may return a non-null-terminated string.
7475
///
75-
/// @return A pointer to the original string, or a null-terminated copy.
76+
/// @return A new view_or_value object.
7677
///
77-
const char* c_str();
78+
view_or_value terminated() const;
79+
80+
///
81+
/// Call data() on this view_or_value's string_view. This method is not
82+
/// guaranteed to return a null-terminated string unless it is used in
83+
/// combination with terminated().
84+
///
85+
/// @return A const char* of this string.
86+
///
87+
const char* data() const;
7888
};
7989

8090
///

src/bsoncxx/test/view_or_value.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,38 @@ TEST_CASE("string::document::view_or_value", "[bsoncxx::string::view_or_value]")
226226
REQUIRE(carlin != other_name);
227227
REQUIRE(other_name != carlin);
228228
}
229+
230+
SECTION("has a terminated() method that returns a copy") {
231+
SECTION("when owning, copy is non-owning") {
232+
std::string name{"Rob"};
233+
string::view_or_value original{std::move(name)};
234+
235+
string::view_or_value copy{original.terminated()};
236+
REQUIRE(copy == "Rob");
237+
238+
char* original_name = const_cast<char*>(original.data());
239+
original_name[0] = 'B';
240+
241+
// copy should also reflect the change
242+
REQUIRE(copy == "Bob");
243+
}
244+
245+
SECTION("when non-owning, copy is owning") {
246+
std::string name{"Sam"};
247+
string::view_or_value original{name};
248+
249+
string::view_or_value copy{original.terminated()};
250+
REQUIRE(copy == "Sam");
251+
252+
char* original_name = const_cast<char*>(original.data());
253+
original_name[0] = 'P';
254+
255+
// copy should not reflect the change
256+
REQUIRE(copy == "Sam");
257+
258+
SECTION("and null-terminated") {
259+
REQUIRE(copy.data()[3] == '\0');
260+
}
261+
}
262+
}
229263
}

src/bsoncxx/view_or_value.hpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ class view_or_value {
9191

9292
/// TODO CXX-800: Create a noexcept expression to check the conditions that must be met.
9393
BSONCXX_INLINE view_or_value(view_or_value&& other) noexcept
94-
: _value{std::move(other._value)}, _view(_value ? *_value : std::move(other._view)) {
94+
: _value{std::move(other._value)},
95+
_view(_value ? *_value : std::move(other._view)) {
9596
other._view = View();
9697
other._value = stdx::nullopt;
9798
}
@@ -108,6 +109,15 @@ class view_or_value {
108109
return *this;
109110
}
110111

112+
///
113+
/// Return whether or not this view_or_value owns an underlying Value.
114+
///
115+
/// @return bool Whether we are owning.
116+
///
117+
BSONCXX_INLINE bool is_owning() const noexcept {
118+
return static_cast<bool>(_value);
119+
}
120+
111121
///
112122
/// This type may be used as a View.
113123
///
@@ -121,8 +131,7 @@ class view_or_value {
121131
return _view;
122132
}
123133

124-
// TODO: make these private again (CXX-801)
125-
protected:
134+
private:
126135
stdx::optional<Value> _value;
127136
View _view;
128137
};

src/mongocxx/collection.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,20 @@ stdx::string_view collection::name() const {
131131
void collection::rename(bsoncxx::string::view_or_value new_name, bool drop_target_before_rename) {
132132
bson_error_t error;
133133

134-
auto result =
135-
libmongoc::collection_rename(_get_impl().collection_t, _get_impl().database_name.c_str(),
136-
new_name.c_str(), drop_target_before_rename, &error);
134+
auto result = libmongoc::collection_rename(
135+
_get_impl().collection_t, _get_impl().database_name.c_str(), new_name.terminated().data(),
136+
drop_target_before_rename, &error);
137137

138138
if (!result) {
139139
throw_exception<operation_exception>(error);
140140
}
141141
}
142142

143143
collection::collection(const database& database, bsoncxx::string::view_or_value collection_name)
144-
: _impl(stdx::make_unique<impl>(libmongoc::database_get_collection(
145-
database._get_impl().database_t, collection_name.c_str()),
146-
database.name(), database._get_impl().client_impl)) {
144+
: _impl(stdx::make_unique<impl>(
145+
libmongoc::database_get_collection(database._get_impl().database_t,
146+
collection_name.terminated().data()),
147+
database.name(), database._get_impl().client_impl)) {
147148
}
148149

149150
collection::collection(const database& database, void* collection)
@@ -290,7 +291,6 @@ cursor collection::aggregate(const pipeline& pipeline, const options::aggregate&
290291

291292
stdx::optional<result::insert_one> collection::insert_one(view_or_value document,
292293
const options::insert& options) {
293-
294294
// TODO: We should consider making it possible to convert from an options::insert into
295295
// an options::bulk_write at the type level, removing the need to re-iterate this code
296296
// many times here and below.
@@ -382,7 +382,6 @@ stdx::optional<result::update> collection::update_many(view_or_value filter, vie
382382

383383
stdx::optional<result::delete_result> collection::delete_many(
384384
view_or_value filter, const options::delete_options& options) {
385-
386385
options::bulk_write bulk_opts;
387386
bulk_opts.ordered(false);
388387

@@ -403,7 +402,6 @@ stdx::optional<result::delete_result> collection::delete_many(
403402

404403
stdx::optional<result::update> collection::update_one(view_or_value filter, view_or_value update,
405404
const options::update& options) {
406-
407405
options::bulk_write bulk_opts;
408406
bulk_opts.ordered(false);
409407

@@ -428,7 +426,6 @@ stdx::optional<result::update> collection::update_one(view_or_value filter, view
428426

429427
stdx::optional<result::delete_result> collection::delete_one(
430428
view_or_value filter, const options::delete_options& options) {
431-
432429
options::bulk_write bulk_opts;
433430
bulk_opts.ordered(false);
434431

src/mongocxx/database.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ database::~database() = default;
4848

4949
database::database(const class client& client, bsoncxx::string::view_or_value name)
5050
: _impl(stdx::make_unique<impl>(
51-
libmongoc::client_get_database(client._get_impl().client_t, name.c_str()),
52-
&client._get_impl(), name.c_str())) {
51+
libmongoc::client_get_database(client._get_impl().client_t, name.terminated().data()),
52+
&client._get_impl(), name.terminated().data())) {
5353
}
5454

5555
database::database(const database& d) {
@@ -115,8 +115,8 @@ class collection database::create_collection(bsoncxx::string::view_or_value name
115115
bson_error_t error;
116116

117117
libbson::scoped_bson_t opts_bson{options.to_document()};
118-
auto result = libmongoc::database_create_collection(_get_impl().database_t, name.c_str(),
119-
opts_bson.bson(), &error);
118+
auto result = libmongoc::database_create_collection(
119+
_get_impl().database_t, name.terminated().data(), opts_bson.bson(), &error);
120120

121121
if (!result) {
122122
throw_exception<operation_exception>(error);
@@ -151,7 +151,8 @@ void database::read_preference(class read_preference rp) {
151151

152152
bool database::has_collection(bsoncxx::string::view_or_value name) const {
153153
bson_error_t error;
154-
return libmongoc::database_has_collection(_get_impl().database_t, name.c_str(), &error);
154+
return libmongoc::database_has_collection(_get_impl().database_t, name.terminated().data(),
155+
&error);
155156
}
156157

157158
class read_preference database::read_preference() const {

0 commit comments

Comments
 (0)