diff options
author | Matt Broadstone <mbroadst@mongodb.com> | 2022-12-20 18:47:07 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-12-20 19:39:17 +0000 |
commit | d72723568e1689fc31617009daf72345f8c18666 (patch) | |
tree | 0d387c25a60405326c92d618bc4ba1a5e44ce49c | |
parent | d2daeef1cef7ec97c17f265bf42762303adf5c4a (diff) | |
download | mongo-d72723568e1689fc31617009daf72345f8c18666.tar.gz |
SERVER-71628 Try to resolve baseUrl defined in nearby jsconfig.json
-rw-r--r-- | src/mongo/scripting/engine.h | 3 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/engine.cpp | 11 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/engine.h | 4 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/implscope.cpp | 13 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/module_loader.cpp | 95 | ||||
-rw-r--r-- | src/mongo/scripting/mozjs/module_loader.h | 7 | ||||
-rw-r--r-- | src/mongo/shell/mongo_main.cpp | 9 |
7 files changed, 123 insertions, 19 deletions
diff --git a/src/mongo/scripting/engine.h b/src/mongo/scripting/engine.h index 62977f3c474..cb6fbd3fbda 100644 --- a/src/mongo/scripting/engine.h +++ b/src/mongo/scripting/engine.h @@ -245,6 +245,9 @@ public: virtual int getJSHeapLimitMB() const = 0; virtual void setJSHeapLimitMB(int limit) = 0; + virtual std::string getLoadPath() const = 0; + virtual void setLoadPath(const std::string& loadPath) = 0; + /** * Calls the constructor for the Global ScriptEngine. 'disableLoadStored' causes future calls to * the function Scope::loadStored(), which would otherwise load stored procedures, to be diff --git a/src/mongo/scripting/mozjs/engine.cpp b/src/mongo/scripting/mozjs/engine.cpp index 8a6f75d9933..db5372f4931 100644 --- a/src/mongo/scripting/mozjs/engine.cpp +++ b/src/mongo/scripting/mozjs/engine.cpp @@ -66,7 +66,8 @@ std::string ScriptEngine::getInterpreterVersionString() { namespace mozjs { -MozJSScriptEngine::MozJSScriptEngine(bool disableLoadStored) : ScriptEngine(disableLoadStored) { +MozJSScriptEngine::MozJSScriptEngine(bool disableLoadStored) + : ScriptEngine(disableLoadStored), _loadPath(boost::filesystem::current_path().string()) { uassert(ErrorCodes::JSInterpreterFailure, "Failed to JS_Init()", JS_Init()); js::DisableExtraThreads(); } @@ -150,6 +151,14 @@ void MozJSScriptEngine::setJSHeapLimitMB(int limit) { gJSHeapLimitMB.store(limit); } +std::string MozJSScriptEngine::getLoadPath() const { + return _loadPath; +} + +void MozJSScriptEngine::setLoadPath(const std::string& loadPath) { + _loadPath = loadPath; +} + void MozJSScriptEngine::registerOperation(OperationContext* opCtx, MozJSImplScope* scope) { stdx::lock_guard<Latch> giLock(_globalInterruptLock); diff --git a/src/mongo/scripting/mozjs/engine.h b/src/mongo/scripting/mozjs/engine.h index 577248dd6d6..b1952f4e4ab 100644 --- a/src/mongo/scripting/mozjs/engine.h +++ b/src/mongo/scripting/mozjs/engine.h @@ -73,6 +73,9 @@ public: int getJSHeapLimitMB() const override; void setJSHeapLimitMB(int limit) override; + std::string getLoadPath() const override; + void setLoadPath(const std::string& loadPath) override; + void registerOperation(OperationContext* ctx, MozJSImplScope* scope); void unregisterOperation(unsigned int opId); @@ -96,6 +99,7 @@ private: // _globalInterruptLock). DeadlineMonitor<MozJSImplScope> _deadlineMonitor; + std::string _loadPath; }; } // namespace mozjs diff --git a/src/mongo/scripting/mozjs/implscope.cpp b/src/mongo/scripting/mozjs/implscope.cpp index 2df867c43c7..fa5d95540aa 100644 --- a/src/mongo/scripting/mozjs/implscope.cpp +++ b/src/mongo/scripting/mozjs/implscope.cpp @@ -477,19 +477,18 @@ MozJSImplScope::MozJSImplScope(MozJSScriptEngine* engine, boost::optional<int> j _statusProto(_context), _timestampProto(_context), _uriProto(_context) { - _environmentPreparer = std::make_unique<EnvironmentPreparer>(_context); - _moduleLoader = std::make_unique<ModuleLoader>(); - uassert(ErrorCodes::JSInterpreterFailure, "Failed to create ModuleLoader", _moduleLoader); - uassert(ErrorCodes::JSInterpreterFailure, - "Failed to initialize ModuleLoader", - _moduleLoader->init(_context, boost::filesystem::current_path())); - { JS_AddInterruptCallback(_context, _interruptCallback); JS_SetGCCallback(_context, _gcCallback, this); JS_SetContextPrivate(_context, this); JSAutoRealm ac(_context, _global); + _environmentPreparer = std::make_unique<EnvironmentPreparer>(_context); + _moduleLoader = std::make_unique<ModuleLoader>(); + uassert(ErrorCodes::JSInterpreterFailure, "Failed to create ModuleLoader", _moduleLoader); + uassert(ErrorCodes::JSInterpreterFailure, + "Failed to initialize ModuleLoader", + _moduleLoader->init(_context, engine->getLoadPath())); _checkErrorState(JS::InitRealmStandardClasses(_context)); diff --git a/src/mongo/scripting/mozjs/module_loader.cpp b/src/mongo/scripting/mozjs/module_loader.cpp index 8d476e36264..836a1a27892 100644 --- a/src/mongo/scripting/mozjs/module_loader.cpp +++ b/src/mongo/scripting/mozjs/module_loader.cpp @@ -29,20 +29,24 @@ #include <boost/filesystem.hpp> +#include "mongo/logv2/log.h" #include "mongo/scripting/mozjs/implscope.h" #include "mongo/scripting/mozjs/module_loader.h" #include "mongo/util/file.h" +#include <js/JSON.h> #include <js/Modules.h> #include <js/SourceText.h> #include <js/StableStringChars.h> +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault + namespace mongo { namespace mozjs { -bool ModuleLoader::init(JSContext* cx, const boost::filesystem::path& loadPath) { - invariant(loadPath.is_absolute()); - _loadPath = loadPath.string(); +bool ModuleLoader::init(JSContext* cx, const std::string& loadPath) { + _baseUrl = resolveBaseUrl(cx, loadPath); + LOGV2_DEBUG(716281, 2, "Resolved module base url.", "baseUrl"_attr = _baseUrl.c_str()); JSRuntime* rt = JS_GetRuntime(cx); JS::SetModuleResolveHook(rt, ModuleLoader::moduleResolveHook); @@ -63,14 +67,14 @@ JSObject* ModuleLoader::loadRootModuleFromSource(JSContext* cx, JSObject* ModuleLoader::loadRootModule(JSContext* cx, const std::string& path, boost::optional<StringData> source) { - JS::RootedString loadPath(cx, JS_NewStringCopyN(cx, _loadPath.c_str(), _loadPath.size())); + JS::RootedString baseUrl(cx, JS_NewStringCopyN(cx, _baseUrl.c_str(), _baseUrl.size())); JS::RootedObject info(cx, [&]() { if (source) { JS::RootedString src(cx, JS_NewStringCopyN(cx, source->rawData(), source->size())); - return createScriptPrivateInfo(cx, loadPath, src); + return createScriptPrivateInfo(cx, baseUrl, src); } - return createScriptPrivateInfo(cx, loadPath, nullptr); + return createScriptPrivateInfo(cx, baseUrl, nullptr); }()); if (!info) { @@ -121,8 +125,8 @@ bool ModuleLoader::importModuleDynamically(JSContext* cx, JS::HandleValue referencingPrivate, JS::HandleObject moduleRequest, JS::HandleObject promise) { - JS::RootedString loadPath(cx, JS_NewStringCopyN(cx, _loadPath.c_str(), _loadPath.size())); - JS::RootedObject info(cx, createScriptPrivateInfo(cx, loadPath, nullptr)); + JS::RootedString baseUrl(cx, JS_NewStringCopyN(cx, _baseUrl.c_str(), _baseUrl.size())); + JS::RootedObject info(cx, createScriptPrivateInfo(cx, baseUrl, nullptr)); JS::RootedValue newReferencingPrivate(cx, JS::ObjectValue(*info)); // The dynamic `import` method returns a Promise, and thus allows us to perform module loading @@ -491,5 +495,80 @@ JSObject* ModuleLoader::createScriptPrivateInfo(JSContext* cx, return info; } +/* +The rules for baseUrl resolution are as follows: + 1. At process start determine a "loadPath" which is either the parent directory of the first + file passed in to execute, or the current working directory. + 2. Search the loadPath for a file called "jsconfig.json", and attempt to read it as a JSON file. + 3. If found, try to find a property "compilerOptions.baseUrl" in that file and resolve that URL + relative to the location of "jsconfig.json". + 4. If not found, loadPath is now the parent directory of loadPath, repeat steps #2-3 until + either the baseUrl is resolved or we reach the root directory. + 5. If no baseUrl is resolved, return the current working directory +*/ +static const char* const kJSConfigJsonFileName = "jsconfig.json"; +static const char* const kCompileOptionsPropertyName = "compilerOptions"; +static const char* const kBaseUrlPropertyName = "baseUrl"; +std::string ModuleLoader::resolveBaseUrl(JSContext* cx, const std::string& loadPath) { + auto path = boost::filesystem::path(loadPath); + while (true) { + const boost::filesystem::directory_iterator end; + const auto it = std::find_if(boost::filesystem::directory_iterator(path), + end, + [&](const boost::filesystem::directory_entry& e) { + return e.path().filename() == kJSConfigJsonFileName; + }); + if (it != end) { + auto jsConfigPath = it->path().string(); + JS::RootedString jsConfigPathString(cx, JS_NewStringCopyZ(cx, jsConfigPath.c_str())); + JS::RootedString jsConfigSource(cx, fileAsString(cx, jsConfigPathString)); + if (!jsConfigSource) { + break; + } + + JS::RootedValue jsConfig(cx); + if (!JS_ParseJSON(cx, jsConfigSource, &jsConfig)) { + LOGV2_ERROR(716282, "Unable to parse JSON.", "jsonConfigPath"_attr = jsConfigPath); + break; + } + + JS::RootedObject jsConfigObject(cx, &jsConfig.toObject()); + JS::RootedValue compilerOptionsValue(cx); + if (!JS_GetProperty( + cx, jsConfigObject, kCompileOptionsPropertyName, &compilerOptionsValue)) { + break; + } + + JS::RootedObject compilerOptionsObject(cx, &compilerOptionsValue.toObject()); + JS::RootedValue baseUrlValue(cx); + if (!JS_GetProperty(cx, compilerOptionsObject, kBaseUrlPropertyName, &baseUrlValue)) { + break; + } + + JS::RootedString baseUrlString(cx, baseUrlValue.toString()); + auto baseUrl = std::string{JS_EncodeStringToUTF8(cx, baseUrlString).get()}; + + boost::system::error_code ec; + auto resolvedPath = + boost::filesystem::canonical(it->path().parent_path() / baseUrl, ec); + if (ec) { + LOGV2_ERROR( + 716283, "Unable to resolve parsed baseUrl.", "error"_attr = ec.to_string()); + break; + } + + return resolvedPath.string(); + } + + if (!path.has_parent_path()) { + break; + } + + path = path.parent_path(); + } + + return boost::filesystem::current_path().string(); +} + } // namespace mozjs } // namespace mongo diff --git a/src/mongo/scripting/mozjs/module_loader.h b/src/mongo/scripting/mozjs/module_loader.h index 1a95c9cebd0..aa324ac9479 100644 --- a/src/mongo/scripting/mozjs/module_loader.h +++ b/src/mongo/scripting/mozjs/module_loader.h @@ -41,11 +41,13 @@ namespace mozjs { class ModuleLoader { public: - bool init(JSContext* ctx, const boost::filesystem::path& loadPath); + bool init(JSContext* ctx, const std::string& loadPath); JSObject* loadRootModuleFromPath(JSContext* cx, const std::string& path); JSObject* loadRootModuleFromSource(JSContext* cx, const std::string& path, StringData source); private: + static std::string resolveBaseUrl(JSContext* cx, const std::string& loadPath); + static JSString* fileAsString(JSContext* cx, JS::HandleString pathnameStr); static JSObject* moduleResolveHook(JSContext* cx, JS::HandleValue referencingPrivate, JS::HandleObject moduleRequest); @@ -80,12 +82,11 @@ private: JS::HandleValue privateValue, JS::MutableHandleString pathOut); - JSString* fileAsString(JSContext* cx, JS::HandleString pathnameStr); JSObject* createScriptPrivateInfo(JSContext* cx, JS::Handle<JSString*> path, JS::Handle<JSString*> source); - std::string _loadPath; + std::string _baseUrl; }; } // namespace mozjs diff --git a/src/mongo/shell/mongo_main.cpp b/src/mongo/shell/mongo_main.cpp index 1b9ec083b5e..585e1adce17 100644 --- a/src/mongo/shell/mongo_main.cpp +++ b/src/mongo/shell/mongo_main.cpp @@ -844,6 +844,15 @@ int mongo_main(int argc, char* argv[]) { mongo::getGlobalScriptEngine()->enableJavaScriptProtection( shellGlobalParams.javascriptProtection); + if (shellGlobalParams.files.size() > 0) { + boost::system::error_code ec; + auto loadPath = + boost::filesystem::canonical(shellGlobalParams.files[0], ec).parent_path().string(); + if (!ec) { + mongo::getGlobalScriptEngine()->setLoadPath(loadPath); + } + } + ScopeGuard poolGuard([] { ScriptEngine::dropScopeCache(); }); std::unique_ptr<mongo::Scope> scope(mongo::getGlobalScriptEngine()->newScope()); |