void BSONInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) { auto holder = getHolder(obj); *resolvedp = false; if (!holder) { return; } IdWrapper idw(cx, id); if (!holder->_readOnly && holder->_removed.count(idw.toString())) { return; } ObjectWrapper o(cx, obj); std::string sname = IdWrapper(cx, id).toString(); if (holder->_obj.hasField(sname)) { auto elem = holder->_obj[sname]; JS::RootedValue vp(cx); ValueReader(cx, &vp).fromBSONElement(elem, holder->_readOnly); o.defineProperty(id, vp, JSPROP_ENUMERATE); if (!holder->_readOnly && (elem.type() == mongo::Object || elem.type() == mongo::Array)) { // if accessing a subobject, we have no way to know if // modifications are being made on writable objects holder->_altered = true; } *resolvedp = true; } }
void DBInfo::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp) { // 2nd look into real values, may be cached collection object if (!vp.isUndefined()) { auto scope = getScope(cx); auto opContext = scope->getOpContext(); if (opContext && vp.isObject()) { ObjectWrapper o(cx, vp); if (o.hasOwnField(InternedString::_fullName)) { // need to check every time that the collection did not get sharded if (haveLocalShardingInfo(opContext, o.getString(InternedString::_fullName))) uasserted(ErrorCodes::BadValue, "can't use sharded collection from db.eval"); } } return; } JS::RootedObject parent(cx); if (!JS_GetPrototype(cx, obj, &parent)) uasserted(ErrorCodes::JSInterpreterFailure, "Couldn't get prototype"); ObjectWrapper parentWrapper(cx, parent); if (parentWrapper.hasOwnField(id)) { parentWrapper.getValue(id, vp); return; } IdWrapper idw(cx, id); // if starts with '_' we dont return collection, one must use getCollection() if (idw.isString()) { JSStringWrapper jsstr; auto sname = idw.toStringData(&jsstr); if (sname.size() == 0 || sname[0] == '_') { return; } } // no hit, create new collection JS::RootedValue getCollection(cx); parentWrapper.getValue(InternedString::getCollection, &getCollection); if (!(getCollection.isObject() && JS_ObjectIsFunction(cx, getCollection.toObjectOrNull()))) { uasserted(ErrorCodes::BadValue, "getCollection is not a function"); } JS::AutoValueArray<1> args(cx); idw.toValue(args[0]); JS::RootedValue coll(cx); ObjectWrapper(cx, obj).callMethod(getCollection, args, &coll); uassert(16861, "getCollection returned something other than a collection", getScope(cx)->getProto<DBCollectionInfo>().instanceOf(coll)); // cache collection for reuse, don't enumerate ObjectWrapper(cx, obj).defineProperty(id, coll, 0); vp.set(coll); }
BSONObj ObjectWrapper::toBSON() { if (getScope(_context)->getProto<BSONInfo>().instanceOf(_object)) { BSONObj* originalBSON = nullptr; bool altered; std::tie(originalBSON, altered) = BSONInfo::originalBSON(_context, _object); if (originalBSON && !altered) return *originalBSON; } JS::RootedId id(_context); // INCREDIBLY SUBTLE BEHAVIOR: // // (jcarey): Be very careful about how the Rooting API is used in // relationship to WriteFieldRecursionFrames. Mozilla'a API more or less // demands that the rooting types are on the stack and only manipulated as // regular objects, which we aren't doing here. The reason they do this is // because the rooting types must be global created and destroyed in an // entirely linear order. This is impossible to screw up in regular use, // but our unwinding of the recursion frames makes it easy to do here. // // The roots above need to be before the first frame is emplaced (so // they'll be destroyed after it) and none of the roots in the below code // (or in ValueWriter::writeThis) can live longer than until the call to // emplace() inside ValueWriter. The runtime asserts enabled by MozJS's // debug mode will catch runtime errors, but be aware of how difficult this // is to get right and what to look for if one of them bites you. BSONObjBuilder b; { // NOTE: Keep the frames in a scope so that it is clear that // we always destroy them before we destroy 'b'. It is // important to do so: if 'b' is destroyed before the frames, // and we don't pop all of the frames (say, due to an // exeption), then the frame dtors would write to freed // memory. WriteFieldRecursionFrames frames; frames.emplace(_context, _object, nullptr, StringData{}); // We special case the _id field in top-level objects and move it to the front. // This matches other drivers behavior and makes finding the _id field quicker in BSON. if (hasOwnField(InternedString::_id)) { _writeField(&b, InternedString::_id, &frames, frames.top().originalBSON); } while (frames.size()) { auto& frame = frames.top(); // If the index is the same as length, we've seen all the keys at this // level and should go up a level if (frame.idx == frame.ids.length()) { frames.pop(); continue; } if (frame.idx == 0 && frame.originalBSON && !frame.altered) { // If this is our first look at the object and it has an unaltered // bson behind it, move idx to the end so we'll roll up on the next // pass through the loop. frame.subbob_or(&b)->appendElements(*frame.originalBSON); frame.idx = frame.ids.length(); continue; } id.set(frame.ids[frame.idx++]); if (frames.size() == 1) { IdWrapper idw(_context, id); // TODO: check if it's cheaper to just compare with an interned // string of "_id" rather than with ascii if (idw.isString() && idw.equalsAscii("_id")) { continue; } } // writeField invokes ValueWriter with the frame stack, which will push // onto frames for subobjects, which will effectively recurse the loop. _writeField(frame.subbob_or(&b), JS::HandleId(id), &frames, frame.originalBSON); } } const int sizeWithEOO = b.len() + 1 /*EOO*/ - 4 /*BSONObj::Holder ref count*/; uassert(17260, str::stream() << "Converting from JavaScript to BSON failed: " << "Object size " << sizeWithEOO << " exceeds limit of " << BSONObjMaxInternalSize << " bytes.", sizeWithEOO <= BSONObjMaxInternalSize); return b.obj(); }