Skip to content

std::random_device fails on windows with NODERAWFS #9628

Description

@mgreter

Given the following emscripten-bug.cpp

#include <random>
#include <iostream>

#ifdef __cplusplus
extern "C" {
#endif

void emscripten_test() {
  std::random_device rd;
  // Fails on windows if NODERAWFS is enabled
  std::cerr << "Random " << rd() << "\n";
  std::cerr << "Random " << rd() << "\n";
  std::cerr << "Random " << rd() << "\n";
}

#ifdef __cplusplus
}
#endif

Compiled via

em++ -Wall -O3 -pipe -DNDEBUG -fomit-frame-pointer -std=c++11 -c \
  -o build/emscripten-bug.o emscripten-bug.cpp
emcc build/emscripten-bug.o -o build/emscripten-bug.js \
	--js-opts 0 -O3 \
	-s WASM=0 \
	-s DETERMINISTIC=1 \
	-s EXPORTED_FUNCTIONS="['_emscripten_test']" \
	-s USE_CLOSURE_COMPILER=0 \
	-s ALLOW_MEMORY_GROWTH=1 \
	-s ENVIRONMENT=node \
	-s NODERAWFS=1 \
	--memory-init-file 0

And invoked by

const sass = require('./build/emscripten-bug');
sass._emscripten_test();

This fails to work on windows. Either removing the random_device or using NODERAWFS=0 fixes the problem, but I really want to have both :/

The reason why it fails is because NODERAWFS overwrites the virtual FS with the following code:

if (ENVIRONMENT_IS_NODE) {
  var _wrapNodeError = function(func) {
    return function() {
      try {
        return func.apply(this, arguments)
      } catch (e) {
        if (!e.code) throw e;
        throw new FS.ErrnoError(ERRNO_CODES[e.code]);
      }
    }
  };
  var VFS = Object.assign({}, FS);
  for (var _key in NODERAWFS) {
    FS[_key] = _wrapNodeError(NODERAWFS[_key]);
  }
} else {
  throw new Error("NODERAWFS is currently only supported on Node.js environment.");
}

The error happens when it tries to read /dev/urandom. Logically this file will not be available on windows and it seems that's why the original virtual FS defines those directly via:

        FS.createDevice('/dev', 'random', random_device);
        FS.createDevice('/dev', 'urandom', random_device);

So instead of using the "shimmed" device it tries to use the real ones.

The fix seems somewhat simple, but I'm not sure what the best way is.
I think it's reasonable to use the VFS for dev and proc even if NODERAWFS is enabled, as those are probably never really portable. Maybe another flag is needed (or use NODERAWFS=2) so users can decide whether they want to use "emulated" dev and proc instead of real ones.

Following is a quick hack/hotfix to get it working:

if (ENVIRONMENT_IS_NODE) {
  var VFS = Object.assign({}, FS);
  var _wrapNodeError = function(key, func) {
    return function() {
      if (arguments.length && arguments['0'].match && arguments['0'].match(/^\/+(?:dev|proc)\//)) {
        return VFS[key].apply(this, arguments);
      }
      try {
        return func.apply(this, arguments)
      } catch (e) {
        if (!e.code) throw e;
        throw new FS.ErrnoError(ERRNO_CODES[e.code]);
      }
    }
  };
  for (var _key in NODERAWFS) {
    FS[_key] = _wrapNodeError(_key, NODERAWFS[_key]);
  }
} else {
  throw new Error("NODERAWFS is currently only supported on Node.js environment.");
}

I might even be able to come up with a PR once it's decided how you want to move forward with this. Thank you and have a nice day!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions