Exemple #1
0
namespace mozjs {

const JSFunctionSpec GlobalInfo::freeFunctions[4] = {
    MONGO_ATTACH_JS_FUNCTION(gc),
    MONGO_ATTACH_JS_FUNCTION(print),
    MONGO_ATTACH_JS_FUNCTION(version),
    JS_FS_END,
};

const char* const GlobalInfo::className = "Global";

namespace {

logger::MessageLogDomain* jsPrintLogDomain;

}  // namespace

void GlobalInfo::Functions::print::call(JSContext* cx, JS::CallArgs args) {
    logger::LogstreamBuilder builder(jsPrintLogDomain, getThreadName(), logger::LogSeverity::Log());
    std::ostream& ss = builder.stream();

    bool first = true;
    for (size_t i = 0; i < args.length(); i++) {
        if (first)
            first = false;
        else
            ss << " ";

        if (args.get(i).isNullOrUndefined()) {
            // failed to get object to convert
            ss << "[unknown type]";
            continue;
        }

        JSStringWrapper jsstr(cx, JS::ToString(cx, args.get(i)));
        ss << jsstr.toStringData();
    }
    ss << std::endl;

    args.rval().setUndefined();
}

void GlobalInfo::Functions::version::call(JSContext* cx, JS::CallArgs args) {
    ValueReader(cx, args.rval()).fromStringData(JS_VersionToString(JS_GetVersion(cx)));
}

void GlobalInfo::Functions::gc::call(JSContext* cx, JS::CallArgs args) {
    auto scope = getScope(cx);

    scope->gc();

    args.rval().setUndefined();
}

MONGO_INITIALIZER(JavascriptPrintDomain)(InitializerContext*) {
    jsPrintLogDomain = logger::globalLogManager()->getNamedDomain("javascriptOutput");
    return Status::OK();
}

}  // namespace mozjs
Exemple #2
0
namespace mozjs {

const JSFunctionSpec ObjectInfo::methods[3] = {
    MONGO_ATTACH_JS_FUNCTION(bsonsize), MONGO_ATTACH_JS_FUNCTION(invalidForStorage), JS_FS_END,
};

const char* const ObjectInfo::className = "Object";

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

}  // namespace mozjs
Exemple #3
0
namespace mozjs {

// These are all executed on some object that owns a js thread, rather than a
// jsthread itself, so CONSTRAINED_METHOD doesn't do the job here.
const JSFunctionSpec JSThreadInfo::threadMethods[6] = {
    MONGO_ATTACH_JS_FUNCTION(init),
    MONGO_ATTACH_JS_FUNCTION(start),
    MONGO_ATTACH_JS_FUNCTION(join),
    MONGO_ATTACH_JS_FUNCTION(hasFailed),
    MONGO_ATTACH_JS_FUNCTION(returnData),
    JS_FS_END,
};

const JSFunctionSpec JSThreadInfo::freeFunctions[3] = {
    MONGO_ATTACH_JS_FUNCTION(_threadInject),
    MONGO_ATTACH_JS_FUNCTION(_scopedThreadInject),
    JS_FS_END,
};

const char* const JSThreadInfo::className = "JSThread";

/**
 * Holder for JSThreads as exposed by fork() in the shell.
 *
 * The idea here is that we create a jsthread by taking a js function and its
 * parameters and encoding them into a single bson object. Then we spawn a
 * thread, have that thread do the work and join() it before checking it's
 * result (serialized through bson). We can check errors at any time by
 * checking a mutex guarded hasError().
 */
class JSThreadConfig {
public:
    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 start() {
        uassert(ErrorCodes::JSInterpreterFailure, "Thread already started", !_started);

        _thread = stdx::thread(JSThread(*this));
        _started = true;
    }

    void join() {
        uassert(ErrorCodes::JSInterpreterFailure, "Thread not running", _started && !_done);

        _thread.join();
        _done = true;
    }

    /**
     * Returns true if the JSThread terminated as a result of an error
     * during its execution, and false otherwise. This operation does
     * not block, nor does it require join() to have been called.
     */
    bool hasFailed() const {
        uassert(ErrorCodes::JSInterpreterFailure, "Thread not started", _started);

        return _sharedData->getErrored();
    }

    BSONObj returnData() {
        if (!_done)
            join();

        return _sharedData->_returnData;
    }

private:
    /**
     * SharedData between the calling thread and the callee
     *
     * JSThreadConfig doesn't always outlive its JSThread (for example, if the parent thread
     * garbage collects the JSThreadConfig before the JSThread has finished running), so any
     * data shared between them has to go in a shared_ptr.
     */
    class SharedData {
    public:
        SharedData() : _errored(false) {}

        void setErrored(bool value) {
            stdx::lock_guard<stdx::mutex> lck(_erroredMutex);
            _errored = value;
        }

        bool getErrored() {
            stdx::lock_guard<stdx::mutex> lck(_erroredMutex);
            return _errored;
        }

        /**
         * These three members aren't protected in any way, so you have to be
         * mindful about how they're used. I.e. _args/_stack need to be set
         * before start() and _returnData can't be touched until after join().
         */
        BSONObj _args;
        BSONObj _returnData;
        std::string _stack;

    private:
        stdx::mutex _erroredMutex;
        bool _errored;
    };

    /**
     * The callable object used by stdx::thread
     */
    class JSThread {
    public:
        JSThread(JSThreadConfig& config) : _sharedData(config._sharedData) {}

        void operator()() {
            try {
                MozJSImplScope scope(static_cast<MozJSScriptEngine*>(globalScriptEngine));

                scope.setParentStack(_sharedData->_stack);
                _sharedData->_returnData = scope.callThreadArgs(_sharedData->_args);
            } catch (...) {
                auto status = exceptionToStatus();

                log() << "js thread raised js exception: " << status.reason()
                      << _sharedData->_stack;
                _sharedData->setErrored(true);
                _sharedData->_returnData = BSON("ret" << BSONUndefined);
            }
        }

    private:
        std::shared_ptr<SharedData> _sharedData;
    };

    bool _started;
    bool _done;
    stdx::thread _thread;
    std::shared_ptr<SharedData> _sharedData;
};

namespace {

JSThreadConfig* getConfig(JSContext* cx, JS::CallArgs args) {
    JS::RootedValue value(cx);
    ObjectWrapper(cx, args.thisv()).getValue("_JSThreadConfig", &value);

    if (!value.isObject())
        uasserted(ErrorCodes::BadValue, "_JSThreadConfig not an object");

    if (!getScope(cx)->getProto<JSThreadInfo>().instanceOf(value))
        uasserted(ErrorCodes::BadValue, "_JSThreadConfig is not a JSThread");

    return static_cast<JSThreadConfig*>(JS_GetPrivate(value.toObjectOrNull()));
}

}  // namespace

void JSThreadInfo::finalize(JSFreeOp* fop, JSObject* obj) {
    auto config = static_cast<JSThreadConfig*>(JS_GetPrivate(obj));

    if (!config)
        return;

    delete config;
}

void JSThreadInfo::Functions::init::call(JSContext* cx, JS::CallArgs args) {
    auto scope = getScope(cx);

    JS::RootedObject obj(cx);
    scope->getProto<JSThreadInfo>().newObject(&obj);
    JSThreadConfig* config = new JSThreadConfig(cx, args);
    JS_SetPrivate(obj, config);

    ObjectWrapper(cx, args.thisv()).setObject("_JSThreadConfig", obj);

    args.rval().setUndefined();
}

void JSThreadInfo::Functions::start::call(JSContext* cx, JS::CallArgs args) {
    getConfig(cx, args)->start();

    args.rval().setUndefined();
}

void JSThreadInfo::Functions::join::call(JSContext* cx, JS::CallArgs args) {
    getConfig(cx, args)->join();

    args.rval().setUndefined();
}

void JSThreadInfo::Functions::hasFailed::call(JSContext* cx, JS::CallArgs args) {
    args.rval().setBoolean(getConfig(cx, args)->hasFailed());
}

void JSThreadInfo::Functions::returnData::call(JSContext* cx, JS::CallArgs args) {
    ValueReader(cx, args.rval())
        .fromBSONElement(getConfig(cx, args)->returnData().firstElement(), true);
}

void JSThreadInfo::Functions::_threadInject::call(JSContext* cx, JS::CallArgs args) {
    uassert(ErrorCodes::JSInterpreterFailure,
            "threadInject takes exactly 1 argument",
            args.length() == 1);
    uassert(ErrorCodes::JSInterpreterFailure,
            "threadInject needs to be passed a prototype",
            args.get(0).isObject());

    JS::RootedObject o(cx, args.get(0).toObjectOrNull());

    if (!JS_DefineFunctions(cx, o, JSThreadInfo::threadMethods))
        throwCurrentJSException(cx, ErrorCodes::JSInterpreterFailure, "Failed to define functions");

    args.rval().setUndefined();
}

void JSThreadInfo::Functions::_scopedThreadInject::call(JSContext* cx, JS::CallArgs args) {
    _threadInject::call(cx, args);
}

}  // namespace mozjs
Exemple #4
0
namespace mozjs {

const char* const BSONInfo::className = "BSON";

const JSFunctionSpec BSONInfo::freeFunctions[2] = {
    MONGO_ATTACH_JS_FUNCTION(bsonWoCompare), JS_FS_END,
};

namespace {

/**
 * Holder for bson objects which tracks state for the js wrapper
 *
 * Basically, we have read only and read/write variants, and a need to manage
 * the appearance of mutable state on the read/write versions.
 */
struct BSONHolder {
    BSONHolder(const BSONObj& obj, bool ro)
        : _obj(obj.getOwned()), _resolved(false), _readOnly(ro), _altered(false) {}

    BSONObj _obj;
    bool _resolved;
    bool _readOnly;
    bool _altered;
    std::set<std::string> _removed;
};

BSONHolder* getHolder(JSObject* obj) {
    return static_cast<BSONHolder*>(JS_GetPrivate(obj));
}

}  // namespace

void BSONInfo::make(JSContext* cx, JS::MutableHandleObject obj, BSONObj bson, bool ro) {
    auto scope = getScope(cx);

    scope->getBsonProto().newInstance(obj);
    JS_SetPrivate(obj, new BSONHolder(bson, ro));
}

void BSONInfo::finalize(JSFreeOp* fop, JSObject* obj) {
    auto holder = getHolder(obj);

    if (!holder)
        return;

    delete holder;
}

void BSONInfo::enumerate(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties) {
    auto holder = getHolder(obj);

    if (!holder)
        return;

    BSONObjIterator i(holder->_obj);

    ObjectWrapper o(cx, obj);
    JS::RootedValue val(cx);
    JS::RootedId id(cx);

    while (i.more()) {
        BSONElement e = i.next();

        // TODO: when we get heterogenous set lookup, switch to StringData
        // rather than involving the temporary string
        if (holder->_removed.count(e.fieldName()))
            continue;

        ValueReader(cx, &val).fromStringData(e.fieldNameStringData());

        if (!JS_ValueToId(cx, val, &id))
            uasserted(ErrorCodes::JSInterpreterFailure, "Failed to invoke JS_ValueToId");

        properties.append(id);
    }
}

void BSONInfo::setProperty(
    JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool strict, JS::MutableHandleValue vp) {
    auto holder = getHolder(obj);

    if (holder) {
        if (holder->_readOnly) {
            uasserted(ErrorCodes::BadValue, "Read only object");
        }

        auto iter = holder->_removed.find(IdWrapper(cx, id).toString());

        if (iter != holder->_removed.end()) {
            holder->_removed.erase(iter);
        }

        holder->_altered = true;
    }

    ObjectWrapper(cx, obj).defineProperty(id, vp, JSPROP_ENUMERATE);
}

void BSONInfo::delProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* succeeded) {
    auto holder = getHolder(obj);

    if (holder) {
        if (holder->_readOnly) {
            uasserted(ErrorCodes::BadValue, "Read only object");
        }

        holder->_altered = true;

        holder->_removed.insert(IdWrapper(cx, id).toString());
    }

    *succeeded = true;
}

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 BSONInfo::construct(JSContext* cx, JS::CallArgs args) {
    auto scope = getScope(cx);

    scope->getBsonProto().newObject(args.rval());
}

std::tuple<BSONObj*, bool> BSONInfo::originalBSON(JSContext* cx, JS::HandleObject obj) {
    std::tuple<BSONObj*, bool> out(nullptr, false);

    if (auto holder = getHolder(obj))
        out = std::make_tuple(&holder->_obj, holder->_altered);

    return out;
}

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

void BSONInfo::postInstall(JSContext* cx, JS::HandleObject global, JS::HandleObject proto) {
    JS::RootedValue value(cx);
    value.setBoolean(true);

    ObjectWrapper(cx, proto).defineProperty("_bson", value, 0);
}

}  // namespace mozjs