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

src: refactor and harden ProcessEmitWarning() #17420

Closed
wants to merge 6 commits into from

Conversation

addaleax
Copy link
Member

@addaleax addaleax commented Dec 1, 2017

  • Handle exceptions when getting process.emitWarning or when
    calling it properly
  • Add Maybe<bool> to the return type, like the V8 API uses it
    to indicate failure conditions
  • Update call sites to account for that and clean up/return to JS
    when encountering an error
  • Add an internal ProcessEmitDeprecationWarning() sibling
    for use in domain: runtime deprecate MakeCallback #17417,
    with common code extracted to a helper function
  • Allow the warning to contain non-Latin-1 characters. Since the
    message will usually be a template string containing data passed
    from the user, this is the right thing to do.
  • Add tests for the failure modes (except string creation failures)
    and UTF-8 warning messages.

Refs: #17417

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)

src

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. labels Dec 1, 2017

// MakeCallback() unneeded because emitWarning is internal code, it calls
// process.emit('warning', ...), but does so on the nextTick.
if (emit_warning.As<Function>()->Call(env->context(),
Copy link
Member

@AndreasMadsen AndreasMadsen Dec 1, 2017

Choose a reason for hiding this comment

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

If process.emitWarning is supposed to be overwritable, as your test says. Then we should use MakeCallback, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Mh … I am not sure. There is usually always a JS call stack below, right?

Copy link
Member

Choose a reason for hiding this comment

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

Well, that won't be true for the domain MakeCallback deprecation. Anything could call MakeCallback in that case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm. True. But that would put us in a pretty difficult position if process.domain happens to be set in that case (by accident), since process would be the resource object?

So, I am okay with changing this but I’m not sure where we’d get the async context to use for MakeCallback?

Copy link
Member

Choose a reason for hiding this comment

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

So, I am okay with changing this but I’m not sure where we’d get the async context to use for MakeCallback?

Yes. This is a general problem. We have process.on('beforeExit'), process.on('exit') without context as well. In this case I think there are two options:

  • Have a general async_context for the process, and use that.
  • Create a new async_context with async_context_.async_id as the triggerAsyncId.

The latter option is better for this case but I don't see it working in general for all ProcessEmitWarningGeneric use cases.

src/node.cc Outdated
if (type != nullptr) {
if (!String::NewFromOneByte(env->isolate(),
reinterpret_cast<const uint8_t*>(type),
v8::NewStringType::kInternalized)
Copy link
Member

Choose a reason for hiding this comment

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

Question: what is an internalized string. Looking it up, I get something about string interning but that doesn't make much sense in this context.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is basically what string interning refers to. As far as I know, what it means is that if V8 already has a copy of that same string, it deduplicates them and returns the existing one to the caller; And if not, the string is created in the “old space” part of the heap, that is, not expected to be short-lived and garbage-collected soon.

As a rule of thumb, this makes sense for things like identifier strings, I think.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, that was my understanding of interning too :) It makes sense for type then, but not code as we are unlikely to emit the same deprecation warning twice.

Copy link
Member Author

Choose a reason for hiding this comment

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

Still – if there is e.g. code that tests the warning code, then that code will contain a copy of that string, right? Also, this is part of the generic helper, and I would like to add codes to the existing native warnings in a follow-up (because I think that might be semver-major)

Copy link
Member

@AndreasMadsen AndreasMadsen Dec 2, 2017

Choose a reason for hiding this comment

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

Still – if there is e.g. code that tests the warning code, then that code will contain a copy of that string, right?

Yes.

Also, this is part of the generic helper, and I would like to add codes to the existing native warnings in a follow-up.

But those will be different codes. Even if it is the same deprecation code from multiple places, it should only be emitted once, thus the V8 string will only be constructed once.


If I understand interning correctly. The benefit isn't so much string copying, but rather string comparison. Instead of a copy, we end up doing some hash table lookup. Which is at least as costly, most likely more costly.

I would even go as far and say that interning the type only makes sense if the string object is reused. Such that it doesn't have to be reconstructed so often. In this case, it isn't reused, but we should properly do that.

Just to clarify: I really don't care about this, I'm just trying to learn :D

Copy link
Member Author

Choose a reason for hiding this comment

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

Even if it is the same deprecation code from multiple places, it should only be emitted once, thus the V8 string will only be constructed once.

Sorry, I was talking about the existing non-deprecation warnings (e.g. the crypto warning that is used in the test here :)

The benefit isn't so much string copying, but rather string comparison.

Yes, I would say so :) That’s why I usually go with using this for identifiers as a rule of thumb

src/node.cc Outdated

if (!emit_warning->IsFunction()) return Just(false);

int i = 0;
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: call this argc.

"load failed: %s\n",
extra_root_certs_file.c_str(),
ERR_error_string(err, nullptr)).IsNothing()) {
return;
Copy link
Member

Choose a reason for hiding this comment

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

This return screws up root_cert_store's reference counting

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm – how would this fail? Returning here leaves root_cert_store initialized but unused, but the code doesn’t look to me like that’s harmful?

Copy link
Member

Choose a reason for hiding this comment

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

SecureContext::ctx_ won't have the root certificate store associated with it if you return here. That's a change from before.

cipher_type);
if (ProcessEmitWarning(env(), "Use Cipheriv for counter mode of %s",
cipher_type).IsNothing())
return;
Copy link
Member

Choose a reason for hiding this comment

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

Likewise, this leaves the cipher object in a half-initialized state.

v8::Maybe<bool> ProcessEmitWarning(Environment* env, const char* fmt, ...);
v8::Maybe<bool> ProcessEmitDeprecationWarning(Environment* env,
const char* warning,
const char* deprecation_code);
Copy link
Member

Choose a reason for hiding this comment

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

Alignment.

@@ -18,7 +18,7 @@ if (process.env.CHILD) {

const env = Object.assign({}, process.env, {
CHILD: 'yes',
NODE_EXTRA_CA_CERTS: `${fixtures.fixturesDir}/no-such-file-exists`,
NODE_EXTRA_CA_CERTS: `${fixtures.fixturesDir}/no-such-file-exists-🐢`,
Copy link
Member

Choose a reason for hiding this comment

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

Haha :-)

src/node.cc Outdated
if (type != nullptr) {
if (!String::NewFromOneByte(env->isolate(),
reinterpret_cast<const uint8_t*>(type),
v8::NewStringType::kInternalized)
Copy link
Member

Choose a reason for hiding this comment

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

I don't know if it makes sense to intern the strings - they stay around forever but they're probably used only once. (At least, how often is the same warning emitted? Not too often, I'd guess.)

Copy link
Member Author

Choose a reason for hiding this comment

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

You and @AndreasMadsen convinced me :)

Copy link
Member

@AndreasMadsen AndreasMadsen left a comment

Choose a reason for hiding this comment

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

Mostly LGTM

I'm fine with using ->Call() for now. However, the comment should definitely be changed.

As said, we need to fix this on a more general level.

@addaleax
Copy link
Member Author

addaleax commented Dec 9, 2017

@addaleax addaleax added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Dec 9, 2017
@apapirovski apapirovski removed the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Dec 9, 2017
@apapirovski
Copy link
Member

@addaleax looks like there's at least one related failure in CI:

https://ci.nodejs.org/job/node-test-commit-linux-linked/nodes=ubuntu1604_sharedlibs_fips20_x64/728/tapResults/

internal/crypto/cipher.js:77
   this._handle.init(cipher, password);
                ^  
Error: crypto.createCipher() is not supported in FIPS mode.
     at new Cipher (internal/crypto/cipher.js:77:16)
     at Object.createCipher (crypto.js:94:10)
     at Object.<anonymous> (/home/iojs/build/workspace/node-test-commit-linux-linked/nodes/ubuntu1604_sharedlibs_fips20_x64/test/parallel/test-process-emit-warning-from-native.js:16:10)
     at Module._compile (module.js:660:30)
     at Object.Module._extensions..js (module.js:671:10)
     at Module.load (module.js:577:32)
     at tryModuleLoad (module.js:517:12)
     at Function.Module._load (module.js:509:3)
     at Function.Module.runMain (module.js:701:10)
     at startup (bootstrap_node.js:195:16)

- Handle exceptions when getting `process.emitWarning` or when
  calling it properly
- Add `Maybe<bool>` to the return type, like the V8 API uses it
  to indicate failure conditions
- Update call sites to account for that and clean up/return to JS
  when encountering an error
- Add an internal `ProcessEmitDeprecationWarning()` sibling
  for use in nodejs#17417,
  with common code extracted to a helper function
- Allow the warning to contain non-Latin-1 characters. Since the
  message will usually be a template string containing data passed
  from the user, this is the right thing to do.
- Add tests for the failure modes (except string creation failures)
  and UTF-8 warning messages.

Refs: nodejs#17417
@addaleax addaleax force-pushed the harden-process-emitwarning branch from d83f667 to 12c4f38 Compare December 10, 2017 00:20
@addaleax addaleax force-pushed the harden-process-emitwarning branch from 12c4f38 to a9613ac Compare December 10, 2017 01:36
@addaleax
Copy link
Member Author

@addaleax addaleax added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Dec 10, 2017
Copy link
Member

@BridgeAR BridgeAR left a comment

Choose a reason for hiding this comment

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

JS part is LGTM

@BridgeAR
Copy link
Member

Landed in f3cd537

@BridgeAR BridgeAR closed this Dec 12, 2017
BridgeAR pushed a commit to BridgeAR/node that referenced this pull request Dec 12, 2017
- Handle exceptions when getting `process.emitWarning` or when
  calling it properly
- Add `Maybe<bool>` to the return type, like the V8 API uses it
  to indicate failure conditions
- Update call sites to account for that and clean up/return to JS
  when encountering an error
- Add an internal `ProcessEmitDeprecationWarning()` sibling
  for use in nodejs#17417,
  with common code extracted to a helper function
- Allow the warning to contain non-Latin-1 characters. Since the
  message will usually be a template string containing data passed
  from the user, this is the right thing to do.
- Add tests for the failure modes (except string creation failures)
  and UTF-8 warning messages.

PR-URL: nodejs#17420
Refs: nodejs#17417
Reviewed-By: Andreas Madsen <[email protected]>
Reviewed-By: Ruben Bridgewater <[email protected]>
@addaleax addaleax removed the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Dec 13, 2017
MylesBorins pushed a commit that referenced this pull request Jan 8, 2018
- Handle exceptions when getting `process.emitWarning` or when
  calling it properly
- Add `Maybe<bool>` to the return type, like the V8 API uses it
  to indicate failure conditions
- Update call sites to account for that and clean up/return to JS
  when encountering an error
- Add an internal `ProcessEmitDeprecationWarning()` sibling
  for use in #17417,
  with common code extracted to a helper function
- Allow the warning to contain non-Latin-1 characters. Since the
  message will usually be a template string containing data passed
  from the user, this is the right thing to do.
- Add tests for the failure modes (except string creation failures)
  and UTF-8 warning messages.

PR-URL: #17420
Refs: #17417
Reviewed-By: Andreas Madsen <[email protected]>
Reviewed-By: Ruben Bridgewater <[email protected]>
@MylesBorins MylesBorins mentioned this pull request Jan 10, 2018
@MylesBorins
Copy link
Contributor

Should this land on LTS? I believe there was a question about potentially backporting this to 6.x, but this may be needed for 8.x either way

@addaleax addaleax deleted the harden-process-emitwarning branch January 22, 2018 14:43
@MylesBorins
Copy link
Contributor

ping re: backport

1 similar comment
@MylesBorins
Copy link
Contributor

ping re: backport

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants