Skip to content

Possibly deoptimizing use of arguments in libs #10323

Description

@vsemozhetbyt

I've started to read the v8-bailout-reasons, in particular the "Bad value context for arguments value" part. @vhf refers to this part of more detailed research, where we can find some safety rules about arguments. So I've tried to scan Node.js libs for possible violations of these rules. Here are the suspicious fragments I've found.

  1. Leaking arguments:

domain.js: function Domain.prototype.intercept leaks here. (Possibly is not worth the efforts; see this comment)
domain.js: function Domain.prototype.bind leaks here. (Possibly is not worth the efforts; see this comment)
internal/process.js: function setupConfig leaks here. (Possibly is not worth the efforts; see this comment)
internal/util.js: function exports._deprecate leaks here. (It does not cause deopt; see this PR)

  1. Using arguments[index] with a possibility of index to be out of the arguments bounds.

This is more difficult case to check, but I've inferred possible arguments.length variability from docs and code comments. Maybe I'm wrong for some or all cases. I will refer to a first possible violation in a function, the function can contain more.

child_process.js: function normalizeExecArgs uses possible out of bounds index(es) from here. (Fixed)
child_process.js: function exports.execFile uses possible out of bounds index(es) from here. (Fixed)
child_process.js: function normalizeSpawnArguments uses possible out of bounds index(es) from here. (Fixed)

dgram.js: function Socket.prototype.bind uses possible out of bounds index(es) from here. (Fixed)

events.js: function EventEmitter.prototype.emit uses possible out of bounds index(es) from here.
There is a sign of awareness in the code, but maybe ES6 makes it possible to resolve this difficulty in the EventEmitter.prototype.emit itself.
(Fixed)

fs.js: all the functions with maybeCallback or makeCallback calls can use possible out of bounds index(es). (they are not called without parameters; see this PR)

util.js: function inspect uses possible out of bounds index(es) from here. (Fixed)

_debugger.js: function Interface.prototype.scripts uses possible out of bounds index(es) from here. (Possibly is not worth the efforts; see this comment)
_debugger.js: function Interface.prototype.watchers uses possible out of bounds index(es) from here. (Possibly is not worth the efforts; see this comment)

I am not sure about any of these cases: if they are real violations, if these violations have any real impact on the performance and so on. And unfortunately, I have not sufficient knowledge of all the codebase system to propose any changes here. So take this as a start point memo for contributors who consider it to be worth any attention and close it if this is some needless overagitating.

P.S. Part 2.

Activity

  1. added
    lib / srcIssues and PRs involving general changes in the lib/ or src/ directories.
    v8 engineIssues and PRs related to the V8 dependency.
    on Dec 18, 2016
  2. added
    performanceIssues and PRs related to the performance of Node.js.
    on Dec 18, 2016
  3. not-an-aardvark commented on Dec 18, 2016

    @not-an-aardvark
    Contributor

    I'm not familiar enough with V8 to know whether these uses of arguments cause a noticeable performance drop, but if we conclude that they are worth avoiding, it seems like it should be possible to make a linter rule for them.

  4. vsemozhetbyt commented on Dec 18, 2016

    @vsemozhetbyt
    ContributorAuthor

    Eslint has a rather radical rule for all arguments usecases. However, the mentioned researches propose less drastic workarounds. For example, index checking, arguments copying and .apply() mediation are already used in many lib places.

  5. vhf commented on Dec 19, 2016

    @vhf
    Contributor

    Interesting, nice findings @vsemozhetbyt .

    Fixing the possible out of bound access to the arguments object might be worth it. I glanced at the other things you mentioned and it might be either complicated to fix or not worth the effort or both.

    I'm not familiar with node internals so I might be wrong, but here is an example: setupConfig leaks arguments. It's a tiny utility method and I'm pretty sure its use is extremely seldom. I'm pretty sure this function not being optimized cannot have any sort of significant performance impact.

    What might be slightly more interesting in terms of performances, but might break some people's code, would be to set process.icu_data_dir to null instead of deleteing it. This way the process object will stay fast (because of delete it currently becomes a dictionary mode object and we can assume this slow object will be reused in people's code).

  6. vsemozhetbyt commented on Dec 19, 2016

    @vsemozhetbyt
    ContributorAuthor

    @vhf Thank you for the answer.

    As I have said, I'm also not aware of all the system. For example, the mentioned setupConfig() function is exported from the internal lib and it needs to be considered in addition how many times it can be called in all the lib system and how many times it can be triggered by various user code. (By the way, it seems you have used by accident the link to the documented property of another lib of the same name instead of the function of the internal lib).

    And thank you for the second comment. It will need more checks for other similar cases. Is this feature connected with v8 hidden classes you've described recently? I've also read about it in another article (chapter "Objects")

  7. bnoordhuis commented on Dec 19, 2016

    @bnoordhuis
    Member

    I suspect the only functions where it could make a material difference are EventEmitter#emit() and util.inspect(), the others are unlikely to register under normal conditions.

    Fixing the other functions might still be worthwhile from a code hygiene perspective, but not if it significantly affects complexity or readability.

  8. Fishrock123 commented on Dec 19, 2016

    @Fishrock123
    Contributor

    (If you'd like to help fix this look here.)

    My assessment:

    • Domain.prototype.intercept - ¯\_(ツ)_/¯
    • Domain.prototype.bind - ¯\_(ツ)_/¯
    • setupConfig (... really v8BreakIterator) - ¯\_(ツ)_/¯
    • exports._deprecate - ⚠️ We should probably check this one.
    • child_process.js > normalizeExecArgs - ⚠️ Should probably fix that.
    • child_process.js > exports.execFile - Are you sure this can happen out of bounds? Probably fix.
    • child_process.js > normalizeSpawnArgs - ⚠️ Should probably fix that.
    • dgram.js > Socket.prototype.bind - ⚠️ Should bail if no arguments before this statement.
    • events.js > EventEmitter.prototype.emit - ❗️Almost certainly important. Check for no args passed.
    • fs I didn't check, but sounds ⚠️.
    • util.js > inspect. - ❗️Almost certainly important. Check arg counts in these.
    • _debugger.js - ¯\_(ツ)_/¯
  9. vsemozhetbyt commented on Dec 19, 2016

    @vsemozhetbyt
    ContributorAuthor

    @Fishrock123

    child_process.js > exports.execFile - Are you sure this can happen out of bounds? Probably fix.

    Well, suppose we have only the first argument (file) set. So arguments.length is 1 (and only arguments[0] is set). So all these if's are skipped and pos remains 1 and callback remains undefined. So arguments[pos] here is arguments[1], i.e. out of bounds. Maybe I miss something.

  10. mgol commented on Dec 23, 2016

    @mgol
    Contributor

    Do rest parameters have the same deopt problems as the arguments object? Are there any reasons why Node still uses arguments when it can use the full ES6 power?

  11. CapacitorSet commented on Dec 25, 2016

    @CapacitorSet

    @mgol, rest parameters are a known Crankshaft bailout (deoptimization) reason at this time.

  12. bmeurer commented on Jan 1, 2017

    @bmeurer
    Member

    @mgol Rest parameters force optimization via TurboFan, which should be fine in many cases, esp. with V8 5.6 and later (not yet available for Node.js tho).

  13. bmeurer commented on Jan 1, 2017

    @bmeurer
    Member

    @Fishrock123 EventEmitter.prototype.emit is pretty serious, esp. since with newer versions of V8, Crankshaft will disable optimization for the whole function (due a correctness fix wrt. Crankshaft's handling of arguments). See crbug.com/v8/3829 for more information on this particular magic.

  14. 18 remaining items

  15. vsemozhetbyt commented on Feb 2, 2017

    @vsemozhetbyt
    ContributorAuthor

    The initial comment is updated considering this opinion and all the committed fixes.

  16. vsemozhetbyt commented on Mar 2, 2017

    @vsemozhetbyt
    ContributorAuthor

    It seems all the mentioned cases have been fixed or reasonably rejected, so this can be closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    good first issueIssues that are suitable for first-time contributors.lib / srcIssues and PRs involving general changes in the lib/ or src/ directories.performanceIssues and PRs related to the performance of Node.js.v8 engineIssues and PRs related to the V8 dependency.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions