summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/bson_compare_bug.js47
-rw-r--r--src/mongo/scripting/mozjs/db.cpp2
-rw-r--r--src/mongo/scripting/mozjs/objectwrapper.cpp42
-rw-r--r--src/mongo/scripting/mozjs/objectwrapper.h7
4 files changed, 96 insertions, 2 deletions
diff --git a/jstests/core/bson_compare_bug.js b/jstests/core/bson_compare_bug.js
new file mode 100644
index 00000000000..2a39efd8db7
--- /dev/null
+++ b/jstests/core/bson_compare_bug.js
@@ -0,0 +1,47 @@
+(function() {
+ "use strict";
+
+ db.bson_compare_bug.drop();
+
+ // We want some BSON objects for this test. One convenient way to get that is to insert them
+ // into the database and then get them back through a query.
+ const coll = db.bson_compare_bug;
+ assert.commandWorked(coll.insert(
+ [
+ {_id: 1, obj: {val: [], _id: 1}},
+ {_id: 2, obj: {val: []}},
+ {_id: 3, obj: {_id: 1, val: []}}
+ ],
+ {writeConcern: {w: "majority"}}));
+
+ // The $replaceRoot is so we can get back two results that have an "_id" field and one that
+ // doesn't. The first two results from this query are the same, except for that.
+ // res[0]: {val: [], _id: 1}
+ // res[1]: {val: []}
+ const res = coll.aggregate([{$sort: {_id: 1}}, {$replaceRoot: {newRoot: "$obj"}}]).toArray();
+ assert.eq(3, res.length);
+
+ // bsonBinaryEqual() should see that the BSON results from the query are not equal.
+ assert(!bsonBinaryEqual(res[0], res[1]));
+
+ // A magic trick: the shell represents the objects in res[0] and res[1] as JavaScript objects
+ // that internally store raw BSON data but also maintain JavaScript properties for each of their
+ // BSON fields. The BSON and JavaScript properties are kept in sync both ways. Reading the "val"
+ // property for the first time results in a call to BSONInfo::resolve(), which materializes the
+ // "val" BSON field as a JavaScript property. In this case, the resolve function also
+ // conservatively marks the object as "altered," because "val" is an array, and there's no way
+ // to observe modifications to it.
+ assert.eq(res[0].val, res[1].val);
+
+ // We repeat the BSON comparison, but this time, the objects are "altered," and bsonBinaryEqual
+ // needs to sync the JavaScript properties back into BSON. Before SERVER-39521, a bug in the
+ // conversion would ignore the "_id" field unless it was previously resolved, which would cause
+ // res[0] and res[1] to appear equal.
+ assert(!bsonBinaryEqual(res[0], res[1]));
+
+ // The bug that caused the "_id" field to get dropped in conversion involves code that is
+ // supposed to move the "_id" field to the front when converting a JavaScript object to BSON.
+ // This check ensures that "_id" is still getting moved to the front. The value of res[0] should
+ // now have changed so that both it and res[2] have their _id field first.
+ assert(bsonBinaryEqual(res[0], res[2]));
+}());
diff --git a/src/mongo/scripting/mozjs/db.cpp b/src/mongo/scripting/mozjs/db.cpp
index 228cd3d4acc..4748e9a308b 100644
--- a/src/mongo/scripting/mozjs/db.cpp
+++ b/src/mongo/scripting/mozjs/db.cpp
@@ -75,7 +75,7 @@ void DBInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool*
}
// Check if this exists on the parent, ie. DBCollection::resolve case
- if (parentWrapper.hasOwnField(id)) {
+ if (parentWrapper.alreadyHasOwnField(id)) {
parentWrapper.getValue(id, &coll);
o.defineProperty(id, coll, 0);
diff --git a/src/mongo/scripting/mozjs/objectwrapper.cpp b/src/mongo/scripting/mozjs/objectwrapper.cpp
index 306c1f4ef64..3c57e262029 100644
--- a/src/mongo/scripting/mozjs/objectwrapper.cpp
+++ b/src/mongo/scripting/mozjs/objectwrapper.cpp
@@ -203,6 +203,41 @@ bool ObjectWrapper::Key::hasOwn(JSContext* cx, JS::HandleObject o) {
switch (_type) {
case Type::Field:
+ if (JS_HasOwnProperty(cx, o, _field, &has))
+ return has;
+ break;
+ case Type::Index: {
+ JS::RootedId id(cx);
+
+ // This is a little different because there is no JS_HasOwnElement
+ if (JS_IndexToId(cx, _idx, &id) && JS_HasOwnPropertyById(cx, o, id, &has))
+ return has;
+ break;
+ }
+ case Type::Id: {
+ JS::RootedId id(cx, _id);
+
+ if (JS_HasOwnPropertyById(cx, o, id, &has))
+ return has;
+ break;
+ }
+ case Type::InternedString: {
+ InternedStringId id(cx, _internedString);
+
+ if (JS_HasOwnPropertyById(cx, o, id, &has))
+ return has;
+ break;
+ }
+ }
+
+ throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to hasOwn value on a JSObject");
+}
+
+bool ObjectWrapper::Key::alreadyHasOwn(JSContext* cx, JS::HandleObject o) {
+ bool has;
+
+ switch (_type) {
+ case Type::Field:
if (JS_AlreadyHasOwnProperty(cx, o, _field, &has))
return has;
break;
@@ -226,7 +261,8 @@ bool ObjectWrapper::Key::hasOwn(JSContext* cx, JS::HandleObject o) {
}
}
- throwCurrentJSException(cx, ErrorCodes::InternalError, "Failed to hasOwn value on a JSObject");
+ throwCurrentJSException(
+ cx, ErrorCodes::InternalError, "Failed to alreadyHasOwn value on a JSObject");
}
void ObjectWrapper::Key::del(JSContext* cx, JS::HandleObject o) {
@@ -456,6 +492,10 @@ bool ObjectWrapper::hasOwnField(Key key) {
return key.hasOwn(_context, _object);
}
+bool ObjectWrapper::alreadyHasOwnField(Key key) {
+ return key.alreadyHasOwn(_context, _object);
+}
+
void ObjectWrapper::callMethod(const char* field,
const JS::HandleValueArray& args,
JS::MutableHandleValue out) {
diff --git a/src/mongo/scripting/mozjs/objectwrapper.h b/src/mongo/scripting/mozjs/objectwrapper.h
index 7516807b130..ea006f82523 100644
--- a/src/mongo/scripting/mozjs/objectwrapper.h
+++ b/src/mongo/scripting/mozjs/objectwrapper.h
@@ -85,6 +85,7 @@ public:
void set(JSContext* cx, JS::HandleObject o, JS::HandleValue value);
bool has(JSContext* cx, JS::HandleObject o);
bool hasOwn(JSContext* cx, JS::HandleObject o);
+ bool alreadyHasOwn(JSContext* cx, JS::HandleObject o);
void define(
JSContext* cx, JS::HandleObject o, unsigned attrs, JSNative getter, JSNative setter);
void define(JSContext* cx, JS::HandleObject o, JS::HandleValue value, unsigned attrs);
@@ -138,9 +139,15 @@ public:
void rename(Key key, const char* to);
+ // has field walks the prototype heirarchy
bool hasField(Key key);
+
+ // has own field checks for the field directly on the object
bool hasOwnField(Key key);
+ // already how own field checks for the field directly on the object, ignoring C++ hooks
+ bool alreadyHasOwnField(Key key);
+
void callMethod(const char* name, const JS::HandleValueArray& args, JS::MutableHandleValue out);
void callMethod(const char* name, JS::MutableHandleValue out);
void callMethod(JS::HandleValue fun,