1
2 /**
3 * Copyright (C) 2018-present MongoDB, Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the Server Side Public License, version 1,
7 * as published by MongoDB, Inc.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * Server Side Public License for more details.
13 *
14 * You should have received a copy of the Server Side Public License
15 * along with this program. If not, see
16 * <http://www.mongodb.com/licensing/server-side-public-license>.
17 *
18 * As a special exception, the copyright holders give permission to link the
19 * code of portions of this program with the OpenSSL library under certain
20 * conditions as described in each individual source file and distribute
21 * linked combinations including the program with the OpenSSL library. You
22 * must comply with the Server Side Public License in all respects for
23 * all of the code used other than as permitted herein. If you modify file(s)
24 * with this exception, you may extend this exception to your version of the
25 * file(s), but you are not obligated to do so. If you do not wish to do so,
26 * delete this exception statement from your version. If you delete this
27 * exception statement from all source files in the program, then also delete
28 * it in the license file.
29 */
30
31 #include "mongo/platform/basic.h"
32
33 #include "mongo/scripting/mozjs/bson.h"
34
35 #include <boost/optional.hpp>
36 #include <set>
37
38 #include "mongo/scripting/mozjs/idwrapper.h"
39 #include "mongo/scripting/mozjs/implscope.h"
40 #include "mongo/scripting/mozjs/internedstring.h"
41 #include "mongo/scripting/mozjs/objectwrapper.h"
42 #include "mongo/scripting/mozjs/valuereader.h"
43 #include "mongo/scripting/mozjs/valuewriter.h"
44 #include "mongo/util/string_map.h"
45
46 namespace mongo {
47 namespace mozjs {
48
49 const char* const BSONInfo::className = "BSON";
50
51 const JSFunctionSpec BSONInfo::freeFunctions[3] = {
52 MONGO_ATTACH_JS_FUNCTION(bsonWoCompare), MONGO_ATTACH_JS_FUNCTION(bsonBinaryEqual), JS_FS_END,
53 };
54
55 namespace {
56
57 /**
58 * Holder for bson objects which tracks state for the js wrapper
59 *
60 * Basically, we have read only and read/write variants, and a need to manage
61 * the appearance of mutable state on the read/write versions.
62 */
63 struct BSONHolder {
BSONHoldermongo::mozjs::__anon41853c1f0111::BSONHolder64 BSONHolder(const BSONObj& obj, const BSONObj* parent, const MozJSImplScope* scope, bool ro)
65 : _obj(obj),
66 _generation(scope->getGeneration()),
67 _isOwned(obj.isOwned() || (parent && parent->isOwned())),
68 _resolved(false),
69 _readOnly(ro),
70 _altered(false) {
71 uassert(
72 ErrorCodes::BadValue,
73 "Attempt to bind an unowned BSON Object to a JS scope marked as requiring ownership",
74 _isOwned || (!scope->requiresOwnedObjects()));
75 if (parent) {
76 _parent.emplace(*parent);
77 }
78 }
79
getOwnermongo::mozjs::__anon41853c1f0111::BSONHolder80 const BSONObj& getOwner() const {
81 return _parent ? *(_parent) : _obj;
82 }
83
uassertValidmongo::mozjs::__anon41853c1f0111::BSONHolder84 void uassertValid(JSContext* cx) const {
85 if (!_isOwned && getScope(cx)->getGeneration() != _generation)
86 uasserted(ErrorCodes::BadValue,
87 "Attempt to access an invalidated BSON Object in JS scope");
88 }
89
90 BSONObj _obj;
91 boost::optional<BSONObj> _parent;
92 std::size_t _generation;
93 bool _isOwned;
94 bool _resolved;
95 bool _readOnly;
96 bool _altered;
97 StringMap<bool> _removed;
98 };
99
getValidHolder(JSContext * cx,JSObject * obj)100 BSONHolder* getValidHolder(JSContext* cx, JSObject* obj) {
101 auto holder = static_cast<BSONHolder*>(JS_GetPrivate(obj));
102
103 if (holder)
104 holder->uassertValid(cx);
105
106 return holder;
107 }
108
109 } // namespace
110
make(JSContext * cx,JS::MutableHandleObject obj,BSONObj bson,const BSONObj * parent,bool ro)111 void BSONInfo::make(
112 JSContext* cx, JS::MutableHandleObject obj, BSONObj bson, const BSONObj* parent, bool ro) {
113 auto scope = getScope(cx);
114
115 scope->getProto<BSONInfo>().newObject(obj);
116 JS_SetPrivate(obj, scope->trackedNew<BSONHolder>(bson, parent, scope, ro));
117 }
118
finalize(JSFreeOp * fop,JSObject * obj)119 void BSONInfo::finalize(JSFreeOp* fop, JSObject* obj) {
120 auto holder = static_cast<BSONHolder*>(JS_GetPrivate(obj));
121
122 if (!holder)
123 return;
124
125 getScope(fop)->trackedDelete(holder);
126 }
127
enumerate(JSContext * cx,JS::HandleObject obj,JS::AutoIdVector & properties,bool enumerableOnly)128 void BSONInfo::enumerate(JSContext* cx,
129 JS::HandleObject obj,
130 JS::AutoIdVector& properties,
131 bool enumerableOnly) {
132 auto holder = getValidHolder(cx, obj);
133
134 if (!holder)
135 return;
136
137 BSONObjIterator i(holder->_obj);
138
139 ObjectWrapper o(cx, obj);
140 JS::RootedValue val(cx);
141 JS::RootedId id(cx);
142
143 while (i.more()) {
144 BSONElement e = i.next();
145
146 // TODO: when we get heterogenous set lookup, switch to StringData
147 // rather than involving the temporary string
148 if (holder->_removed.find(e.fieldName()) != holder->_removed.end())
149 continue;
150
151 ValueReader(cx, &val).fromStringData(e.fieldNameStringData());
152
153 if (!JS_ValueToId(cx, val, &id))
154 uasserted(ErrorCodes::JSInterpreterFailure, "Failed to invoke JS_ValueToId");
155
156 properties.append(id);
157 }
158 }
159
setProperty(JSContext * cx,JS::HandleObject obj,JS::HandleId id,JS::MutableHandleValue vp,JS::ObjectOpResult & result)160 void BSONInfo::setProperty(JSContext* cx,
161 JS::HandleObject obj,
162 JS::HandleId id,
163 JS::MutableHandleValue vp,
164 JS::ObjectOpResult& result) {
165 auto holder = getValidHolder(cx, obj);
166
167 if (holder) {
168 if (holder->_readOnly) {
169 uasserted(ErrorCodes::BadValue, "Read only object");
170 return;
171 }
172
173 auto iter = holder->_removed.find(IdWrapper(cx, id).toString());
174
175 if (iter != holder->_removed.end()) {
176 holder->_removed.erase(iter);
177 }
178
179 holder->_altered = true;
180 }
181
182 ObjectWrapper(cx, obj).defineProperty(id, vp, JSPROP_ENUMERATE);
183 result.succeed();
184 }
185
delProperty(JSContext * cx,JS::HandleObject obj,JS::HandleId id,JS::ObjectOpResult & result)186 void BSONInfo::delProperty(JSContext* cx,
187 JS::HandleObject obj,
188 JS::HandleId id,
189 JS::ObjectOpResult& result) {
190 auto holder = getValidHolder(cx, obj);
191
192 if (holder) {
193 if (holder->_readOnly) {
194 uasserted(ErrorCodes::BadValue, "Read only object");
195 return;
196 }
197
198 holder->_altered = true;
199
200 JSStringWrapper jsstr;
201 holder->_removed[IdWrapper(cx, id).toStringData(&jsstr)] = true;
202 }
203
204 result.succeed();
205 }
206
resolve(JSContext * cx,JS::HandleObject obj,JS::HandleId id,bool * resolvedp)207 void BSONInfo::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp) {
208 auto holder = getValidHolder(cx, obj);
209
210 *resolvedp = false;
211
212 if (!holder) {
213 return;
214 }
215
216 IdWrapper idw(cx, id);
217 JSStringWrapper jsstr;
218
219 auto sname = idw.toStringData(&jsstr);
220
221 if (!holder->_readOnly && holder->_removed.find(sname.toString()) != holder->_removed.end()) {
222 return;
223 }
224
225 ObjectWrapper o(cx, obj);
226
227 if (holder->_obj.hasField(sname)) {
228 auto elem = holder->_obj[sname];
229
230 JS::RootedValue vp(cx);
231
232 ValueReader(cx, &vp).fromBSONElement(elem, holder->getOwner(), holder->_readOnly);
233
234 o.defineProperty(id, vp, JSPROP_ENUMERATE);
235
236 if (!holder->_readOnly && (elem.type() == mongo::Object || elem.type() == mongo::Array)) {
237 // if accessing a subobject, we have no way to know if
238 // modifications are being made on writable objects
239
240 holder->_altered = true;
241 }
242
243 *resolvedp = true;
244 }
245 }
246
originalBSON(JSContext * cx,JS::HandleObject obj)247 std::tuple<BSONObj*, bool> BSONInfo::originalBSON(JSContext* cx, JS::HandleObject obj) {
248 std::tuple<BSONObj*, bool> out(nullptr, false);
249
250 if (auto holder = getValidHolder(cx, obj))
251 out = std::make_tuple(&holder->_obj, holder->_altered);
252
253 return out;
254 }
255
call(JSContext * cx,JS::CallArgs args)256 void BSONInfo::Functions::bsonWoCompare::call(JSContext* cx, JS::CallArgs args) {
257 if (args.length() != 2)
258 uasserted(ErrorCodes::BadValue, "bsonWoCompare needs 2 arguments");
259
260 if (!args.get(0).isObject())
261 uasserted(ErrorCodes::BadValue, "first argument to bsonWoCompare must be an object");
262
263 if (!args.get(1).isObject())
264 uasserted(ErrorCodes::BadValue, "second argument to bsonWoCompare must be an object");
265
266 BSONObj firstObject = ValueWriter(cx, args.get(0)).toBSON();
267 BSONObj secondObject = ValueWriter(cx, args.get(1)).toBSON();
268
269 args.rval().setInt32(firstObject.woCompare(secondObject));
270 }
271
call(JSContext * cx,JS::CallArgs args)272 void BSONInfo::Functions::bsonBinaryEqual::call(JSContext* cx, JS::CallArgs args) {
273 if (args.length() != 2)
274 uasserted(ErrorCodes::BadValue, "bsonBinaryEqual needs 2 arguments");
275
276 if (!args.get(0).isObject())
277 uasserted(ErrorCodes::BadValue, "first argument to bsonBinaryEqual must be an object");
278
279 if (!args.get(1).isObject())
280 uasserted(ErrorCodes::BadValue, "second argument to bsonBinaryEqual must be an object");
281
282 BSONObj firstObject = ValueWriter(cx, args.get(0)).toBSON();
283 BSONObj secondObject = ValueWriter(cx, args.get(1)).toBSON();
284
285 args.rval().setBoolean(firstObject.binaryEqual(secondObject));
286 }
287
postInstall(JSContext * cx,JS::HandleObject global,JS::HandleObject proto)288 void BSONInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
289 JS::RootedValue value(cx);
290 value.setBoolean(true);
291
292 ObjectWrapper(cx, proto).defineProperty(InternedString::_bson, value, 0);
293 }
294
295 } // namespace mozjs
296 } // namespace mongo
297