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

querystring: check that maxKeys is finite #5066

Merged
merged 1 commit into from
Feb 5, 2016

Conversation

MylesBorins
Copy link
Contributor

There was a very subtle change in behavior introduced with 27def4f

In the past if querystring.parse was given Infinity for maxKeys,
everything worked as expected.

Check to see is maxKeys is Infinity before forwarding the value to
String.prototype.split which causes this regression

@MylesBorins
Copy link
Contributor Author

/cc @jasnell @targos @thefourtheye

@evanlucas
Copy link
Contributor

LGTM, but should the test be in a separate commit?

@MylesBorins
Copy link
Contributor Author

LGTM, but should the test be in a separate commit?

I'm really not sure, I can check previous commits. To me it makes sense to include the test with the commit removing the regression.

// maxKeys <= 0 means that we should not limit keys count
if (maxKeys > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't it be better, if we just throw an isFinite check here?

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 is actually a great idea.

@MylesBorins MylesBorins changed the title querystring: revert "use String.prototype..." querystring: check that maxKeys is finite Feb 4, 2016
@thefourtheye
Copy link
Contributor

The spec documents that the second argument must be an integer.
Infinity is being interpreted as 0... instead of MAX_INT
(perhaps this should be a fix upstream to v8).

Sadly, it is as per the spec only.

String.prototype.split says,

If limit is undefined, let lim = 232–1; else let lim = ToUint32(limit).

and 9.6 ToUint32: (Unsigned 32 Bit Integer) says,

If number is NaN, +0, −0, +∞, or −∞, return +0.

@MylesBorins
Copy link
Contributor Author

@thefourtheye great idea, I've restructured this commit

@evanlucas I'll need another LGTM. Rather than reverting I've added an extra check for infinity. It now makes much more sense to include the test

@mscdex mscdex added the querystring Issues and PRs related to the built-in querystring module. label Feb 4, 2016
@MylesBorins
Copy link
Contributor Author

@nodejs/testing is there a preference to writing more smaller tests, or putting this in with another test already testing querystring?

@evanlucas
Copy link
Contributor

Yea, I like this. LGTM. Definitely need CI on this one though to make sure arm is happy with it too

@thefourtheye
Copy link
Contributor

Adding a new test is fine in this case, and it would be better to have this issue also mentioned somewhere in the comments.

@@ -0,0 +1,36 @@
'use strict';
const common = require('../common');
Copy link
Contributor

Choose a reason for hiding this comment

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

Didn't our linter complain about unused variable? I thought this,

  ## disallow declaration of variables that are not used in the code
  no-unused-vars: [2, {"args": "none"}]

forbids us

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah... I just pushed an update that fixes the linting issues

@MylesBorins MylesBorins force-pushed the query-string-infinity branch 2 times, most recently from 928bb12 to 34c252c Compare February 4, 2016 00:26
// 27def4f introduced a change to parse that would cause Inifity
// to be passed to String.prototype.split as an argument for limit
// In this instance split will always return an empty array
// this test confirms that the output of parse is the expe4cted length
Copy link
Contributor

Choose a reason for hiding this comment

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

expe4cted -> expected

// In this instance split will always return an empty array
// this test confirms that the output of parse is the expe4cted length
// when passed Infinity as the argument for maxKeys
const result = parse(params, undefined, undefined, {maxKeys: Infinity});
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that we are doing it, let's introduce tests with NaN, 'Infinity' and 'NaN'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@thefourtheye I've updated the test to include tests for all four "non finite" values

@MylesBorins
Copy link
Contributor Author

@thefourtheye
Copy link
Contributor

I couldn't think of any other corner cases. LGTM.

@cjihrig
Copy link
Contributor

cjihrig commented Feb 4, 2016

It might be a little more efficient to add the isFinite() check to the line if (options && typeof options.maxKeys === 'number') {. Then you would only get the overhead if a number is provided as input.

@cjihrig
Copy link
Contributor

cjihrig commented Feb 4, 2016

LGTM with a comment.

@thefourtheye
Copy link
Contributor

In that case we can simply do if (options && Number.isFinite(options.maxKeys)

@Trott
Copy link
Member

Trott commented Feb 4, 2016

@nodejs/testing is there a preference to writing more smaller tests, or putting this in with another test already testing querystring?

Speaking for myself only, I definitely prefer smaller, isolated tests.

const assert = require('assert');
const parse = require('querystring').parse;

// taken from express-js/body-parser
Copy link
Member

Choose a reason for hiding this comment

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

put in a full github url including hash for the exact version you're pulling from

@rvagg
Copy link
Member

rvagg commented Feb 4, 2016

/cc @manvalls

@thealphanerd I think this is ok, lgtm

@Trott:

@nodejs/testing is there a preference to writing more smaller tests, or putting this in with another test already testing querystring?

Speaking for myself only, I definitely prefer smaller, isolated tests.

One thing to keep in mind is that separate tests take longer to run in CI than compacting them into fewer tests. Primarily due to very slow startup time on slower ARM machines but also it seems like this AIX and musl libc based systems have slow exit times (ref #5056).

More files, separate tests is certainly the nicer way to approach it but I just wanted to register the above concern so that @nodejs/testing is aware of this and can (maybe) take it into account when forming opinions on how to best structure tests.

@MylesBorins
Copy link
Contributor Author

@cjihrig @thefourtheye if we made the suggested change then maxKeys will always = 1000, this would also result in a behaviour change

@MylesBorins MylesBorins force-pushed the query-string-infinity branch 2 times, most recently from 1856970 to 518f4f5 Compare February 4, 2016 21:26
@MylesBorins
Copy link
Contributor Author

@rvagg I've updated the test with a comment that includes the github link, with sha and line numbers.

As you can expect this link is far over the max characters per line. I've split it on to two lines, hopefully that isn't confusing

@cjihrig
Copy link
Contributor

cjihrig commented Feb 4, 2016

@thealphanerd no, this is what I was proposing. It should be functionally equivalent to what you have, but slightly more efficient. And, as @thefourtheye pointed out, the last two conditions can be condensed to Number.isFinite().

  var maxKeys = 1000;
  if (options && typeof options.maxKeys === 'number' && isFinite(options.maxKeys)) {
    maxKeys = options.maxKeys;
  }

  // maxKeys <= 0 means that we should not limit keys count
  if (maxKeys > 0) {

@MylesBorins
Copy link
Contributor Author

maxKeys is set just above though. Meaning that if(maxKeys > 0) will be true if options.MaxKeys === Infinity

thus the split will be called with maxKeys set to 1000 which is still a behavior change.

To double check that I am correct I have implemented your change and run it against the tests and we get a failure

AssertionError: 1000 == 10000

@cjihrig
Copy link
Contributor

cjihrig commented Feb 5, 2016

Sorry, you're right. I was thinking we wanted maxKeys to be the default value on bad input. The existing behavior seems silly... Use 1000 as a default, but if a number is provided use that, but if it's not positive and finite, ignore it completely.

@thefourtheye
Copy link
Contributor

Use 1000 as a default, but if a number is provided use that, but if it's not positive and finite, ignore it completely.

Ya, that doesn't look right. Can we fix this behavior here?

@MylesBorins
Copy link
Contributor Author

@thefourtheye I think we can fix that behavior, but that would be a Major change. In the mean time we do need to patch back the behavior that is expected (imho).

Or are you arguing that the behavior itself was a bug?

@thefourtheye
Copy link
Contributor

@thealphanerd Though it seems odd, as you pointed out, our priority here is to get this fixed asap, with the current functionality. Perhaps we can improve this in a separate patch. LGTM.

@MylesBorins
Copy link
Contributor Author

I think it is also worth mentioning that #5012 rewrite all the code this is touching.

@ncopa
Copy link
Contributor

ncopa commented Feb 5, 2016

it seems like this AIX and musl libc based systems have slow exit times (ref #5056).

Technically, exit itself is quick. What happens is that the parent exits without reaping the child processes, so init (pid 1) has to reap those zombies. The slow timeout on Alpine Linux was due to busybox init has a "design flaw" that will intentionally delay the reaping of the orphaned child processes. This has actually nothing to do with musl libc and I am pretty sure that busybox init with glibc has the exact same issue. Most likely this is what AIX init does too.

Exit times are fast. But someone/something needs to reap the dead processes. Ideally should the parent process take care of that instead of letting init (pid 1) do it.

@Fishrock123
Copy link
Contributor

Rubber-stamp LGTM

There was a very subtle change in behavior introduced with 27def4f

In the past if querystring.parse was given Infinity for maxKeys,
everything worked as expected.

Check to see is maxKeys is Infinity before forwarding the value to
String.prototype.split which causes this regression

PR-URL: nodejs#5066
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
@MylesBorins MylesBorins merged commit 878bcd4 into nodejs:master Feb 5, 2016
@MylesBorins
Copy link
Contributor Author

Landed as 878bcd4

@thefourtheye
Copy link
Contributor

Express must be happy now, right?

@rvagg
Copy link
Member

rvagg commented Feb 8, 2016

Thanks @ncopa, that's very interesting background

rvagg pushed a commit that referenced this pull request Feb 8, 2016
There was a very subtle change in behavior introduced with 27def4f

In the past if querystring.parse was given Infinity for maxKeys,
everything worked as expected.

Check to see is maxKeys is Infinity before forwarding the value to
String.prototype.split which causes this regression

PR-URL: #5066
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
@MylesBorins MylesBorins deleted the query-string-infinity branch February 8, 2016 18:46
rvagg pushed a commit that referenced this pull request Feb 9, 2016
There was a very subtle change in behavior introduced with 27def4f

In the past if querystring.parse was given Infinity for maxKeys,
everything worked as expected.

Check to see is maxKeys is Infinity before forwarding the value to
String.prototype.split which causes this regression

PR-URL: #5066
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
scovetta pushed a commit to scovetta/node that referenced this pull request Apr 2, 2016
There was a very subtle change in behavior introduced with 27def4f

In the past if querystring.parse was given Infinity for maxKeys,
everything worked as expected.

Check to see is maxKeys is Infinity before forwarding the value to
String.prototype.split which causes this regression

PR-URL: nodejs#5066
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed By: Sakthipriyan Vairamani <thechargingvolcano@gmail.com>
Reviewed-By: Rod Vagg <rod@vagg.org>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
querystring Issues and PRs related to the built-in querystring module.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants