Ejemplo n.º 1
0
namespace mozjs {

const JSFunctionSpec CursorHandleInfo::methods[2] = {
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(zeroCursorId, CursorHandleInfo), JS_FS_END,
};

const char* const CursorHandleInfo::className = "CursorHandle";

namespace {

long long* getCursorId(JSObject* thisv) {
    CursorHandleInfo::CursorTracker* tracker =
        static_cast<CursorHandleInfo::CursorTracker*>(JS_GetPrivate(thisv));
    if (tracker) {
        return &tracker->cursorId;
    }

    return nullptr;
}

long long* getCursorId(JS::CallArgs& args) {
    return getCursorId(args.thisv().toObjectOrNull());
}

}  // namespace

void CursorHandleInfo::finalize(JSFreeOp* fop, JSObject* obj) {
    auto cursorTracker = static_cast<CursorHandleInfo::CursorTracker*>(JS_GetPrivate(obj));
    if (cursorTracker) {
        const long long cursorId = cursorTracker->cursorId;
        if (cursorId) {
            try {
                cursorTracker->client->killCursor(cursorTracker->ns, cursorId);
            } catch (...) {
                auto status = exceptionToStatus();

                try {
                    LOG(0) << "Failed to kill cursor " << cursorId << " due to " << status;
                } catch (...) {
                    // This is here in case logging fails.
                }
            }
        }

        getScope(fop)->trackedDelete(cursorTracker);
    }
}

void CursorHandleInfo::Functions::zeroCursorId::call(JSContext* cx, JS::CallArgs args) {
    long long* cursorId = getCursorId(args);
    if (cursorId) {
        *cursorId = 0;
    }
}

}  // namespace mozjs
Ejemplo n.º 2
0
Archivo: oid.cpp Proyecto: DINKIN/mongo
namespace mozjs {

const JSFunctionSpec OIDInfo::methods[3] = {
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(toString, OIDInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(toJSON, OIDInfo),
    JS_FS_END,
};

const char* const OIDInfo::className = "ObjectId";

void OIDInfo::finalize(JSFreeOp* fop, JSObject* obj) {
    auto oid = static_cast<OID*>(JS_GetPrivate(obj));

    if (oid) {
        getScope(fop)->trackedDelete(oid);
    }
}

void OIDInfo::Functions::toString::call(JSContext* cx, JS::CallArgs args) {
    auto oid = static_cast<OID*>(JS_GetPrivate(args.thisv().toObjectOrNull()));

    std::string str = str::stream() << "ObjectId(\"" << oid->toString() << "\")";

    ValueReader(cx, args.rval()).fromStringData(str);
}

void OIDInfo::Functions::toJSON::call(JSContext* cx, JS::CallArgs args) {
    auto oid = static_cast<OID*>(JS_GetPrivate(args.thisv().toObjectOrNull()));

    ValueReader(cx, args.rval()).fromBSON(BSON("$oid" << oid->toString()), nullptr, false);
}

void OIDInfo::Functions::getter::call(JSContext* cx, JS::CallArgs args) {
    auto oid = static_cast<OID*>(JS_GetPrivate(args.thisv().toObjectOrNull()));

    ValueReader(cx, args.rval()).fromStringData(oid->toString());
}

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());
}

void OIDInfo::make(JSContext* cx, const OID& oid, JS::MutableHandleValue out) {
    auto scope = getScope(cx);

    JS::RootedObject thisv(cx);
    scope->getProto<OIDInfo>().newObject(&thisv);
    JS_SetPrivate(thisv, scope->trackedNew<OID>(oid));

    out.setObjectOrNull(thisv);
}

OID OIDInfo::getOID(JSContext* cx, JS::HandleValue value) {
    JS::RootedObject obj(cx, value.toObjectOrNull());
    return getOID(cx, obj);
}

OID OIDInfo::getOID(JSContext* cx, JS::HandleObject object) {
    auto oid = static_cast<OID*>(JS_GetPrivate(object));

    if (oid) {
        return *oid;
    }

    uasserted(ErrorCodes::BadValue, "Can't call getOID on OID prototype");
}

void OIDInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
    JS::RootedValue undef(cx);
    undef.setUndefined();

    if (!JS_DefinePropertyById(cx,
                               proto,
                               getScope(cx)->getInternedStringId(InternedString::str),
                               undef,
                               JSPROP_ENUMERATE | JSPROP_SHARED,
                               smUtils::wrapConstrainedMethod<Functions::getter, true, OIDInfo>,
                               nullptr)) {
        uasserted(ErrorCodes::JSInterpreterFailure, "Failed to JS_DefinePropertyById");
    }
}

}  // namespace mozjs
Ejemplo n.º 3
0
namespace mozjs {

const JSFunctionSpec BinDataInfo::methods[5] = {
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(base64, BinDataInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(hex, BinDataInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(toString, BinDataInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(toJSON, BinDataInfo),
    JS_FS_END,
};

const JSFunctionSpec BinDataInfo::freeFunctions[4] = {
    MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(HexData, JSFUN_CONSTRUCTOR),
    MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(MD5, JSFUN_CONSTRUCTOR),
    MONGO_ATTACH_JS_FUNCTION_WITH_FLAGS(UUID, JSFUN_CONSTRUCTOR),
    JS_FS_END,
};

const char* const BinDataInfo::className = "BinData";

namespace {

void hexToBinData(JSContext* cx,
                  int type,
                  const JS::Handle<JS::Value> hexdata,
                  JS::MutableHandleValue out) {
    auto scope = getScope(cx);
    uassert(ErrorCodes::BadValue, "BinData data must be a String", hexdata.isString());

    auto hexstr = ValueWriter(cx, hexdata).toString();

    uassert(
        ErrorCodes::BadValue, "BinData hex string must be an even length", hexstr.size() % 2 == 0);
    auto len = hexstr.size() / 2;

    std::unique_ptr<char[]> data(new char[len]);
    const char* src = hexstr.c_str();
    for (size_t i = 0; i < len; i++) {
        int src_index = i * 2;
        if (!std::isxdigit(src[src_index]) || !std::isxdigit(src[src_index + 1]))
            uasserted(ErrorCodes::BadValue, "Invalid hex character in string");
        data[i] = uassertStatusOK(fromHex(src + src_index));
    }

    std::string encoded = base64::encode(data.get(), len);
    JS::AutoValueArray<2> args(cx);

    args[0].setInt32(type);
    ValueReader(cx, args[1]).fromStringData(encoded);
    return scope->getProto<BinDataInfo>().newInstance(args, out);
}

std::string* getEncoded(JS::HandleValue thisv) {
    return static_cast<std::string*>(JS_GetPrivate(thisv.toObjectOrNull()));
}

std::string* getEncoded(JSObject* thisv) {
    return static_cast<std::string*>(JS_GetPrivate(thisv));
}

}  // namespace

void BinDataInfo::finalize(js::FreeOp* fop, JSObject* obj) {
    auto str = getEncoded(obj);

    if (str) {
        getScope(fop)->trackedDelete(str);
    }
}

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());
}

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 BinDataInfo::Functions::HexData::call(JSContext* cx, JS::CallArgs args) {
    if (args.length() != 2)
        uasserted(ErrorCodes::BadValue, "HexData needs 2 arguments");

    JS::RootedValue type(cx, args.get(0));

    if (!type.isNumber() || type.toInt32() < 0 || type.toInt32() > 255)
        uasserted(ErrorCodes::BadValue,
                  "HexData subtype must be a Number between 0 and 255 inclusive");

    hexToBinData(cx, type.toInt32(), args.get(1), args.rval());
}

void BinDataInfo::Functions::toString::call(JSContext* cx, JS::CallArgs args) {
    ObjectWrapper o(cx, args.thisv());

    auto str = getEncoded(args.thisv());

    str::stream ss;
    auto binType = o.getNumber(InternedString::type);

    if (binType == newUUID) {
        auto decoded = mongo::base64::decode(*str);

        // If this is in fact a UUID, use a more friendly string representation.
        if (decoded.length() == mongo::UUID::kNumBytes) {
            mongo::UUID uuid = mongo::UUID::fromCDR({decoded.data(), decoded.length()});
            ss << "UUID(\"" << uuid.toString() << "\")";
            ValueReader(cx, args.rval()).fromStringData(ss.operator std::string());
            return;
        }
    }

    ss << "BinData(" << binType << ",\"" << *str << "\")";
    ValueReader(cx, args.rval()).fromStringData(ss.operator std::string());
}

void BinDataInfo::Functions::toJSON::call(JSContext* cx, JS::CallArgs args) {
    ObjectWrapper o(cx, args.thisv());

    auto data_str = getEncoded(args.thisv());

    std::stringstream ss;
    ss << std::hex;
    ss.width(2);
    ss.fill('0');
    ss << o.getNumber(InternedString::type);

    ValueReader(cx, args.rval())
        .fromBSON(BSON("$binary" << *data_str << "$type" << ss.str()), nullptr, false);
}

void BinDataInfo::Functions::base64::call(JSContext* cx, JS::CallArgs args) {
    auto str = getEncoded(args.thisv());

    ValueReader(cx, args.rval()).fromStringData(*str);
}

void BinDataInfo::Functions::hex::call(JSContext* cx, JS::CallArgs args) {
    auto str = getEncoded(args.thisv());

    std::string data = mongo::base64::decode(*str);
    std::stringstream ss;
    ss.setf(std::ios_base::hex, std::ios_base::basefield);
    ss.fill('0');
    ss.setf(std::ios_base::right, std::ios_base::adjustfield);
    for (auto it = data.begin(); it != data.end(); ++it) {
        unsigned v = (unsigned char)*it;
        ss << std::setw(2) << v;
    }

    ValueReader(cx, args.rval()).fromStringData(ss.str());
}

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);
}

}  // namespace mozjs
Ejemplo n.º 4
0
namespace mozjs {

const JSFunctionSpec SessionInfo::methods[8] = {
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(end, SessionInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(getId, SessionInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(getTxnState, SessionInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(setTxnState, SessionInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(getTxnNumber, SessionInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(setTxnNumber, SessionInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(incrementTxnNumber, SessionInfo),
    JS_FS_END,
};

const char* const SessionInfo::className = "Session";
struct SessionHolder {
    enum class TransactionState { kActive, kInactive, kCommitted, kAborted };
    // txnNumber starts at -1 because when we increment it, the first transaction
    // and retryable write will both have a txnNumber of 0.
    SessionHolder(std::shared_ptr<DBClientBase> client, BSONObj lsid)
        : client(std::move(client)),
          lsid(std::move(lsid)),
          txnState(TransactionState::kInactive),
          txnNumber(-1) {}

    std::shared_ptr<DBClientBase> client;
    BSONObj lsid;
    TransactionState txnState;
    std::int64_t txnNumber;
};

namespace {

StringData transactionStateName(SessionHolder::TransactionState state) {
    switch (state) {
        case SessionHolder::TransactionState::kActive:
            return "active"_sd;
        case SessionHolder::TransactionState::kInactive:
            return "inactive"_sd;
        case SessionHolder::TransactionState::kCommitted:
            return "committed"_sd;
        case SessionHolder::TransactionState::kAborted:
            return "aborted"_sd;
    }

    MONGO_UNREACHABLE;
}

SessionHolder::TransactionState transactionStateEnum(StringData name) {
    if (name == "active"_sd) {
        return SessionHolder::TransactionState::kActive;
    } else if (name == "inactive"_sd) {
        return SessionHolder::TransactionState::kInactive;
    } else if (name == "committed"_sd) {
        return SessionHolder::TransactionState::kCommitted;
    } else if (name == "aborted"_sd) {
        return SessionHolder::TransactionState::kAborted;
    } else {
        uasserted(ErrorCodes::BadValue, str::stream() << "Invalid TransactionState name: " << name);
    }
}

SessionHolder* getHolder(JSObject* thisv) {
    return static_cast<SessionHolder*>(JS_GetPrivate(thisv));
}

SessionHolder* getHolder(JS::CallArgs& args) {
    return getHolder(args.thisv().toObjectOrNull());
}

void endSession(SessionHolder* holder) {
    if (!holder->client) {
        return;
    }

    BSONObj out;

    if (holder->txnState == SessionHolder::TransactionState::kActive) {
        holder->txnState = SessionHolder::TransactionState::kAborted;
        BSONObj abortObj = BSON("abortTransaction" << 1 << "lsid" << holder->lsid << "txnNumber"
                                                   << holder->txnNumber
                                                   << "autocommit"
                                                   << false);

        MONGO_COMPILER_VARIABLE_UNUSED auto ignored =
            holder->client->runCommand("admin", abortObj, out);
    }

    EndSessions es;

    es.setEndSessions({holder->lsid});

    MONGO_COMPILER_VARIABLE_UNUSED auto ignored =
        holder->client->runCommand("admin", es.toBSON(), out);

    holder->client.reset();
}

}  // namespace

void SessionInfo::finalize(js::FreeOp* fop, JSObject* obj) {
    auto holder = getHolder(obj);

    if (holder) {
        const auto lsid = holder->lsid;

        try {
            endSession(holder);
        } catch (...) {
            auto status = exceptionToStatus();

            try {
                LOG(0) << "Failed to end session " << lsid << " due to " << status;
            } catch (...) {
                // This is here in case logging fails.
            }
        }

        getScope(fop)->trackedDelete(holder);
    }
}

void SessionInfo::Functions::end::call(JSContext* cx, JS::CallArgs args) {
    auto holder = getHolder(args);
    invariant(holder);

    endSession(holder);

    args.rval().setUndefined();
}

void SessionInfo::Functions::getId::call(JSContext* cx, JS::CallArgs args) {
    auto holder = getHolder(args);
    invariant(holder);

    ValueReader(cx, args.rval()).fromBSON(holder->lsid, nullptr, 1);
}

void SessionInfo::Functions::getTxnState::call(JSContext* cx, JS::CallArgs args) {
    auto holder = getHolder(args);
    invariant(holder);
    uassert(ErrorCodes::BadValue, "getTxnState takes no arguments", args.length() == 0);

    ValueReader(cx, args.rval()).fromStringData(transactionStateName(holder->txnState));
}

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::getTxnNumber::call(JSContext* cx, JS::CallArgs args) {
    auto holder = getHolder(args);
    invariant(holder);
    uassert(ErrorCodes::BadValue, "getTxnNumber takes no arguments", args.length() == 0);

    ValueReader(cx, args.rval()).fromInt64(holder->txnNumber);
}

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();
}

void SessionInfo::Functions::incrementTxnNumber::call(JSContext* cx, JS::CallArgs args) {
    auto holder = getHolder(args);
    invariant(holder);
    uassert(ErrorCodes::BadValue, "incrementTxnNumber takes no arguments", args.length() == 0);

    ++holder->txnNumber;
    args.rval().setUndefined();
}

void SessionInfo::make(JSContext* cx,
                       JS::MutableHandleObject obj,
                       std::shared_ptr<DBClientBase> client,
                       BSONObj lsid) {
    auto scope = getScope(cx);

    scope->getProto<SessionInfo>().newObject(obj);
    JS_SetPrivate(obj, scope->trackedNew<SessionHolder>(std::move(client), std::move(lsid)));
}

}  // namespace mozjs
Ejemplo n.º 5
0
namespace mozjs {

const JSFunctionSpec CursorInfo::methods[7] = {
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(close, CursorInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(hasNext, CursorInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(next, CursorInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(objsLeftInBatch, CursorInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(readOnly, CursorInfo),
    MONGO_ATTACH_JS_CONSTRAINED_METHOD_NO_PROTO(isClosed, CursorInfo),
    JS_FS_END,
};

const char* const CursorInfo::className = "Cursor";

namespace {

DBClientCursor* getCursor(JSObject* thisv) {
    return static_cast<CursorInfo::CursorHolder*>(JS_GetPrivate(thisv))->cursor.get();
}

DBClientCursor* getCursor(JS::CallArgs& args) {
    return getCursor(args.thisv().toObjectOrNull());
}

}  // namespace

void CursorInfo::finalize(js::FreeOp* fop, JSObject* obj) {
    auto cursor = static_cast<CursorInfo::CursorHolder*>(JS_GetPrivate(obj));

    if (cursor) {
        getScope(fop)->trackedDelete(cursor);
    }
}

void CursorInfo::Functions::next::call(JSContext* cx, JS::CallArgs args) {
    auto cursor = getCursor(args);

    if (!cursor) {
        args.rval().setUndefined();
        return;
    }

    ObjectWrapper o(cx, args.thisv());

    BSONObj bson = cursor->next();
    bool ro = o.hasField(InternedString::_ro) ? o.getBoolean(InternedString::_ro) : false;

    // getOwned because cursor->next() gives us unowned bson from an internal
    // buffer and we need to make a copy
    ValueReader(cx, args.rval()).fromBSON(bson.getOwned(), nullptr, ro);
}

void CursorInfo::Functions::hasNext::call(JSContext* cx, JS::CallArgs args) {
    auto cursor = getCursor(args);

    if (!cursor) {
        args.rval().setBoolean(false);
        return;
    }

    args.rval().setBoolean(cursor->more());
}

void CursorInfo::Functions::objsLeftInBatch::call(JSContext* cx, JS::CallArgs args) {
    auto cursor = getCursor(args);

    if (!cursor) {
        args.rval().setInt32(0);
        return;
    }

    args.rval().setInt32(cursor->objsLeftInBatch());
}

void CursorInfo::Functions::readOnly::call(JSContext* cx, JS::CallArgs args) {
    ObjectWrapper(cx, args.thisv()).setBoolean(InternedString::_ro, true);

    args.rval().set(args.thisv());
}

void CursorInfo::Functions::close::call(JSContext* cx, JS::CallArgs args) {
    auto cursor = getCursor(args);

    if (cursor)
        cursor->kill();

    args.rval().setUndefined();
}

void CursorInfo::Functions::isClosed::call(JSContext* cx, JS::CallArgs args) {
    auto cursor = getCursor(args);

    if (!cursor) {
        args.rval().setBoolean(true);
        return;
    }

    args.rval().setBoolean(cursor->isDead());
}

}  // namespace mozjs