What does --dll expect LIBRARY to look like?

What does --dll expect LIBRARY to look like?

Can we import a C++ library into the shell?

--dll is a hack with a very specific purpose: to get stack traces out of JS shell runs in continuous integration. The one and only thing it actually does is call dlopen()/ LoadLibraryExW() to import a .so or .dylib or .dll. There is no facility for then calling dlsym or an equivalent, which makes it pretty useless for anything custom.

The JS shell makes use of this by defining a couple of functions with weak linkage. It tests those against nullptr to decide whether to call them. This works because dlopen will update those symbols’ values to a pointer to the function within the loaded library. (Actually, it does this with both functions and data, to handle an edge case where we want to declare that a crash is expected and we do not want a stack trace from it.)

The --dll flag will probably be removed soon(ish), because it relies on PR_LoadLibrary (well, PR_LoadLibraryWithFlags) to do its thing, and that is implemented in NSPR. We barely rely on NSPR now, and normally do local testing without it, and we’d like to entirely remove it as a dependency.

In the meantime, you could probably use --dll to do some tricky and horrible stuff by generating a library with initialization code that sets things up. The linking would probably get messy, but if you had a library that had undefined symbols coming from mozjs, I think you could use JS_DefineFunction to install JS functions on the global that invoked functionality from the loaded library (or a library that it was linked to, if you wanted to keep your bootstrapping code separate).

I don’t recommend it, and it may stop working before too long, but I think it’s at least theoretically doable. Though I don’t think it would have any advantages, and several disadvantages, to using ctypes instead. (Note that ctypes depends on NSPR as well, so --dll and ctypes will most likely be going away at the same time.)

In QuickJS we can do this https://github.com/guest271314/webserver-c/tree/quickjs-webserver in C

$ clang -Wall -L./quickjs -fvisibility=hidden -shared \
  -I ./quickjs -g -ggdb -O webserver.c -o webserver.so
#include "quickjs.h"
#include "cutils.h"
// ...
void status(JSContext* ctx, JSValue this_val, char* str) {
  JSValue callback, params[1];
  params[0] = JS_NewString(ctx, str);
  callback = JS_Call(ctx, this_val, JS_UNDEFINED, 1, params);
  JS_FreeValue(ctx, callback);
  JS_FreeValue(ctx, params[0]);
}

static JSValue module_webserver(JSContext* ctx,
                                JSValueConst this_val,
                                int argc,
                                JSValueConst argv[]) {
  // Check for correct callback function
  if(!JS_IsFunction(ctx, argv[1])) {
    return JS_ThrowTypeError(ctx, "argument 2 must be a function");
  }
// ...
// Shared-object entry point. when loaded by QuickJS will get executed
// (make the function the only exported name of the shared-object)
__attribute__((visibility("default"))) JSModuleDef* js_init_module(
    JSContext* ctx,
    const char* module_name) {
  JSModuleDef* m;
  // Creates a new module, named according to the 'from' string given in the
  // import directive and with the initialization function above.
  m = JS_NewCModule(ctx, module_name, module_init);
  if (!m)
    return NULL;
  // Adds the exports
  JS_AddModuleExportList(ctx, m, module_funcs, countof(module_funcs));
  return m;
}

Then do this in the runtime

#!/usr/bin/env -S ./qjs -m --std
// webserver.js
import {webserver} from './webserver.so';

So I was thinking I could import this https://github.com/guest271314/NativeMessagingHosts/blob/main/nm_cpp.cpp C++ into js to handle reading standrd input

// C++ Native Messaging host
// https://browserext.github.io/native-messaging/
// https://developer.chrome.com/docs/apps/nativeMessaging/
// https://www.reddit.com/user/Eternal_Weeb/
// guest271314, 2022
#include <iostream>
#include <vector>
using namespace std;

vector<uint8_t> getMessage() {
  uint32_t length = 0;
  size_t size = fread(&length, sizeof(length), 1, stdin);
  vector<uint8_t> message(length); 
  size = fread(message.data(), sizeof(*message.data()), message.size(), stdin);
  return message;
}

I do have a working js version (and a working d8 version). It’s just that js and V8’s d8 https://github.com/guest271314/native-messaging-d8 are horrible at reading standard input with readline() because there’s no consistency or uniformity or compatibility in JavaScript for reading STDIN or writing to STDOUT - it’s unspecified.

This is what I’m doing right now with js https://github.com/guest271314/native-messaging-spidermonkey-shell/blob/main/nm_spidermonkey.js

#!/usr/bin/env -S JS_STDERR=err.txt /home/user/.jsvu/engines/spidermonkey/spidermonkey
// /home/user/bin/jsshell-linux-x86_64/js
// SpiderMonkey Shell Native Messaging host
// guest271314 7-7-2023, 6-16-2024

function encodeMessage(str) {
  return new Uint8Array([...str].map((s) => s.codePointAt()));
}

function getMessage() {
  // Call readline() N times to catch `\r\n\r\n"` from 2d port.postMessage()
  let stdin;
  while (true) {
    stdin = readline();
    if (stdin !== null) {
      break;
    }
  }
  
  let data = `${stdin}`.replace(/[\r\n]+|\\x([0-9A-Fa-f]{2,4})/gu, "")
    .replace(/[^A-Za-z0-9\s\[,\]\{\}:_"]+/igu, "")
    .replace(/^"rnrn/gu, "")
    .replace(/^[#\r\n\}_]+(?=\[)/gu, "")
    .replace(/^"(?=["\{]+)|^"(?!"$)/gu, "") 
    .replace(/^\[(?=\[(?!.*\]{2}$))/gu, "")
    .replace(/^\{(?!\}|.+\}$)/gu, "")
    .replace(/^[0-9A-Z]+(?=[\[\{"])/igu, "") 
    .replace(/^[\]\}](?=\[)/i, "")
    .trimStart().trim();
  // https://stackoverflow.com/a/52434176
  // let previous = redirect("length.txt");
  // putstr(data.length);
  // redirect(previous); // restore the redirection to stdout
  // os.file.writeTypedArrayToFile("input.txt", encodeMessage(data));
  return encodeMessage(data);
}

function sendMessage(message) {
  os.file.writeTypedArrayToFile(
    "/proc/self/fd/1",
    new Uint32Array([message.length]),
  );
  os.file.writeTypedArrayToFile("/proc/self/fd/1", message);
}

function main() {
  // Send help() to client
  // const previous = redirect("help.txt");
  // putstr(help());
  // redirect(previous); // restore the redirection to stdout
  // const h = read("help.txt", "binary");
  // sendMessage(encodeMessage(JSON.stringify([...h])));
  while (true) {
    // Terminate current process when chrome-extension://<ID> is not a running process
    // \x00 or \x01, 512 or 513
    if (os.system("pgrep", ["-f", scriptArgs[0]]) == 513) {
      break;
    }
    const message = getMessage();
    sendMessage(message);
  }
}

try {
  main();
} catch (e) {
  os.file.writeTypedArrayToFile(
    "caught.txt",
    encodeMessage(JSON.stringify(e.message)),
  );
  quit();
}