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

fs: throw errors on invalid paths synchronously #18308

Closed
wants to merge 2 commits into from

Conversation

joyeecheung
Copy link
Member

@joyeecheung joyeecheung commented Jan 23, 2018

Since type check errors on paths are throw synchronously, it makes more sense to throw other types of checks on the paths synchronously as well.

fs: throw errors on invalid paths synchronously

  • Throw getPathFromURL() and nullCheck() errors synchronously instead
    of deferring them to the next tick, since we already throw
    validatePath() errors synchronously.
  • Merge nullCheck() into validatePath()
  • Never throws in fs.exists(), instead, invoke the callback with
    false, or emit a warning when the callback is not a function.
    This is to bring it inline with fs.existsSync(), which never throws.
  • Updates the comment of rethrow()
  • Throw ERR_INVALID_ARG_VALUE for null checks
Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

fs, whatwg-url

@nodejs-github-bot nodejs-github-bot added dont-land-on-v4.x fs Issues and PRs related to the fs subsystem / file system. whatwg-url Issues and PRs related to the WHATWG URL implementation. labels Jan 23, 2018
@joyeecheung
Copy link
Member Author

@joyeecheung joyeecheung added semver-major PRs that contain breaking changes and should be released in the next major version. and removed dont-land-on-v4.x labels Jan 23, 2018
@@ -101,26 +101,6 @@ common.expectsError(() => {
);
});

// Throws if the source path is an invalid path.
Copy link
Member Author

Choose a reason for hiding this comment

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

This is removed because there are checks in test-fs-null-bytes.js

lib/fs.js Outdated
@@ -204,6 +204,37 @@ function validateOffsetLengthWrite(offset, length, byteLength) {
}
}

// Returns silently if path contains null bytes.
Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, should've been Check if the path contains null types if it is a string nor Uint8Array, otherwise return silently.

@gireeshpunathil
Copy link
Member

@joyeecheung - Will this cause a previously working code that received a propagated error message in the API's callback to now synchronously throw an error? I guess the decision to throw inline or async or error-flled callback may be taken based the API interface itself, rather than the type of the error we are dealing with?

@joyeecheung
Copy link
Member Author

joyeecheung commented Jan 23, 2018

@gireeshpunathil But we do throw type-checking errors and range errors synchronously in almost all of our APIs at the moment, even in the async APIs, so the users should always be prepared for both types of errors even without this change. Aside from fs, it's also common in APIs like http to throw validation errors like this without passing them to callbacks - in fact fs looks like an exception in this regard.

The URL type errors and null check errors IMO are not that far from type errors and range errors, it's rather surprising that some of them are thrown asynchronously while some of them are not. Deferring them to the next tick does not seem to yield any benefit so if we are unifying the behavior, we might as well throw them synchronously.

@gireeshpunathil
Copy link
Member

thanks @joyeecheung . Neither I have a full view of the existing behavior, documented evidence to support them, nor any specific concerns on this PR. But In general, I think it is a good idea for an async API to call back under all possible circumstances, except from native assertions and machine check exceptions - will you please spawn a separate thread to gather views and reach consensus?

@joyeecheung
Copy link
Member Author

cc @nodejs/tsc since it's semver-major

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

I agree with this change.

LGTM with the FIXME addressed.

lib/fs.js Outdated
const err = new errors.Error('ERR_INVALID_ARG_TYPE',
propName,
'string without null bytes',
path);
Copy link
Member

Choose a reason for hiding this comment

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

I do not like leaving FIXME in the code, can you get this sorted?

@joyeecheung
Copy link
Member Author

@mcollina Was going to fix that later, but I am fine with fixing it now as well. Updated, PTAL.

@mcollina
Copy link
Member

LGTM

@jasnell jasnell added this to the 10.0.0 milestone Jan 24, 2018
@joyeecheung
Copy link
Member Author

@joyeecheung
Copy link
Member Author

joyeecheung commented Jan 24, 2018

cc @nodejs/tsc this needs at least one more approval to land, although we might want to discuss about it in a meeting.

cons:

  1. more errors get thrown synchronously out of asynchronous APIs

pros:

  1. This is already the case for the most ERR_INVALID_ARG_TYPE and ERR_OUT_OF_RANGE errors in fs as well as many validations in other modules. This patch just adds ERR_INVALID_FILE_URL_PATH, ERR_INVALID_URL_SCHEME , and a ERR_INVALID_ARG_VALUE to the list.
  2. AFAICT throwing this type of errors is more prevalent than passing them down asynchronously (searching for ERR_INVALID_* and ERR_OUT_OF_RANGE and the like in the codebase would show that)
  3. These errors are synchronous in nature, they usually happen before any asynchronous operations can even be started because the operations cannot really be initiated if the arguments are invalid.
  4. They are kind of like ReferenceError in callbacks, TypeError in null.something, and JSON parse errors, .etc which are all thrown synchronously by the JS engine.

Also see #18308 (comment) on the special case of fs.exists and fs.existsSync.

@joyeecheung
Copy link
Member Author

Trott
Trott previously approved these changes Jan 24, 2018
@Trott Trott dismissed their stale review January 24, 2018 20:45

second-guessing myself here...

@joyeecheung joyeecheung force-pushed the fs-sync-error-path branch 2 times, most recently from 2cf2fdb to 354c61d Compare January 24, 2018 22:43
@joyeecheung
Copy link
Member Author

Updated:

  1. Spin off the ERR_INVALID_ARG_VALUE changes in errors: improve the description of ERR_INVALID_ARG_VALUE #18358 , the first commit is taken from that.
  2. Updated the fs.exists and fs.existsSync behavior, because unlike other sync APIs, the users have already expected fs.existSync to NEVER throw, even when it's invoked like fs.existSync(), therefore we cannot throw in the async counterpart either.
  3. Updated the description of rethrow because we didn't throw the error in v7 (reverted), and we probably won't in the near future (fs: Revert throw on invalid callbacks #12976).

@joyeecheung
Copy link
Member Author

joyeecheung commented Jan 24, 2018

cc @jasnell @mcollina Please take another look since the behavior of fs.exists and fs.existsSync are changed significantly because of https://ci.nodejs.org/view/Node.js-citgm/job/citgm-smoker/1229/:

  1. fs.existsSync never throws at the moment. I updated it to stay that way.
  2. fs.exists currently throws on invalid paths, but any other errors in callbacks are coerced to false. This updates that to never throw etiher, instead it invokes the callback with false when there are invalid arguments, to be inline with fs.existsSync. Mind that the callback fs.exists does not even take an error parameter, it only takes a boolean. IMO if we want to throw from fs.exist and fs.existSync, we would need a proper deprecation cycle. I would want to defer that to future PRs.

}
return util_;
}

Copy link
Member

Choose a reason for hiding this comment

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

Using the other lazy require is faster. It is probably not much of a difference in the real world but I would still prefer to keep it as is.

E('ERR_INVALID_ARG_VALUE', (name, value) =>
`The value "${String(value)}" is invalid for argument "${name}"`);
E('ERR_INVALID_ARG_VALUE', (name, value, reason = 'is invalid') => {
const util = lazyUtil();
Copy link
Member

Choose a reason for hiding this comment

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

This is not necessary in case you assign util to a "global" (module) variable. In that case util will always be loaded before the error here can be called. That is another reason why I would prefer to keep the old lazy loading.

Copy link
Member Author

Choose a reason for hiding this comment

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

@BridgeAR This has already landed in master as a separate PR #18358, this PR just needs a rebase. I don't personally feel very strongly about this approach, just following the convention of lazyErrors() in other modules.

@joyeecheung
Copy link
Member Author

Rebased. CI: https://ci.nodejs.org/job/node-test-pull-request/12902/

@BridgeAR See #18308 (comment) about the lazyUtils change, can you open a separate PR if you want to restore the way we lazy load utils?

@joyeecheung
Copy link
Member Author

CI is unstable today...launched two builds and the combination look green...

https://ci.nodejs.org/job/node-test-pull-request/12903/ (ubuntu1604-arm64_odroid_c2 and osx1010 infra issue)
https://ci.nodejs.org/job/node-test-pull-request/12904/ (ubuntu1604-arm64_odroid_c2 and ppcbe-ubuntu1404 infra issue, two known flake test-tls-server-verify and test-child-process-exec-kill-throws on windows)

@joyeecheung
Copy link
Member Author

@joyeecheung
Copy link
Member Author

There don't seem to be new errors from the CITGM run. The errors that did not appear on master appear on other PRs' CITGM runs.

cc @mcollina @jasnell @gireeshpunathil @BridgeAR @Trott any more thoughts on this PR? The only significant change is that we don't throw in fs.existSync/fs.exists under any circumstances now for backwards compatibility. See #18308 (comment)

@joyeecheung
Copy link
Member Author

Also @mcollina @jasnell @gireeshpunathil To people who have already signed-off on this, does this still LGTY? I would like to land this soon so I can continue to work on improving the async errors' stack traces.

@mcollina
Copy link
Member

mcollina commented Feb 3, 2018

Still LGTM.

@gireeshpunathil
Copy link
Member

still LGTM

- Throw getPathFromURL() and nullCheck() errors synchronously instead
  of deferring them to the next tick, since we already throw
  validatePath() errors synchronously.
- Merge nullCheck() into validatePath()
- Never throws in `fs.exists()`, instead, invoke the callback with
  false, or emit a warning when the callback is not a function.
  This is to bring it inline with fs.existsSync(), which never throws.
- Updates the comment of rethrow()
- Throw ERR_INVALID_ARG_VALUE for null checks
@joyeecheung
Copy link
Member Author

joyeecheung commented Feb 7, 2018

@joyeecheung
Copy link
Member Author

CI is clean minus one infra issue. Landing..

@joyeecheung
Copy link
Member Author

Landed in d8f7338, thanks!

@joyeecheung joyeecheung closed this Feb 8, 2018
joyeecheung added a commit that referenced this pull request Feb 8, 2018
- Throw getPathFromURL() and nullCheck() errors synchronously instead
  of deferring them to the next tick, since we already throw
  validatePath() errors synchronously.
- Merge nullCheck() into validatePath()
- Never throws in `fs.exists()`, instead, invoke the callback with
  false, or emit a warning when the callback is not a function.
  This is to bring it inline with fs.existsSync(), which never throws.
- Updates the comment of rethrow()
- Throw ERR_INVALID_ARG_VALUE for null checks

PR-URL: #18308
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
MayaLekova pushed a commit to MayaLekova/node that referenced this pull request May 8, 2018
- Throw getPathFromURL() and nullCheck() errors synchronously instead
  of deferring them to the next tick, since we already throw
  validatePath() errors synchronously.
- Merge nullCheck() into validatePath()
- Never throws in `fs.exists()`, instead, invoke the callback with
  false, or emit a warning when the callback is not a function.
  This is to bring it inline with fs.existsSync(), which never throws.
- Updates the comment of rethrow()
- Throw ERR_INVALID_ARG_VALUE for null checks

PR-URL: nodejs#18308
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fs Issues and PRs related to the fs subsystem / file system. semver-major PRs that contain breaking changes and should be released in the next major version. whatwg-url Issues and PRs related to the WHATWG URL implementation.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants