Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: prevent mangling errno codes for nodefs #14722

Merged
merged 9 commits into from
Jul 26, 2021

Conversation

cyyynthia
Copy link
Contributor

As described in #14705, errno codes may be looked up dynamically when using nodefs, but closure was mangling the properties making some fs operations behave unexpectedly resulting in undefined behavior.

This PR adds ERRNO_CODES and its properties as node externs, to solve this issue and only affect code size when property names may be needed (witnin a web browser, we will never need plain code names). Also includes a test case to prevent future regressions.

Closes #14705

@welcome
Copy link

welcome bot commented Jul 22, 2021

Thank you for submitting a pull request! If this is your first PR, make sure to add yourself to AUTHORS.

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the issue exactly. Can you point me to where in the nodefs code things go wrong without this change?

@@ -5202,6 +5202,10 @@ def test_fs_nodefs_home(self):
self.emcc_args += ['-lnodefs.js']
self.do_runf(test_file('fs/test_nodefs_home.c'), 'success', js_engines=[config.NODE_JS])

def test_fs_nodefs_stat(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this test is specifically related to closure perhaps it should be named as such? Maybe test_fs_nodefs_errno_closure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a better name for it; I've made the change in abd8d45

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would updating ERRNO_CODES in src/library.js to be defined with strings for keys fix the issue also (or have the same effect)?

I just gave it a try, the test fails and the keys are transformed by Closure

I think its our library system the removes the quotes here, rather than closure. Sadly I can't think of a better way to achieve this, but I worry that this will increase code side a fair bit for programs that need ERRNO_CODE.

I wonder if we can convert a lot of the uses of ERRNO_CODES to use the {{{ cDefine('ENOTTY') }}} pattern and therefore avoid including ERRNO_CODES unless really needed?

Copy link
Contributor Author

@cyyynthia cyyynthia Jul 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the problem is that the code is dynamically returned by Node itself as a property of the error, not minifying the codes is the only way I can think of to allow that dynamic lookup :/

For the code size, I'm unsure how big the real world impact could be, but I doubt the impact would be anything more than a couple bytes, considering I've seen during my Closure-ified code exploration that all references to the ERRNO_CODES enum were inlined; if it wasn't for that dynamic lookup imposed by nodefs.js the object would already get stipped away.

@sbc100
Copy link
Collaborator

sbc100 commented Jul 22, 2021

I'm not sure I understand the issue exactly. Can you point me to where in the nodefs code things go wrong without this change?

Would updating ERRNO_CODES in src/library.js to be defined with strings for keys fix the issue also (or have the same effect)?

@cyyynthia
Copy link
Contributor Author

cyyynthia commented Jul 22, 2021

I'm not sure I understand the issue exactly. Can you point me to where in the nodefs code things go wrong without this change?

When doing some operations (like stat on a non existing file), node throws an error with the errno attached to it as a string. This string is used to lookup the error code in the map (in the convertNodeError method), which fails when the map has been reprocessed by Closure

@cyyynthia
Copy link
Contributor Author

Would updating ERRNO_CODES in src/library.js to be defined with strings for keys fix the issue also (or have the same effect)?

I just gave it a try, the test fails and the keys are transformed by Closure

@kripken
Copy link
Member

kripken commented Jul 22, 2021

Good find, yes, dynamic name lookups on ERRNO_CODES may break with closure.

After #14730 we will at least only use ERRNO_CODES where we need an actual dynamic lookup. And then I think the proper solution is to quote that object's keys, that is,

ERRNO_CODES = {
  "EAGAIN": ..

Doing it that way instead of as a closure extern is slightly simpler, and better as it will still be able to minify the name of ERRNO_CODES itself.

@kripken
Copy link
Member

kripken commented Jul 22, 2021

Oh, sorry, I just saw the earlier comments on quoting now.

We should probably fix the quoting in our JS library. But this should also work, to move the object values into a string:

diff --git a/src/library.js b/src/library.js
index dc9566970..36fb829b5 100644
--- a/src/library.js
+++ b/src/library.js
@@ -1550,8 +1550,8 @@ LibraryManager.library = {
   // errno.h
   // ==========================================================================
 
-  $ERRNO_CODES: {
-    EPERM: {{{ cDefine('EPERM') }}},
+  $ERRNO_CODES__postset: `ERRNO_CODES = {
+    "EPERM": {{{ cDefine('EPERM') }}},
     ENOENT: {{{ cDefine('ENOENT') }}},
     ESRCH: {{{ cDefine('ESRCH') }}},
     EINTR: {{{ cDefine('EINTR') }}},
@@ -1672,7 +1672,8 @@ LibraryManager.library = {
     ENOTRECOVERABLE: {{{ cDefine('ENOTRECOVERABLE') }}},
     EOWNERDEAD: {{{ cDefine('EOWNERDEAD') }}},
     ESTRPIPE: {{{ cDefine('ESTRPIPE') }}},
-  },
+  };`,
+  $ERRNO_CODES: {},
   $ERRNO_MESSAGES: {
     0: 'Success',
     {{{ cDefine('EPERM') }}}: 'Not super-user',

@@ -5202,6 +5202,10 @@ def test_fs_nodefs_home(self):
self.emcc_args += ['-lnodefs.js']
self.do_runf(test_file('fs/test_nodefs_home.c'), 'success', js_engines=[config.NODE_JS])

def test_fs_nodefs_errno_closure(self):
self.emcc_args += ['-lnodefs.js', '--closure=1']
self.do_runf(test_file('fs/test_nodefs_errno_closure.c'), 'success', js_engines=[config.NODE_JS])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can get test coverage for this simply by adding self.maybe_closure() to some/all of the exists nodefs tests? (i.e. avoid adding a new test).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, even we do add this new test that way to get closure coverage in the core tests in this file is to add self.maybe_closure() rather than '--closure=1' directly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it looks like for example you can add self.maybe_closure() to the existing test_fs_nodefs_home test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure if test_fs_nodefs_home or any existing test would catch this specific case, as this issue happens only when an error is thrown by Node and it seems all these tests expect no error to happen (and therefore we never need to do that problematic dynamic lookup).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. if none of the existing tests are testing error conditions that sounds pretty bad.. maybe find the existing test that uses exercises stat and add some error condition testing to it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those existing tests do look quite spotty!

How about you just add a new assert to ./tests/fs/test_nodefs_rw.c ? Just try to open a non-existent file and check the error code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've replaced my test case by 2 additional asserts in 99b9306, seems to cover the case nicely

@sbc100
Copy link
Collaborator

sbc100 commented Jul 23, 2021

Thanks!

@kripken
Copy link
Member

kripken commented Jul 26, 2021

The SIMD errors have been fixed on main meanwhile, and merging in latest should fix them. I think the same is true for the other errors as well, as the new quotes should only be used in NODEFS, NODERAWFS, and PROXYFS.

@kripken
Copy link
Member

kripken commented Jul 26, 2021

Perfect, thanks @cyyynthia !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Closure mangles ERRNO code names, but they are looked up dynamically by nodefs
3 participants