void BSONInfo::Functions::bsonWoCompare(JSContext* cx, JS::CallArgs args) { if (args.length() != 2) uasserted(ErrorCodes::BadValue, "bsonWoCompare needs 2 argument"); if (!args.get(0).isObject()) uasserted(ErrorCodes::BadValue, "first argument to bsonWoCompare must be an object"); if (!args.get(1).isObject()) uasserted(ErrorCodes::BadValue, "second argument to bsonWoCompare must be an object"); BSONObj firstObject = ValueWriter(cx, args.get(0)).toBSON(); BSONObj secondObject = ValueWriter(cx, args.get(1)).toBSON(); args.rval().setInt32(firstObject.woCompare(secondObject)); }
BSONObj MozJSImplScope::callThreadArgs(const BSONObj& args) { MozJSEntry entry(this); JS::RootedValue function(_context); ValueReader(_context, &function).fromBSONElement(args.firstElement(), true); int argc = args.nFields() - 1; JS::AutoValueVector argv(_context); BSONObjIterator it(args); it.next(); JS::RootedValue value(_context); for (int i = 0; i < argc; ++i) { ValueReader(_context, &value).fromBSONElement(*it, true); argv.append(value); it.next(); } JS::RootedValue out(_context); JS::RootedObject thisv(_context); _checkErrorState(JS::Call(_context, thisv, function, argv, &out), false, true); BSONObjBuilder b; ValueWriter(_context, out).writeThis(&b, "ret"); return b.obj(); }
void MozJSImplScope::_reportError(JSContext* cx, const char* message, JSErrorReport* report) { auto scope = getScope(cx); if (!JSREPORT_IS_WARNING(report->flags)) { str::stream ss; ss << message; // TODO: something far more elaborate that mimics the stack printing from v8 JS::RootedValue excn(cx); if (JS_GetPendingException(cx, &excn) && excn.isObject()) { JS::RootedValue stack(cx); ObjectWrapper(cx, excn).getValue("stack", &stack); auto str = ValueWriter(cx, stack).toString(); if (str.empty()) { ss << " @" << report->filename << ":" << report->lineno << ":" << report->column << "\n"; } else { ss << " :\n" << str; } } scope->_status = Status( JSErrorReportToStatus(cx, report, ErrorCodes::JSInterpreterFailure, message).code(), ss); } }
void BinDataInfo::Functions::UUID::call(JSContext* cx, JS::CallArgs args) { boost::optional<mongo::UUID> uuid; if (args.length() == 0) { uuid = mongo::UUID::gen(); } else { uassert(ErrorCodes::BadValue, "UUID needs 0 or 1 arguments", args.length() == 1); auto arg = args.get(0); std::string str = ValueWriter(cx, arg).toString(); // For backward compatibility quietly accept and convert 32-character hex strings to // BinData(3, ...) as used for the deprecated UUID v3 BSON type. if (str.length() == 32) { hexToBinData(cx, bdtUUID, arg, args.rval()); return; } uuid = uassertStatusOK(mongo::UUID::parse(str)); }; ConstDataRange cdr = uuid->toCDR(); std::string encoded = mongo::base64::encode(cdr.data(), cdr.length()); JS::AutoValueArray<2> newArgs(cx); newArgs[0].setInt32(newUUID); ValueReader(cx, newArgs[1]).fromStringData(encoded); getScope(cx)->getProto<BinDataInfo>().newInstance(newArgs, args.rval()); }
JSThreadConfig(JSContext* cx, JS::CallArgs args) : _started(false), _done(false), _sharedData(new SharedData()) { auto scope = getScope(cx); uassert(ErrorCodes::JSInterpreterFailure, "need at least one argument", args.length() > 0); uassert(ErrorCodes::JSInterpreterFailure, "first argument must be a function", args.get(0).isObject() && JS_ObjectIsFunction(cx, args.get(0).toObjectOrNull())); BSONObjBuilder b; for (unsigned i = 0; i < args.length(); ++i) { // 10 decimal digits for a 32 bit unsigned, then 1 for the null char buf[11]; std::sprintf(buf, "%i", i); ValueWriter(cx, args.get(i)).writeThis(&b, buf); } _sharedData->_args = b.obj(); _sharedData->_stack = currentJSStackToString(cx); if (!scope->getParentStack().empty()) { _sharedData->_stack = _sharedData->_stack + scope->getParentStack(); } }
void DBInfo::construct(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); if (args.length() != 2) uasserted(ErrorCodes::BadValue, "db constructor requires 2 arguments"); for (unsigned i = 0; i < args.length(); ++i) { uassert(ErrorCodes::BadValue, "db initializer called with undefined argument", !args.get(i).isUndefined()); } JS::RootedObject thisv(cx); scope->getProto<DBInfo>().newObject(&thisv); ObjectWrapper o(cx, thisv); o.setValue(InternedString::_mongo, args.get(0)); o.setValue(InternedString::_name, args.get(1)); std::string dbName = ValueWriter(cx, args.get(1)).toString(); if (!NamespaceString::validDBName(dbName, NamespaceString::DollarInDbNameBehavior::Allow)) uasserted(ErrorCodes::BadValue, str::stream() << "[" << dbName << "] is not a valid database name"); args.rval().setObjectOrNull(thisv); }
void TimestampInfo::construct(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); JS::RootedObject thisv(cx); scope->getProto<TimestampInfo>().newObject(&thisv); ObjectWrapper o(cx, thisv); if (args.length() == 0) { o.setNumber(InternedString::t, 0); o.setNumber(InternedString::i, 0); } else if (args.length() == 2) { if (!args.get(0).isNumber()) uasserted(ErrorCodes::BadValue, "Timestamp time must be a number"); if (!args.get(1).isNumber()) uasserted(ErrorCodes::BadValue, "Timestamp increment must be a number"); int64_t t = ValueWriter(cx, args.get(0)).toInt64(); int64_t largestVal = int64_t(Timestamp::max().getSecs()); if (t > largestVal) uasserted(ErrorCodes::BadValue, str::stream() << "The first argument must be in seconds; " << t << " is too large (max " << largestVal << ")"); o.setValue(InternedString::t, args.get(0)); o.setValue(InternedString::i, args.get(1)); } else { uasserted(ErrorCodes::BadValue, "Timestamp needs 0 or 2 arguments"); } args.rval().setObjectOrNull(thisv); }
void DBCollectionInfo::construct(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); if (args.length() != 4) uasserted(ErrorCodes::BadValue, "collection constructor requires 4 arguments"); for (unsigned i = 0; i < args.length(); ++i) { uassert(ErrorCodes::BadValue, "collection constructor called with undefined argument", !args.get(i).isUndefined()); } JS::RootedObject thisv(cx); scope->getDbCollectionProto().newObject(&thisv); ObjectWrapper o(cx, thisv); o.setValue("_mongo", args.get(0)); o.setValue("_db", args.get(1)); o.setValue("_shortName", args.get(2)); o.setValue("_fullName", args.get(3)); std::string fullName = ValueWriter(cx, args.get(3)).toString(); auto context = scope->getOpContext(); if (context && haveLocalShardingInfo(context->getClient(), fullName)) uasserted(ErrorCodes::BadValue, "can't use sharded collection from db.eval"); args.rval().setObjectOrNull(thisv); }
void SessionInfo::Functions::setTxnState::call(JSContext* cx, JS::CallArgs args) { auto holder = getHolder(args); invariant(holder); uassert(ErrorCodes::BadValue, "setTxnState takes 1 argument", args.length() == 1); auto arg = args.get(0); holder->txnState = transactionStateEnum(ValueWriter(cx, arg).toString().c_str()); args.rval().setUndefined(); }
void SessionInfo::Functions::setTxnNumber::call(JSContext* cx, JS::CallArgs args) { auto holder = getHolder(args); invariant(holder); uassert(ErrorCodes::BadValue, "setTxnNumber takes 1 argument", args.length() == 1); auto arg = args.get(0); holder->txnNumber = ValueWriter(cx, arg).toInt64(); args.rval().setUndefined(); }
std::string parseJSFunctionOrExpression(JSContext* cx, const StringData input) { JS::RootedValue jsStrOut(cx); JS::RootedValue jsStrIn(cx); ValueReader(cx, &jsStrIn).fromStringData(input); ObjectWrapper helpersWrapper(cx, getScope(cx)->getProto<MongoHelpersInfo>().getProto()); helpersWrapper.callMethod("functionExpressionParser", JS::HandleValueArray(jsStrIn), &jsStrOut); return ValueWriter(cx, jsStrOut).toString(); }
void BinDataInfo::Functions::MD5::call(JSContext* cx, JS::CallArgs args) { if (args.length() != 1) uasserted(ErrorCodes::BadValue, "MD5 needs 1 argument"); auto arg = args.get(0); auto str = ValueWriter(cx, arg).toString(); if (str.length() != 32) uasserted(ErrorCodes::BadValue, "MD5 string must have 32 characters"); hexToBinData(cx, MD5Type, arg, args.rval()); }
void OIDInfo::construct(JSContext* cx, JS::CallArgs args) { OID oid; if (args.length() == 0) { oid.init(); } else { auto str = ValueWriter(cx, args.get(0)).toString(); Scope::validateObjectIdString(str); oid.init(str); } make(cx, oid, args.rval()); }
bool MozJSImplScope::_checkErrorState(bool success, bool reportError, bool assertOnError) { if (success) return false; if (_quickExit) return false; if (_status.isOK()) { JS::RootedValue excn(_context); if (JS_GetPendingException(_context, &excn) && excn.isObject()) { str::stream ss; JS::RootedValue stack(_context); ObjectWrapper(_context, excn).getValue("stack", &stack); ss << ValueWriter(_context, excn).toString() << " :\n" << ValueWriter(_context, stack).toString(); _status = Status(ErrorCodes::JSInterpreterFailure, ss); } else { _status = Status(ErrorCodes::UnknownError, "Unknown Failure from JSInterpreter"); } } _error = _status.reason(); if (reportError) error() << _error << std::endl; // Clear the status state auto status = std::move(_status); if (assertOnError) { // Throw if necessary uassertStatusOK(status); } return true; }
void BinDataInfo::construct(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); if (args.length() != 2) { uasserted(ErrorCodes::BadValue, "BinData takes 2 arguments -- BinData(subtype,data)"); } auto type = args.get(0); auto typeNumber = ValueWriter(cx, type).toInt32(); if (!type.isNumber() || typeNumber < 0 || typeNumber > 255) { uasserted(ErrorCodes::BadValue, "BinData subtype must be a Number between 0 and 255 inclusive"); } auto utf = args.get(1); if (!utf.isString()) { uasserted(ErrorCodes::BadValue, "BinData data must be a String"); } auto str = ValueWriter(cx, utf).toString(); auto tmpBase64 = base64::decode(str); JS::RootedObject thisv(cx); scope->getProto<BinDataInfo>().newObject(&thisv); ObjectWrapper o(cx, thisv); JS::RootedValue len(cx); len.setInt32(tmpBase64.length()); o.defineProperty(InternedString::len, len, JSPROP_READONLY); o.defineProperty(InternedString::type, type, JSPROP_READONLY); JS_SetPrivate(thisv, scope->trackedNew<std::string>(std::move(str))); args.rval().setObjectOrNull(thisv); }
void ObjectInfo::Functions::bsonsize::call(JSContext* cx, JS::CallArgs args) { if (args.length() != 1) uasserted(ErrorCodes::BadValue, "bsonsize needs 1 argument"); if (args.get(0).isNull()) { args.rval().setInt32(0); return; } if (!args.get(0).isObject()) uasserted(ErrorCodes::BadValue, "argument to bsonsize has to be an object"); args.rval().setInt32(ValueWriter(cx, args.get(0)).toBSON().objsize()); }
void NumberDecimalInfo::construct(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); JS::RootedObject thisv(cx); scope->getProto<NumberDecimalInfo>().newObject(&thisv); Decimal128 x(0); if (args.length() == 0) { // Do nothing } else if (args.length() == 1) { x = ValueWriter(cx, args.get(0)).toDecimal128(); } else { uasserted(ErrorCodes::BadValue, "NumberDecimal takes 0 or 1 arguments"); } JS_SetPrivate(thisv, scope->trackedNew<Decimal128>(x)); args.rval().setObjectOrNull(thisv); }
void NumberIntInfo::construct(JSContext* cx, JS::CallArgs args) { auto scope = getScope(cx); JS::RootedObject thisv(cx); scope->getProto<NumberIntInfo>().newObject(&thisv); int32_t x = 0; if (args.length() == 0) { // Do nothing } else if (args.length() == 1) { x = ValueWriter(cx, args.get(0)).toInt32(); } else { uasserted(ErrorCodes::BadValue, "NumberInt takes 0 or 1 arguments"); } JS_SetPrivate(thisv, new int(x)); args.rval().setObjectOrNull(thisv); }
bool MozJSImplScope::exec(StringData code, const std::string& name, bool printResult, bool reportError, bool assertOnError, int timeoutMs) { MozJSEntry entry(this); JS::CompileOptions co(_context); setCompileOptions(&co); co.setFile(name.c_str()); JS::RootedScript script(_context); bool success = JS::Compile(_context, _global, co, code.rawData(), code.size(), &script); if (_checkErrorState(success, reportError, assertOnError)) return false; if (timeoutMs) _engine->getDeadlineMonitor().startDeadline(this, timeoutMs); JS::RootedValue out(_context); success = JS_ExecuteScript(_context, _global, script, &out); if (timeoutMs) _engine->getDeadlineMonitor().stopDeadline(this); if (_checkErrorState(success, reportError, assertOnError)) return false; ObjectWrapper(_context, _global).setValue(kExecResult, out); if (printResult && !out.isUndefined()) { // appears to only be used by shell std::cout << ValueWriter(_context, out).toString() << std::endl; } return true; }
void MozJSImplScope::_reportError(JSContext* cx, const char* message, JSErrorReport* report) { auto scope = getScope(cx); if (!JSREPORT_IS_WARNING(report->flags)) { str::stream ss; ss << message; // TODO: something far more elaborate that mimics the stack printing from v8 JS::RootedValue excn(cx); if (JS_GetPendingException(cx, &excn) && excn.isObject()) { JS::RootedValue stack(cx); ObjectWrapper(cx, excn).getValue("stack", &stack); ss << " :\n" << ValueWriter(cx, stack).toString(); } scope->_status = Status(report->errorNumber ? static_cast<ErrorCodes::Error>(report->errorNumber) : ErrorCodes::JSInterpreterFailure, ss); } }
void ObjectInfo::Functions::invalidForStorage::call(JSContext* cx, JS::CallArgs args) { if (args.length() != 1) uasserted(ErrorCodes::BadValue, "invalidForStorage needs 1 argument"); if (args.get(0).isNull()) { args.rval().setNull(); return; } if (!args.get(0).isObject()) uasserted(ErrorCodes::BadValue, "argument to invalidForStorage has to be an object"); Status validForStorage = ValueWriter(cx, args.get(0)).toBSON().storageValid(true); if (validForStorage.isOK()) { args.rval().setNull(); return; } std::string errmsg = str::stream() << validForStorage.codeString() << ": " << validForStorage.reason(); ValueReader(cx, args.rval()).fromStringData(errmsg); }
double ObjectWrapper::getNumber(Key key) { JS::RootedValue x(_context); getValue(key, &x); return ValueWriter(_context, x).toNumber(); }
long long ObjectWrapper::getNumberLongLong(Key key) { JS::RootedValue x(_context); getValue(key, &x); return ValueWriter(_context, x).toInt64(); }
int ObjectWrapper::type(Key key) { JS::RootedValue x(_context); getValue(key, &x); return ValueWriter(_context, x).type(); }
Decimal128 ObjectWrapper::getNumberDecimal(Key key) { JS::RootedValue x(_context); getValue(key, &x); return ValueWriter(_context, x).toDecimal128(); }
void ValueWriter::_writeObject(BSONObjBuilder* b, StringData sd, ObjectWrapper::WriteFieldRecursionFrames* frames) { auto scope = getScope(_context); // We open a block here because it's important that the two rooting types // we need (obj and o) go out of scope before we actually open a // new WriteFieldFrame (in the emplace at the bottom of the function). If // we don't do this, we'll destroy the local roots in this function body // before the frame we added, which will break the gc rooting list. { JS::RootedObject obj(_context, _value.toObjectOrNull()); ObjectWrapper o(_context, obj); auto jsclass = JS_GetClass(obj); if (jsclass) { if (scope->getProto<OIDInfo>().getJSClass() == jsclass) { b->append(sd, OIDInfo::getOID(_context, obj)); return; } if (scope->getProto<NumberLongInfo>().getJSClass() == jsclass) { long long out = NumberLongInfo::ToNumberLong(_context, obj); b->append(sd, out); return; } if (scope->getProto<NumberIntInfo>().getJSClass() == jsclass) { b->append(sd, NumberIntInfo::ToNumberInt(_context, obj)); return; } if (scope->getProto<CodeInfo>().getJSClass() == jsclass) { if (o.hasOwnField(InternedString::scope) // CodeWScope && o.type(InternedString::scope) == mongo::Object) { if (o.type(InternedString::code) != mongo::String) { uasserted(ErrorCodes::BadValue, "code must be a string"); } b->appendCodeWScope( sd, o.getString(InternedString::code), o.getObject(InternedString::scope)); } else { // Code if (o.type(InternedString::code) != mongo::String) { uasserted(ErrorCodes::BadValue, "code must be a string"); } b->appendCode(sd, o.getString(InternedString::code)); } return; } if (scope->getProto<NumberDecimalInfo>().getJSClass() == jsclass) { b->append(sd, NumberDecimalInfo::ToNumberDecimal(_context, obj)); return; } if (scope->getProto<DBPointerInfo>().getJSClass() == jsclass) { JS::RootedValue id(_context); o.getValue("id", &id); b->appendDBRef(sd, o.getString("ns"), OIDInfo::getOID(_context, id)); return; } if (scope->getProto<BinDataInfo>().getJSClass() == jsclass) { auto str = static_cast<std::string*>(JS_GetPrivate(obj)); auto binData = base64::decode(*str); b->appendBinData(sd, binData.size(), static_cast<mongo::BinDataType>( static_cast<int>(o.getNumber(InternedString::type))), binData.c_str()); return; } if (scope->getProto<TimestampInfo>().getJSClass() == jsclass) { Timestamp ot(o.getNumber("t"), o.getNumber("i")); b->append(sd, ot); return; } if (scope->getProto<MinKeyInfo>().getJSClass() == jsclass) { b->appendMinKey(sd); return; } if (scope->getProto<MaxKeyInfo>().getJSClass() == jsclass) { b->appendMaxKey(sd); return; } } auto protoKey = JS::IdentifyStandardInstance(obj); switch (protoKey) { case JSProto_Function: { uassert(16716, "cannot convert native function to BSON", !scope->getProto<NativeFunctionInfo>().instanceOf(obj)); JSStringWrapper jsstr; b->appendCode(sd, ValueWriter(_context, _value).toStringData(&jsstr)); return; } case JSProto_RegExp: { JS::RootedValue v(_context); v.setObjectOrNull(obj); std::string regex = ValueWriter(_context, v).toString(); regex = regex.substr(1); std::string r = regex.substr(0, regex.rfind('/')); std::string o = regex.substr(regex.rfind('/') + 1); b->appendRegex(sd, r, o); return; } case JSProto_Date: { JS::RootedValue dateval(_context); o.callMethod("getTime", &dateval); auto d = Date_t::fromMillisSinceEpoch(ValueWriter(_context, dateval).toInt64()); b->appendDate(sd, d); return; } default: break; } } // nested object or array // This emplace is effectively a recursive function call, as this code path // unwinds back to ObjectWrapper::toBSON. In that function we'll actually // write the child we've just pushed onto the frames stack. frames->emplace(_context, _value.toObjectOrNull(), b, sd); }
BSONObj ObjectWrapper::getObject(Key key) { JS::RootedValue x(_context); getValue(key, &x); return ValueWriter(_context, x).toBSON(); }
int ObjectWrapper::getNumberInt(Key key) { JS::RootedValue x(_context); getValue(key, &x); return ValueWriter(_context, x).toInt32(); }
std::string ObjectWrapper::getString(Key key) { JS::RootedValue x(_context); getValue(key, &x); return ValueWriter(_context, x).toString(); }
bool ObjectWrapper::getBoolean(Key key) { JS::RootedValue x(_context); getValue(key, &x); return ValueWriter(_context, x).toBoolean(); }