summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Broadstone <mbroadst@mongodb.com>2022-12-20 18:47:07 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-12-20 19:39:17 +0000
commitd72723568e1689fc31617009daf72345f8c18666 (patch)
tree0d387c25a60405326c92d618bc4ba1a5e44ce49c
parentd2daeef1cef7ec97c17f265bf42762303adf5c4a (diff)
downloadmongo-d72723568e1689fc31617009daf72345f8c18666.tar.gz
SERVER-71628 Try to resolve baseUrl defined in nearby jsconfig.json
-rw-r--r--src/mongo/scripting/engine.h3
-rw-r--r--src/mongo/scripting/mozjs/engine.cpp11
-rw-r--r--src/mongo/scripting/mozjs/engine.h4
-rw-r--r--src/mongo/scripting/mozjs/implscope.cpp13
-rw-r--r--src/mongo/scripting/mozjs/module_loader.cpp95
-rw-r--r--src/mongo/scripting/mozjs/module_loader.h7
-rw-r--r--src/mongo/shell/mongo_main.cpp9
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());