summaryrefslogtreecommitdiff
path: root/src/mongo/bson
diff options
context:
space:
mode:
authorLucas de Castro Borges <lucas@gnuabordo.com.br>2025-02-18 17:02:53 -0300
committerLucas de Castro Borges <lucas@gnuabordo.com.br>2025-02-18 17:02:53 -0300
commit959575a5ca598bf5f37fb5cebe7ed1d80d3d71f7 (patch)
treeacc8d60aedb12b70048e676e8a7349deb0010db8 /src/mongo/bson
parent76588293975fc059cf076779e4283e6ffaf8afff (diff)
New upstream version 6.0.20upstream
Diffstat (limited to 'src/mongo/bson')
-rw-r--r--src/mongo/bson/SConscript17
-rw-r--r--src/mongo/bson/bson_validate.cpp129
-rw-r--r--src/mongo/bson/bson_validate.h22
-rw-r--r--src/mongo/bson/bson_validate.idl61
-rw-r--r--src/mongo/bson/bson_validate_test.cpp23
-rw-r--r--src/mongo/bson/bsonelement.cpp59
-rw-r--r--src/mongo/bson/bsonelement.h7
-rw-r--r--src/mongo/bson/bsonelement_test.cpp50
-rw-r--r--src/mongo/bson/bsonobj.cpp70
-rw-r--r--src/mongo/bson/bsonobj.h11
-rw-r--r--src/mongo/bson/bsonobjbuilder.cpp2
-rw-r--r--src/mongo/bson/bsonobjbuilder.h62
-rw-r--r--src/mongo/bson/bsontypes.cpp3
-rw-r--r--src/mongo/bson/bsontypes.h5
-rw-r--r--src/mongo/bson/simple_bsonobj_comparator.h4
-rw-r--r--src/mongo/bson/timestamp.h2
-rw-r--r--src/mongo/bson/util/builder.h44
-rw-r--r--src/mongo/bson/util/builder_test.cpp33
-rw-r--r--src/mongo/bson/util/simple8b_test.cpp3
19 files changed, 452 insertions, 155 deletions
diff --git a/src/mongo/bson/SConscript b/src/mongo/bson/SConscript
index 31eb607823c..472aed01ad3 100644
--- a/src/mongo/bson/SConscript
+++ b/src/mongo/bson/SConscript
@@ -28,6 +28,7 @@ env.CppUnitTest(
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/bson/bson_validate',
'$BUILD_DIR/mongo/bson/util/bson_column',
],
)
@@ -39,6 +40,7 @@ env.Benchmark(
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/bson/bson_validate',
],
)
@@ -50,6 +52,21 @@ env.CppLibfuzzerTest(
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/bson/bson_validate',
+ ],
+)
+
+env.Library(
+ target='bson_validate',
+ source=[
+ 'bson_validate.cpp',
+ 'bson_validate.idl',
+ ],
+ LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/bson/util/bson_column',
+ '$BUILD_DIR/mongo/idl/idl_parser',
+ '$BUILD_DIR/mongo/idl/server_parameter',
],
)
diff --git a/src/mongo/bson/bson_validate.cpp b/src/mongo/bson/bson_validate.cpp
index 726a12b818c..da4b54ef194 100644
--- a/src/mongo/bson/bson_validate.cpp
+++ b/src/mongo/bson/bson_validate.cpp
@@ -83,9 +83,15 @@ constexpr ErrorCodes::Error InvalidBSON = ErrorCodes::InvalidBSON;
constexpr ErrorCodes::Error NonConformantBSON = ErrorCodes::NonConformantBSON;
template <bool precise>
+Status _doValidateColumn(const char* originalBuffer,
+ uint64_t maxLength,
+ ValidationVersion validationVersion);
+
+template <bool precise>
class ValidateBuffer {
public:
- ValidateBuffer(const char* data, uint64_t maxLength) : _data(data), _maxLength(maxLength) {
+ ValidateBuffer(const char* data, uint64_t maxLength, ValidationVersion validationVersion)
+ : _data(data), _maxLength(maxLength), _validationVersion(validationVersion) {
if constexpr (precise)
_frames.resize(BSONDepth::getMaxAllowableDepth() + 1);
}
@@ -127,30 +133,21 @@ public:
// Handle one element without using iterative loop, and without expecting
// multiple instances or an EOO. Only resume with the iterative loop if
- // the frame stack has been incremented, meaning we have nested objects
-
- // Save pointer to currFrame->end so we can fill it in once we know the size
- const char** preEnd = &(_currFrame->end);
- const char* ptr = _validateElem(Cursor{_data + 2, _data + _maxLength}, *_data);
+ // we have nested objects
+ _currElem = _data;
+ const char* ptr = _validateElem<false>(Cursor{_data + 2, _data + _maxLength}, *_data);
- if (_currFrame != _frames.begin()) {
- // We know that type was kObject or kArray, so size is fieldname, type,
- // and a stored int
+ if (_firstFrameUpdated) {
+ // We know that type was kObject/kArray/kCodeWScope
+ // Size is fieldname, type, and a stored int
int64_t size =
static_cast<int64_t>(ConstDataView(_data + 2).read<LittleEndian<int32_t>>()) + 2;
uassert(InvalidBSON,
"BSON literal content exceeds buffer size",
(size_t)size <= _maxLength);
- *preEnd = _data + size;
- const char* internalEnd = _currFrame->end;
- _popFrame();
- uassert(InvalidBSON,
- "BSON literal nested content does not end at external end",
- _currFrame->end == internalEnd);
_validateIterative(Cursor{ptr, _data + size});
return size;
} else {
- *preEnd = ptr;
return ptr - _data;
}
}
@@ -216,7 +213,10 @@ private:
uassert(ErrorCodes::Overflow,
"BSONObj exceeds maximum nested object depth",
++_currFrame != _frames.end());
+ return _updateFrame(cursor);
+ }
+ const char* _updateFrame(Cursor cursor) {
auto obj = cursor.ptr;
auto len = cursor.template read<int32_t>();
uassert(ErrorCodes::InvalidBSON, "Nested BSON object has to be at least 5 bytes", len >= 5);
@@ -236,18 +236,19 @@ private:
return true;
}
- static const char* _validateSpecial(Cursor cursor, uint8_t type) {
+ const char* _validateSpecial(Cursor cursor, uint8_t type) {
switch (type) {
case BSONType::BinData: {
auto count = cursor.template read<uint32_t>();
auto subtype = cursor.template read<uint8_t>();
const char* columnStart = cursor.ptr;
cursor.skip(count);
- if (subtype == BinDataType::Column) {
+ if (subtype == BinDataType::Column && _validationVersion >= V2_Column) {
/* do not pass down cursor; we want to reset the nesting depth */
- uassert(NonConformantBSON,
- "Invalid BSON column",
- validateBSONColumn(columnStart, count).isOK());
+ uassert(
+ NonConformantBSON,
+ "Invalid BSON column",
+ _doValidateColumn<precise>(columnStart, count, _validationVersion).isOK());
}
break;
}
@@ -274,10 +275,15 @@ private:
return cursor.ptr;
}
+ template <bool nestedFrame>
const char* _pushCodeWithScope(Cursor cursor) {
- cursor.ptr = _pushFrame(cursor); // Push a dummy frame to check the CodeWScope size.
- cursor.skipString(); // Now skip the BSON UTF8 string containing the code.
- _currElem = cursor.ptr - 1; // Use the terminating NUL as adummy scope element.
+ // Push a dummy frame to check the CodeWScope size.
+ if constexpr (nestedFrame)
+ cursor.ptr = _pushFrame(cursor);
+ else
+ cursor.ptr = _updateFrame(cursor);
+ cursor.skipString(); // Now skip the BSON UTF8 string containing the code.
+ _currElem = cursor.ptr - 1; // Use the terminating NUL as a dummy scope element.
return _pushFrame(cursor);
}
@@ -291,21 +297,30 @@ private:
}
}
+ template <bool nestedFrame>
const char* _validateElem(Cursor cursor, uint8_t type) {
if (MONGO_unlikely(type > JSTypeMax))
return _validateSpecial(cursor, type);
auto style = kTypeInfoTable[type];
- if (MONGO_likely(style <= kSkip16))
+ if (MONGO_likely(style <= kSkip16)) {
cursor.skip(style * 4);
- else if (MONGO_likely(style == kString))
+ } else if (MONGO_likely(style == kString)) {
cursor.skipString();
- else if (MONGO_likely(style == kObjectOrArray))
- cursor.ptr = _pushFrame(cursor);
- else if (MONGO_unlikely(precise && type == CodeWScope))
- cursor.ptr = _pushCodeWithScope(cursor);
- else
+ } else if (MONGO_likely(style == kObjectOrArray)) {
+ if constexpr (nestedFrame) {
+ cursor.ptr = _pushFrame(cursor);
+ } else {
+ cursor.ptr = _updateFrame(cursor);
+ _firstFrameUpdated = true;
+ }
+ } else if (MONGO_unlikely(precise && type == CodeWScope)) {
+ cursor.ptr = _pushCodeWithScope<nestedFrame>(cursor);
+ if constexpr (!nestedFrame)
+ _firstFrameUpdated = true;
+ } else {
cursor.ptr = _validateSpecial(cursor, type);
+ }
return cursor.ptr;
}
@@ -319,7 +334,7 @@ private:
uint8_t type = *cursor.ptr;
_currElem = cursor.ptr;
cursor.ptr += len + 1;
- cursor.ptr = _validateElem(cursor, type);
+ cursor.ptr = _validateElem<true>(cursor, type);
if constexpr (precise) {
// See if the _id field was just validated. If so, set the global scope element.
@@ -361,11 +376,16 @@ private:
const char* _currElem = nullptr; // Element to validate: only the name is known to be good.
typename Frames::iterator _currFrame; // Frame currently being validated.
Frames _frames; // Has end pointers to check and the containing element for precise mode.
+ bool _firstFrameUpdated = false; // Has the first frame received nested while measuring an elem
+ ValidationVersion _validationVersion;
};
+template <bool precise>
class ColumnValidator {
public:
- static Status doValidateBSONColumn(const char* originalBuffer, int maxLength) noexcept {
+ static Status doValidateBSONColumn(const char* originalBuffer,
+ int maxLength,
+ ValidationVersion validationVersion) noexcept {
// run control pointer through to end of buffer
// run over literal data as directed by lengths from control
// check formatting of Simple8B blocks
@@ -398,7 +418,8 @@ public:
return Status::OK();
}
} else if (bsoncolumn::isUncompressedLiteralControlByte(control)) {
- ptr += ValidateBuffer<false>(ptr, end - ptr).validateAndMeasureElem();
+ ptr += ValidateBuffer<precise>(ptr, end - ptr, validationVersion)
+ .validateAndMeasureElem();
} else if (bsoncolumn::isInterleavedStartControlByte(control)) {
// interleaved objects begin with a reference object, and then a series
// of diff blocks for followup objects, ending with an EOO. Nesting interleaved
@@ -430,20 +451,50 @@ public:
return Status(NonConformantBSON, "Missing terminating EOO");
}
};
+
+template <bool precise>
+Status _doValidateColumn(const char* originalBuffer,
+ uint64_t maxLength,
+ ValidationVersion validationVersion) {
+ if constexpr (precise) {
+ // First try validating using the fast but less precise version. That version will return
+ // a not-OK status for objects with CodeWScope or nesting exceeding 32 levels. These cases
+ // and actual failures will rerun the precise version that gives a detailed error context.
+ if (MONGO_likely(ColumnValidator<false>::doValidateBSONColumn(
+ originalBuffer, maxLength, validationVersion)
+ .isOK()))
+ return Status::OK();
+
+ return ColumnValidator<true>::doValidateBSONColumn(
+ originalBuffer, maxLength, validationVersion);
+ } else {
+ return ColumnValidator<false>::doValidateBSONColumn(
+ originalBuffer, maxLength, validationVersion);
+ }
+}
} // namespace
-Status validateBSON(const char* originalBuffer, uint64_t maxLength) noexcept {
+Status validateBSON(const char* originalBuffer,
+ uint64_t maxLength,
+ ValidationVersion validationVersion) noexcept {
// First try validating using the fast but less precise version. That version will return
// a not-OK status for objects with CodeWScope or nesting exceeding 32 levels. These cases and
// actual failures will rerun the precise version that gives a detailed error context.
- if (MONGO_likely(ValidateBuffer<false>(originalBuffer, maxLength).validate().isOK()))
+ if (MONGO_likely(
+ ValidateBuffer<false>(originalBuffer, maxLength, validationVersion).validate().isOK()))
return Status::OK();
- return ValidateBuffer<true>(originalBuffer, maxLength).validate();
+ return ValidateBuffer<true>(originalBuffer, maxLength, validationVersion).validate();
+}
+
+Status validateBSON(const BSONObj& obj, ValidationVersion validationVersion) noexcept {
+ return validateBSON(obj.objdata(), obj.objsize(), validationVersion);
}
-Status validateBSONColumn(const char* originalBuffer, int maxLength) noexcept {
- return ColumnValidator::doValidateBSONColumn(originalBuffer, maxLength);
+Status validateBSONColumn(const char* originalBuffer,
+ int maxLength,
+ ValidationVersion validationVersion) noexcept {
+ return _doValidateColumn<true>(originalBuffer, maxLength, validationVersion);
}
} // namespace mongo
diff --git a/src/mongo/bson/bson_validate.h b/src/mongo/bson/bson_validate.h
index a737f61b129..add1c18b55b 100644
--- a/src/mongo/bson/bson_validate.h
+++ b/src/mongo/bson/bson_validate.h
@@ -36,6 +36,17 @@
namespace mongo {
+enum ValidationVersion {
+ /* Original validator */
+ V1_Original = 1,
+ /* Adds validation for the content of Column-typed BinData */
+ V2_Column = 2
+};
+
+// When adding new versions of BSON validation, update both this and the range and the
+// default for the server parameter in src/mongo/bson/bson_validate.idl
+static constexpr ValidationVersion currentValidationVersion = V2_Column;
+
/**
* Checks that the buf holds a BSON object as defined in http://bsonspec.org/spec.html.
* Note that maxLength is the buffer size, NOT the BSON size.
@@ -49,8 +60,15 @@ namespace mongo {
* validity, code validity, correct length and formatting of binary subtypes, etc.
* Length is only limited by the buffer's maxLength and the inherent 2GB - 1 format limitation.
*/
-Status validateBSON(const char* buf, uint64_t maxLength) noexcept;
+Status validateBSON(const char* buf,
+ uint64_t maxLength,
+ ValidationVersion validationVersion = currentValidationVersion) noexcept;
+
+Status validateBSON(const BSONObj& obj,
+ ValidationVersion validationVersion = currentValidationVersion) noexcept;
-Status validateBSONColumn(const char* buf, int maxLength) noexcept;
+Status validateBSONColumn(const char* buf,
+ int maxLength,
+ ValidationVersion validationVersion = currentValidationVersion) noexcept;
} // namespace mongo
diff --git a/src/mongo/bson/bson_validate.idl b/src/mongo/bson/bson_validate.idl
new file mode 100644
index 00000000000..9d4bb7b9fbe
--- /dev/null
+++ b/src/mongo/bson/bson_validate.idl
@@ -0,0 +1,61 @@
+# Copyright (C) 2023-present MongoDB, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the Server Side Public License, version 1,
+# as published by MongoDB, Inc.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Server Side Public License for more details.
+#
+# You should have received a copy of the Server Side Public License
+# along with this program. If not, see
+# <http://www.mongodb.com/licensing/server-side-public-license>.
+#
+# As a special exception, the copyright holders give permission to link the
+# code of portions of this program with the OpenSSL library under certain
+# conditions as described in each individual source file and distribute
+# linked combinations including the program with the OpenSSL library. You
+# must comply with the Server Side Public License in all respects for
+# all of the code used other than as permitted herein. If you modify file(s)
+# with this exception, you may extend this exception to your version of the
+# file(s), but you are not obligated to do so. If you do not wish to do so,
+# delete this exception statement from your version. If you delete this
+# exception statement from all source files in the program, then also delete
+# it in the license file.
+#
+
+# BSON validate modes
+
+global:
+ cpp_namespace: "mongo"
+
+imports:
+ - "mongo/idl/basic_types.idl"
+
+enums:
+ BSONValidateMode:
+ description: "The validate mode that dbCheck will run for BSON consistency checks"
+ type: string
+ # kDefault: Only fast structural BSON consistency checks
+ # kExtended: Structural BSON consistency and extra fast checks on BSON specifications.
+ # kFull: Structural BSON consistency and extra comprehensive checks on BSON specifications.
+ values:
+ kDefault: "kDefault"
+ kExtended: "kExtended"
+ kFull: "kFull"
+
+# Server paramaters for validation.
+# Range and default value should be kept in sync with src/mongo/bson/bson_validate.h
+
+server_parameters:
+ bsonTestValidationVersion:
+ description: "The version of the BSON validator that will be used to check correctness of data in test environments"
+ set_at: startup
+ cpp_vartype: int
+ cpp_varname: bsonTestValidationVersion
+ validator:
+ gte: 1
+ lte: 2
+ default: 2 \ No newline at end of file
diff --git a/src/mongo/bson/bson_validate_test.cpp b/src/mongo/bson/bson_validate_test.cpp
index b105e0f58e2..29e5f1536d2 100644
--- a/src/mongo/bson/bson_validate_test.cpp
+++ b/src/mongo/bson/bson_validate_test.cpp
@@ -51,17 +51,17 @@ using std::unique_ptr;
void appendInvalidStringElement(const char* fieldName, BufBuilder* bb) {
// like a BSONObj string, but without a NUL terminator.
bb->appendChar(String);
- bb->appendStr(fieldName, /*withNUL*/ true);
+ bb->appendCStr(fieldName);
bb->appendNum(4);
- bb->appendStr("asdf", /*withNUL*/ false);
+ bb->appendStrBytes("asdf"); // Missing required final NUL.
}
TEST(BSONValidate, Basic) {
BSONObj x;
- ASSERT_TRUE(x.valid());
+ ASSERT_TRUE(validateBSON(x).isOK());
x = BSON("x" << 1);
- ASSERT_TRUE(x.valid());
+ ASSERT_TRUE(validateBSON(x).isOK());
}
TEST(BSONValidate, RandomData) {
@@ -87,7 +87,7 @@ TEST(BSONValidate, RandomData) {
ASSERT_EQUALS(size, o.objsize());
- if (o.valid()) {
+ if (validateBSON(o).isOK()) {
numValid++;
jsonSize += o.jsonString().size();
ASSERT_OK(validateBSON(o.objdata(), o.objsize()));
@@ -138,7 +138,7 @@ TEST(BSONValidate, MuckingData1) {
data[i] = 0xc8U;
numToRun++;
- if (mine.valid()) {
+ if (validateBSON(mine).isOK()) {
numValid++;
jsonSize += mine.jsonString().size();
ASSERT_OK(validateBSON(mine.objdata(), mine.objsize()));
@@ -368,7 +368,7 @@ TEST(BSONValidateFast, StringHasSomething) {
BufBuilder bb;
BSONObjBuilder ob(bb);
bb.appendChar(String);
- bb.appendStr("x", /*withNUL*/ true);
+ bb.appendCStr("x");
bb.appendNum(0);
const BSONObj x = ob.done();
ASSERT_EQUALS(5 // overhead
@@ -483,6 +483,7 @@ TEST_F(BSONValidateColumn, BSONColumnInBSON) {
TEST_F(BSONValidateColumn, BSONColumnMissingEOO) {
BSONColumnBuilder cb("");
+
cb.append(BSON("a"
<< "deadbeef")
.getField("a"));
@@ -694,4 +695,12 @@ TEST_F(BSONValidateColumn, BSONColumnBadExtendedSelector) {
ASSERT_OK(validateBSONColumn((char*)columnData.data, columnData.length));
}
+TEST(BSONValidateColumn, BSONColumnWithCodeWScope) {
+ BSONObj obj = BSON("a" << BSONCodeWScope("code", BSON("c" << 1)));
+ BSONColumnBuilder cb("");
+ cb.append(obj.getField("a"));
+ BSONBinData columnData = cb.finalize();
+ ASSERT_OK(validateBSONColumn((char*)columnData.data, columnData.length));
+}
+
} // namespace
diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp
index 2409084bcfc..0990b1a18c7 100644
--- a/src/mongo/bson/bsonelement.cpp
+++ b/src/mongo/bson/bsonelement.cpp
@@ -46,6 +46,7 @@
#include "mongo/logv2/log.h"
#include "mongo/platform/strnlen.h"
#include "mongo/util/base64.h"
+#include "mongo/util/decimal_counter.h"
#include "mongo/util/duration.h"
#include "mongo/util/hex.h"
#include "mongo/util/scopeguard.h"
@@ -444,6 +445,7 @@ int BSONElement::compareElements(const BSONElement& l,
*/
std::vector<BSONElement> BSONElement::Array() const {
chk(mongo::Array);
+
std::vector<BSONElement> v;
BSONObjIterator i(Obj());
while (i.more()) {
@@ -464,6 +466,23 @@ std::vector<BSONElement> BSONElement::Array() const {
return v;
}
+std::vector<BSONElement> BSONElement::ArrayVerifyIndexes() const {
+ chk(mongo::Array);
+
+ std::vector<BSONElement> v;
+ DecimalCounter<std::uint32_t> counter(0);
+ for (auto element : Obj()) {
+ auto fieldName = element.fieldNameStringData();
+ uassert(ErrorCodes::BadValue,
+ fmt::format(
+ "Invalid array index field name: \"{}\", expected \"{}\"", fieldName, counter),
+ fieldName == counter);
+ counter++;
+ v.push_back(element);
+ }
+ return v;
+}
+
int BSONElement::woCompare(const BSONElement& elem,
ComparisonRulesSet rules,
const StringData::ComparatorInterface* comparator) const {
@@ -646,41 +665,13 @@ BSONElement BSONElement::operator[](StringData field) const {
}
namespace {
-MONGO_COMPILER_NOINLINE void msgAssertedBadType [[noreturn]] (const char* data) {
- // We intentionally read memory that may be out of the allocated memory's boundary, so do not
- // do this when the address sanitizer is enabled. We do this in an attempt to log as much
- // context about the failure, even if that risks undefined behavior or a segmentation fault.
-#if !__has_feature(address_sanitizer)
- bool logMemory = true;
-#else
- bool logMemory = false;
-#endif
- str::stream output;
- if (!logMemory) {
- output << fmt::format("BSONElement: bad type {0:d} @ {1:p}", *data, data);
- } else {
- // To reduce the risk of a segmentation fault, only print the bytes in the 32-bit aligned
- // block in which the address is located (i.e. round down to the lowest multiple of 32). The
- // hope is that it's safe to read memory that may fall within the same cache line. Generate
- // a mask to zero-out the last bits for a block-aligned address.
- // Ex: Inverse of 0x1F (32 - 1) looks like 0xFFFFFFE0, and ANDed with the pointer, zeroes
- // the lowest 5 bits, giving the starting address of a 32-bit block.
- const size_t blockSize = 32;
- const size_t mask = ~(blockSize - 1);
- const char* startAddr =
- reinterpret_cast<const char*>(reinterpret_cast<uintptr_t>(data) & mask);
- const size_t offset = data - startAddr;
-
- output << fmt::format(
- "BSONElement: bad type {0:d} @ {1:p} at offset {2:d} in block: ", *data, data, offset);
-
- for (size_t i = 0; i < blockSize; i++) {
- output << fmt::format("{0:#x} ", static_cast<uint8_t>(startAddr[i]));
- }
- }
- msgasserted(10320, output);
+MONGO_COMPILER_NOINLINE void msgAssertedBadType [[noreturn]] (int8_t type) {
+ int err = 10320; // work around linter
+ LOGV2_ERROR(err, "BSONElement: bad type", "type"_attr = zeroPaddedHex(type));
+ uasserted(err, "BSONElement: bad type");
}
+
} // namespace
int BSONElement::computeSize(int8_t type, const char* elem, int fieldNameSize, int bufSize) {
@@ -751,7 +742,7 @@ int BSONElement::computeSize(int8_t type, const char* elem, int fieldNameSize, i
if (type == MaxKey || type == MinKey)
return fieldNameSize + 1;
if (type != BSONType::RegEx)
- msgAssertedBadType(elem);
+ msgAssertedBadType(type);
// RegEx is two c-strings back-to-back.
const char* p = elem + fieldNameSize + 1;
diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h
index 251ed41dd2d..f319c2981fb 100644
--- a/src/mongo/bson/bsonelement.h
+++ b/src/mongo/bson/bsonelement.h
@@ -143,7 +143,14 @@ public:
bool Bool() const {
return chk(mongo::Bool).boolean();
}
+
std::vector<BSONElement> Array() const; // see implementation for detailed comments
+
+ /**
+ * Like Array() above, but, if the array keys are not in sequential order or are otherwise
+ * invalid, an exception is thrown.
+ */
+ std::vector<BSONElement> ArrayVerifyIndexes() const;
mongo::OID OID() const {
return chk(jstOID).__oid();
}
diff --git a/src/mongo/bson/bsonelement_test.cpp b/src/mongo/bson/bsonelement_test.cpp
index 4e5bc3f6f07..0921823f92f 100644
--- a/src/mongo/bson/bsonelement_test.cpp
+++ b/src/mongo/bson/bsonelement_test.cpp
@@ -446,5 +446,55 @@ TEST(BSONElementTryCoeceToLongLongTest, CoerceFails) {
ASSERT_NOT_OK(result) << " for input document " << testCase.toString();
}
}
+
+TEST(BSONElement, ArrayToVectorFunctionsBehaveCorrectlyWithValidArray) {
+ // Create a valid array by creating a BSONObj with contiguous array indexes that is then
+ // passed to the BSONArray ctor.
+ BSONObj updateArrAsObj = BSON("0"
+ << "foo"
+ << "1"
+ << "bar");
+ BSONArray updateArr(updateArrAsObj);
+ BSONObj parentObj = BSON("arr" << updateArr);
+ auto arrElem = parentObj.getField("arr");
+
+ // Both 'Array()' and 'ArrayVerifyIndexes()' will not throw, and instead create vectors of
+ // size 2.
+ auto elementVector = arrElem.Array();
+ ASSERT_EQ(elementVector.size(), 2);
+ ASSERT(elementVector[0].binaryEqual(updateArrAsObj.getField("0")));
+ ASSERT(elementVector[1].binaryEqual(updateArrAsObj.getField("1")));
+
+ auto verifiedVector = arrElem.ArrayVerifyIndexes();
+ ASSERT_EQ(verifiedVector.size(), 2);
+
+ // The two vectors should have the same elements.
+ ASSERT(elementVector[0].binaryEqual(verifiedVector[0]));
+ ASSERT(elementVector[1].binaryEqual(verifiedVector[1]));
+}
+
+TEST(BSONElement, ArrayVerifyIndexesThrowsOnInvalidArrayIndexes) {
+ // Create our invalid array by creating a BSONObj with non contiguous array indexes that is then
+ // passed to the BSONArray ctor.
+ BSONObj updateArrAsObj = BSON("0"
+ << "foo"
+ << "2"
+ << "bar");
+ BSONArray updateArr(updateArrAsObj);
+ BSONObj parentObj = BSON("badArray" << updateArr);
+ auto arrElem = parentObj.getField("badArray");
+
+ // The regular 'Array()' will not throw, and instead create an EOO BSONElement at index 1.
+ auto elementVector = arrElem.Array();
+ ASSERT_EQ(elementVector.size(), 3);
+
+ ASSERT(elementVector[0].binaryEqual(updateArrAsObj.getField("0")));
+ ASSERT(elementVector[1].binaryEqual(BSONElement()));
+ ASSERT(elementVector[2].binaryEqual(updateArrAsObj.getField("2")));
+
+ // 'ArrayVerifyIndexes()', on the other hand, will throw.
+ ASSERT_THROWS(arrElem.ArrayVerifyIndexes(), ExceptionFor<ErrorCodes::BadValue>);
+}
+
} // namespace
} // namespace mongo
diff --git a/src/mongo/bson/bsonobj.cpp b/src/mongo/bson/bsonobj.cpp
index 7b5fa7dae04..f0c5946565c 100644
--- a/src/mongo/bson/bsonobj.cpp
+++ b/src/mongo/bson/bsonobj.cpp
@@ -138,41 +138,71 @@ BSONObj BSONObj::getOwned(const BSONObj& obj) {
return obj.getOwned();
}
-BSONObj BSONObj::redact(bool onlyEncryptedFields) const {
+BSONObj BSONObj::redact(RedactLevel level,
+ std::function<std::string(const BSONElement&)> fieldNameRedactor) const {
_validateUnownedSize(objsize());
// Helper to get an "internal function" to be able to do recursion
struct redactor {
- void appendRedactedElem(BSONObjBuilder& builder, const BSONElement& e, bool appendMask) {
+ void appendRedactedElem(BSONObjBuilder& builder,
+ const StringData& fieldNameString,
+ bool appendMask) {
if (appendMask) {
- builder.append(e.fieldNameStringData(), "###"_sd);
+ builder.append(fieldNameString, "###"_sd);
} else {
- builder.appendNull(e.fieldNameStringData());
+ builder.appendNull(fieldNameString);
}
}
void operator()(BSONObjBuilder& builder,
const BSONObj& obj,
bool appendMask,
- bool onlyEncryptedFields) {
+ RedactLevel level,
+ std::function<std::string(const BSONElement&)> fieldNameRedactor) {
for (BSONElement e : obj) {
+ StringData fieldNameString;
+ // Temporarily allocated string that must live long enough to be copied by builder.
+ std::string tempString;
+ if (!fieldNameRedactor) {
+ fieldNameString = e.fieldNameStringData();
+ } else {
+ tempString = fieldNameRedactor(e);
+ fieldNameString = {tempString};
+ }
if (e.type() == Object) {
- BSONObjBuilder subBuilder = builder.subobjStart(e.fieldNameStringData());
- operator()(subBuilder, e.Obj(), appendMask, onlyEncryptedFields);
+ BSONObjBuilder subBuilder = builder.subobjStart(fieldNameString);
+ operator()(subBuilder, e.Obj(), appendMask, level, fieldNameRedactor);
subBuilder.done();
} else if (e.type() == Array) {
- BSONObjBuilder subBuilder = builder.subarrayStart(e.fieldNameStringData());
- operator()(subBuilder, e.Obj(), appendMask, onlyEncryptedFields);
+ BSONObjBuilder subBuilder = builder.subarrayStart(fieldNameString);
+ operator()(subBuilder, e.Obj(), appendMask, level, fieldNameRedactor);
subBuilder.done();
} else {
- if (onlyEncryptedFields) {
- if (e.type() == BinData && e.binDataType() == BinDataType::Encrypt) {
- appendRedactedElem(builder, e, appendMask);
- } else {
- builder.append(e);
+ // SERVER-79068 Templatizing this could be a good opportunity for performance
+ // improvements.
+ switch (level) {
+ case RedactLevel::all: {
+ appendRedactedElem(builder, fieldNameString, appendMask);
+ break;
+ }
+ case RedactLevel::encryptedAndSensitive: {
+ if (e.type() == BinData &&
+ (e.binDataType() == BinDataType::Encrypt ||
+ e.binDataType() == BinDataType::Sensitive)) {
+ appendRedactedElem(builder, fieldNameString, appendMask);
+ } else {
+ builder.append(e);
+ }
+ break;
+ }
+ case RedactLevel::sensitiveOnly: {
+ if (e.type() == BinData && e.binDataType() == BinDataType::Sensitive) {
+ appendRedactedElem(builder, fieldNameString, appendMask);
+ } else {
+ builder.append(e);
+ }
+ break;
}
- } else {
- appendRedactedElem(builder, e, appendMask);
}
}
}
@@ -181,7 +211,7 @@ BSONObj BSONObj::redact(bool onlyEncryptedFields) const {
try {
BSONObjBuilder builder;
- redactor()(builder, *this, /*appendMask=*/true, onlyEncryptedFields);
+ redactor()(builder, *this, /*appendMask=*/true, level, fieldNameRedactor);
return builder.obj();
} catch (const ExceptionFor<ErrorCodes::BSONObjectTooLarge>&) {
}
@@ -191,7 +221,7 @@ BSONObj BSONObj::redact(bool onlyEncryptedFields) const {
// we use BSONType::jstNull, which ensures the redacted object will not be larger than the
// original.
BSONObjBuilder builder;
- redactor()(builder, *this, /*appendMask=*/false, onlyEncryptedFields);
+ redactor()(builder, *this, /*appendMask=*/false, level, fieldNameRedactor);
return builder.obj();
}
@@ -298,10 +328,6 @@ BSONObj BSONObj::jsonStringBuffer(JsonStringFormat format,
}
}
-bool BSONObj::valid() const {
- return validateBSON(objdata(), objsize()).isOK();
-}
-
int BSONObj::woCompare(const BSONObj& r,
const Ordering& o,
ComparisonRulesSet rules,
diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h
index a5270adfd25..5793f9e5212 100644
--- a/src/mongo/bson/bsonobj.h
+++ b/src/mongo/bson/bsonobj.h
@@ -264,10 +264,14 @@ public:
*/
BSONObj copy() const;
+ enum class RedactLevel : int8_t { all, encryptedAndSensitive, sensitiveOnly };
+
/**
* @return a new full (and owned) redacted copy of the object.
*/
- BSONObj redact(bool onlyEncryptedFields = false) const;
+ BSONObj redact(
+ RedactLevel level = RedactLevel::all,
+ std::function<std::string(const BSONElement&)> fieldNameRedactor = nullptr) const;
/**
* Readable representation of a BSON object in an extended JSON-style notation.
@@ -638,11 +642,6 @@ public:
bool hasFieldNames() const;
/**
- * Returns true if this object is valid and returns false otherwise.
- */
- bool valid() const;
-
- /**
* add all elements of the object to the specified vector
*/
void elems(std::vector<BSONElement>&) const;
diff --git a/src/mongo/bson/bsonobjbuilder.cpp b/src/mongo/bson/bsonobjbuilder.cpp
index e8e50fd38a7..096ba297e99 100644
--- a/src/mongo/bson/bsonobjbuilder.cpp
+++ b/src/mongo/bson/bsonobjbuilder.cpp
@@ -182,7 +182,7 @@ Derived& BSONObjBuilderBase<Derived, B>::appendMaxForType(StringData fieldName,
template <class Derived, class B>
Derived& BSONObjBuilderBase<Derived, B>::appendDate(StringData fieldName, Date_t dt) {
_b.appendNum((char)Date);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum(dt.toMillisSinceEpoch());
return static_cast<Derived&>(*this);
}
diff --git a/src/mongo/bson/bsonobjbuilder.h b/src/mongo/bson/bsonobjbuilder.h
index be826d5a824..008ad7953fb 100644
--- a/src/mongo/bson/bsonobjbuilder.h
+++ b/src/mongo/bson/bsonobjbuilder.h
@@ -153,7 +153,7 @@ public:
// do not append eoo, that would corrupt us. the builder auto appends when done() is called.
verify(!e.eoo());
_b.appendNum((char)e.type());
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendBuf((void*)e.value(), e.valuesize());
return static_cast<Derived&>(*this);
}
@@ -161,7 +161,7 @@ public:
/** add a subobject as a member */
Derived& append(StringData fieldName, BSONObj subObj) {
_b.appendNum((char)Object);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendBuf((void*)subObj.objdata(), subObj.objsize());
return static_cast<Derived&>(*this);
}
@@ -176,7 +176,7 @@ public:
verify(size > 4 && size < 100000000);
_b.appendNum((char)Object);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendBuf((void*)objdata, size);
return static_cast<Derived&>(*this);
}
@@ -194,7 +194,7 @@ public:
*/
B& subobjStart(StringData fieldName) {
_b.appendNum((char)Object);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
return _b;
}
@@ -203,7 +203,7 @@ public:
*/
Derived& appendArray(StringData fieldName, const BSONObj& subObj) {
_b.appendNum((char)Array);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendBuf((void*)subObj.objdata(), subObj.objsize());
return static_cast<Derived&>(*this);
@@ -216,14 +216,14 @@ public:
the subarray's body */
B& subarrayStart(StringData fieldName) {
_b.appendNum((char)Array);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
return _b;
}
/** Append a boolean element */
Derived& appendBool(StringData fieldName, int val) {
_b.appendNum((char)Bool);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum((char)(val ? 1 : 0));
return static_cast<Derived&>(*this);
}
@@ -233,7 +233,7 @@ public:
Derived& append(StringData fieldName, const T& n) {
constexpr BSONType type = BSONObjAppendFormat<T>::value;
_b.appendNum(static_cast<char>(type));
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
if constexpr (type == Bool) {
_b.appendNum(static_cast<char>(n));
} else if constexpr (type == NumberInt) {
@@ -284,7 +284,7 @@ public:
*/
Derived& appendOID(StringData fieldName, OID* oid = nullptr, bool generateIfBlank = false) {
_b.appendNum((char)jstOID);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
if (oid)
_b.appendBuf(oid->view().view(), OID::kOIDSize);
else {
@@ -305,7 +305,7 @@ public:
*/
Derived& append(StringData fieldName, OID oid) {
_b.appendNum((char)jstOID);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendBuf(oid.view().view(), OID::kOIDSize);
return static_cast<Derived&>(*this);
}
@@ -324,7 +324,7 @@ public:
*/
Derived& appendTimeT(StringData fieldName, time_t dt) {
_b.appendNum((char)Date);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum(static_cast<unsigned long long>(dt) * 1000);
return static_cast<Derived&>(*this);
}
@@ -343,9 +343,9 @@ public:
*/
Derived& appendRegex(StringData fieldName, StringData regex, StringData options = "") {
_b.appendNum((char)RegEx);
- _b.appendStr(fieldName);
- _b.appendStr(regex);
- _b.appendStr(options);
+ _b.appendCStr(fieldName);
+ _b.appendCStr(regex);
+ _b.appendCStr(options);
return static_cast<Derived&>(*this);
}
@@ -356,9 +356,9 @@ public:
Derived& appendCode(StringData fieldName, StringData code) {
_b.appendNum((char)Code);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum((int)code.size() + 1);
- _b.appendStr(code);
+ _b.appendStrBytesAndNul(code);
return static_cast<Derived&>(*this);
}
@@ -370,7 +370,7 @@ public:
@param sz size includes terminating null character */
Derived& append(StringData fieldName, const char* str, int sz) {
_b.appendNum((char)String);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum((int)sz);
_b.appendBuf(str, sz);
@@ -383,17 +383,17 @@ public:
/** Append a string element */
Derived& append(StringData fieldName, StringData str) {
_b.appendNum((char)String);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum((int)str.size() + 1);
- _b.appendStr(str, true);
+ _b.appendStrBytesAndNul(str);
return static_cast<Derived&>(*this);
}
Derived& appendSymbol(StringData fieldName, StringData symbol) {
_b.appendNum((char)Symbol);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum((int)symbol.size() + 1);
- _b.appendStr(symbol);
+ _b.appendStrBytesAndNul(symbol);
return static_cast<Derived&>(*this);
}
@@ -404,7 +404,7 @@ public:
/** Append a Null element to the object */
Derived& appendNull(StringData fieldName) {
_b.appendNum((char)jstNULL);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
return static_cast<Derived&>(*this);
}
@@ -412,13 +412,13 @@ public:
// Append an element that is less than all other keys.
Derived& appendMinKey(StringData fieldName) {
_b.appendNum((char)MinKey);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
return static_cast<Derived&>(*this);
}
// Append an element that is greater than all other keys.
Derived& appendMaxKey(StringData fieldName) {
_b.appendNum((char)MaxKey);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
return static_cast<Derived&>(*this);
}
@@ -439,9 +439,9 @@ public:
*/
Derived& appendDBRef(StringData fieldName, StringData ns, const OID& oid) {
_b.appendNum((char)DBRef);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum((int)ns.size() + 1);
- _b.appendStr(ns);
+ _b.appendStrBytesAndNul(ns);
_b.appendBuf(oid.view().view(), OID::kOIDSize);
return static_cast<Derived&>(*this);
@@ -460,7 +460,7 @@ public:
*/
Derived& appendBinData(StringData fieldName, int len, BinDataType type, const void* data) {
_b.appendNum((char)BinData);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum(len);
_b.appendNum((char)type);
_b.appendBuf(data, len);
@@ -480,7 +480,7 @@ public:
*/
Derived& appendBinDataArrayDeprecated(const char* fieldName, const void* data, int len) {
_b.appendNum((char)BinData);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum(len + 4);
_b.appendNum((char)0x2);
_b.appendNum(len);
@@ -494,10 +494,10 @@ public:
*/
Derived& appendCodeWScope(StringData fieldName, StringData code, const BSONObj& scope) {
_b.appendNum((char)CodeWScope);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
_b.appendNum((int)(4 + 4 + code.size() + 1 + scope.objsize()));
_b.appendNum((int)code.size() + 1);
- _b.appendStr(code);
+ _b.appendStrBytesAndNul(code);
_b.appendBuf((void*)scope.objdata(), scope.objsize());
return static_cast<Derived&>(*this);
@@ -509,7 +509,7 @@ public:
Derived& appendUndefined(StringData fieldName) {
_b.appendNum((char)Undefined);
- _b.appendStr(fieldName);
+ _b.appendCStr(fieldName);
return static_cast<Derived&>(*this);
}
diff --git a/src/mongo/bson/bsontypes.cpp b/src/mongo/bson/bsontypes.cpp
index 8be3c5e1d04..1f9112f2dc4 100644
--- a/src/mongo/bson/bsontypes.cpp
+++ b/src/mongo/bson/bsontypes.cpp
@@ -199,6 +199,8 @@ const char* typeName(BinDataType type) {
return "encrypt";
case Column:
return "column";
+ case Sensitive:
+ return "sensitive";
case bdtCustom:
return "Custom";
default:
@@ -217,6 +219,7 @@ bool isValidBinDataType(int type) {
case Encrypt:
case Column:
case bdtCustom:
+ case Sensitive:
return true;
default:
return false;
diff --git a/src/mongo/bson/bsontypes.h b/src/mongo/bson/bsontypes.h
index 0dc2fe8a4ab..5204f1c3f72 100644
--- a/src/mongo/bson/bsontypes.h
+++ b/src/mongo/bson/bsontypes.h
@@ -198,8 +198,9 @@ enum BinDataType {
bdtUUID = 3, /* deprecated */
newUUID = 4, /* language-independent UUID format across all drivers */
MD5Type = 5,
- Encrypt = 6, /* encryption placeholder or encrypted data */
- Column = 7, /* compressed column */
+ Encrypt = 6, /* encryption placeholder or encrypted data */
+ Column = 7, /* compressed column */
+ Sensitive = 8, /* data that should be redacted and protected from unnecessary exposure */
bdtCustom = 128
};
diff --git a/src/mongo/bson/simple_bsonobj_comparator.h b/src/mongo/bson/simple_bsonobj_comparator.h
index ed08cbdd939..b073491747f 100644
--- a/src/mongo/bson/simple_bsonobj_comparator.h
+++ b/src/mongo/bson/simple_bsonobj_comparator.h
@@ -95,6 +95,10 @@ public:
};
};
+inline auto simpleHash(const BSONObj& obj) {
+ return SimpleBSONObjComparator::kInstance.hash(obj);
+}
+
/**
* A set of BSONObjs that performs comparisons with simple binary semantics.
*/
diff --git a/src/mongo/bson/timestamp.h b/src/mongo/bson/timestamp.h
index 3d0befd7450..28bf9cb31cd 100644
--- a/src/mongo/bson/timestamp.h
+++ b/src/mongo/bson/timestamp.h
@@ -139,7 +139,7 @@ public:
// No endian conversions needed, since we store in-memory representation
// in little endian format, regardless of target endian.
builder.appendNum(static_cast<char>(bsonTimestamp));
- builder.appendStr(fieldName);
+ builder.appendCStr(fieldName);
builder.appendNum(asULL());
}
BSONObj toBSON() const;
diff --git a/src/mongo/bson/util/builder.h b/src/mongo/bson/util/builder.h
index 5d7d2d5673c..883b19a0ffe 100644
--- a/src/mongo/bson/util/builder.h
+++ b/src/mongo/bson/util/builder.h
@@ -57,6 +57,7 @@
#include "mongo/util/itoa.h"
#include "mongo/util/shared_buffer.h"
#include "mongo/util/shared_buffer_fragment.h"
+#include "mongo/util/str_basic.h"
namespace mongo {
@@ -325,7 +326,7 @@ public:
@return point to region that was skipped. pointer may change later (on realloc), so for
immediate use only
*/
- char* skip(int n) {
+ char* skip(size_t n) {
return grow(n);
}
@@ -394,7 +395,7 @@ public:
}
void appendBuf(const void* src, size_t len) {
if (len)
- memcpy(grow((int)len), src, len);
+ memcpy(grow(len), src, len);
}
template <class T>
@@ -402,9 +403,36 @@ public:
appendBuf(&s, sizeof(T));
}
- void appendStr(StringData str, bool includeEndingNull = true) {
- const int len = str.size() + (includeEndingNull ? 1 : 0);
- str.copyTo(grow(len), includeEndingNull);
+ /**
+ * Appends the raw bytes of str with no NUL terminator.
+ */
+ void appendStrBytes(StringData str) {
+ str.copy(grow(str.size()), str.size());
+ }
+
+ /**
+ * Appends the raw bytes of str followed by a final NUL byte.
+ *
+ * WARNING: only use this method for formats with explicit string lengths where the NUL byte is
+ * not used to find the end. This method does not check for embedded NUL bytes, so they can
+ * trick a parser into thinking the string has ended. Use appendCStr() instead for that use
+ * case.
+ */
+ void appendStrBytesAndNul(StringData str) {
+ auto dest = grow(str.size() + 1);
+ dest += str.copy(dest, str.size());
+ *dest = '\0';
+ }
+
+ /**
+ * Appends the raw bytes of str followed by a final NUL byte, throwing if str already has an
+ * embedded NUL byte.
+ *
+ * This method is intended to pair with BufReader::readCStr() on the parse side.
+ */
+ void appendCStr(StringData str) {
+ str::uassertNoEmbeddedNulBytes(str);
+ appendStrBytesAndNul(str);
}
/** Returns the length of data in the current buffer */
@@ -423,8 +451,8 @@ public:
}
/* returns the pre-grow write position */
- char* grow(int by) {
- if (MONGO_likely(by <= _end - _nextByte)) {
+ char* grow(size_t by) {
+ if (MONGO_likely(by <= static_cast<size_t>(_end - _nextByte))) {
char* oldNextByte = _nextByte;
_nextByte += by;
return oldNextByte;
@@ -751,7 +779,7 @@ public:
}
void append(StringData str) {
- str.copyTo(_buf.grow(str.size()), false);
+ _buf.appendStrBytes(str);
}
void reset(int maxSize = 0) {
diff --git a/src/mongo/bson/util/builder_test.cpp b/src/mongo/bson/util/builder_test.cpp
index a4ed4c87115..0ad7518ffe5 100644
--- a/src/mongo/bson/util/builder_test.cpp
+++ b/src/mongo/bson/util/builder_test.cpp
@@ -38,12 +38,43 @@ TEST(Builder, String1) {
ASSERT_EQUALS(small, "eliot");
BufBuilder bb;
- bb.appendStr(small);
+ bb.appendCStr(small);
+
+ ASSERT_EQUALS(bb.len(), small.size() + 1);
+ ASSERT_EQUALS(bb.buf()[small.size()], 0);
ASSERT_EQUALS(0, strcmp(bb.buf(), "eliot"));
ASSERT_EQUALS(0, strcmp("eliot", bb.buf()));
}
+TEST(Builder, StringNulByteHandling) {
+ auto hasNulByte = "hello\0world"_sd;
+
+ {
+ // appendCStr() throws without changing bb;
+ BufBuilder bb;
+ ASSERT_THROWS_CODE(bb.appendCStr(hasNulByte), DBException, 9527900);
+ ASSERT_EQ(bb.len(), 0);
+ }
+
+ {
+ // appendStrBytes appends embedded NUL without terminator.
+ BufBuilder bb;
+ bb.appendStrBytes(hasNulByte);
+ ASSERT_EQ(StringData(bb.buf(), bb.len()), hasNulByte);
+ }
+
+ {
+ // appendStrBytesAndNul appends embedded NUL and NUL terminator.
+ BufBuilder bb;
+ bb.appendStrBytesAndNul(hasNulByte);
+ // Since hasNulByte points to a string literal, we know that
+ // *(hasNulByte.data() + hasNulByte.size()) is valid and == '\0'
+ ASSERT_EQ(StringData(bb.buf(), bb.len()),
+ StringData(hasNulByte.rawData(), hasNulByte.size() + 1));
+ }
+}
+
TEST(Builder, StringBuilderAddress) {
const void* longPtr = reinterpret_cast<const void*>(-1);
const void* shortPtr = reinterpret_cast<const void*>(static_cast<uintptr_t>(0xDEADBEEF));
diff --git a/src/mongo/bson/util/simple8b_test.cpp b/src/mongo/bson/util/simple8b_test.cpp
index c612c221aec..df8c849f73b 100644
--- a/src/mongo/bson/util/simple8b_test.cpp
+++ b/src/mongo/bson/util/simple8b_test.cpp
@@ -1437,6 +1437,7 @@ TEST(Simple8b, ResetRLEAfterLargeValue) {
// The second block should be an RLE block
ASSERT_GT(size, 16);
- uint64_t secondBlock = *((uint64_t*)(data.get() + sizeof(uint64_t)));
+ uint64_t secondBlock =
+ ConstDataView(data.get() + sizeof(uint64_t)).read<LittleEndian<uint64_t>>();
ASSERT_TRUE((secondBlock & kBaseSelectorMask) == kRleSelector);
}