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

feat(NODE-4874): support EJSON parse for BigInt from $numberLong #552

Merged

Conversation

W-A-James
Copy link
Contributor

@W-A-James W-A-James commented Jan 5, 2023

Description

Adding support to parse bigints from EJSON

Is there new documentation needed for these changes?

No

What is the motivation for this change?

Continue BigInt support improvement.

Double check the following

  • Ran npm run lint script
  • Self-review completed using the steps outlined here
  • PR title follows the correct format: type(NODE-xxxx)[!]: description
    • Example: feat(NODE-1234)!: rewriting everything in coffeescript
  • Changes are covered by tests
  • New TODOs have a related JIRA ticket

Comment on lines 373 to 377
options = {
useBigInt64: options?.useBigInt64 ?? false,
relaxed: options?.relaxed ?? true,
legacy: options?.legacy ?? false
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did this to clean up how default options are handled. It is not handled cleanly in a number of places in this file

@W-A-James W-A-James force-pushed the NODE-4874/support_ejson_parse_for_BigInt_from_numberLong branch from b9c0850 to 2f5db05 Compare January 6, 2023 21:20
@W-A-James W-A-James marked this pull request as ready for review January 6, 2023 21:20
@nbbeeken nbbeeken self-requested a review January 10, 2023 20:58
@nbbeeken nbbeeken added the Primary Review In Review with primary reviewer, not yet ready for team's eyes label Jan 10, 2023
Copy link
Contributor

@nbbeeken nbbeeken left a comment

Choose a reason for hiding this comment

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

Starting with code change comments, looking at the tests now

src/extended_json.ts Show resolved Hide resolved
src/extended_json.ts Outdated Show resolved Hide resolved
src/extended_json.ts Outdated Show resolved Hide resolved
src/long.ts Outdated Show resolved Hide resolved
src/long.ts Outdated Show resolved Hide resolved
test/node/bigint.test.ts Show resolved Hide resolved
src/extended_json.ts Outdated Show resolved Hide resolved
src/extended_json.ts Outdated Show resolved Hide resolved
src/long.ts Outdated
const defaults = { useBigInt64: false, relaxed: true };
const ejsonOptions = { ...defaults, ...options };
if (ejsonOptions.useBigInt64) {
return BigInt(doc.$numberLong);
Copy link
Contributor

Choose a reason for hiding this comment

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

Unlike in BSON and the 'number' case for EJSON, there isn't restrictions on value size or length.

Luckily BigInt casting function will throw an error for bad syntax unlike Number (yay!) (can we add a test? {$numberLong: "blah!"})

But we have added support here for syntax that should not work:

  • { $numberLong: "0xA" } should fail
  • { $numberLong: "0o7" } should fail
  • { $numberLong: "0b1" } should fail

And we've unintentionally allowed strings of any length to be provided here, I'm not sure what would be the best approach here, we can either figure out some way to assert some maximum length. Or we can construct a Long.fromString and use long.toBigInt here, asserting that the fromString logic would prevent an extremely long string from being converted.

Copy link
Contributor

@nbbeeken nbbeeken Jan 11, 2023

Choose a reason for hiding this comment

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

From discussion: We want to assert a maximum length on the string, this isn't the same as what Long does but our newer API can be better about preventing malformed input. There should be no leading zeros permitted but a plus or minus sign needs to be supported. We can iterate the string or use regexp to assert this.

Consider making a new BSONError subclass

Copy link
Contributor

Choose a reason for hiding this comment

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

There should be no leading zeros permitted

They are currently permitted when deserializing to Long, as far as I can tell. I would recommend to keep validation consistent between deserializing to Long and deserializing to bigint (and changing validation for deserializing to Long would probably be breaking, but this might be exactly the right time for that if it’s something you want to do).

Copy link
Contributor

@nbbeeken nbbeeken Jan 12, 2023

Choose a reason for hiding this comment

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

If we're all in agreement (@dariakp @baileympearson @durran @W-A-James) I think we can do the validation for both bigint and Long cases, I was aiming to only impact the bigint case here since it would be opt-in, but useBigInt64 and increasing the validation are divergent concerns so we shouldn't overload the option's behavior.

We certainly don't want to add support for the hex, oct, and bin formats just because useBigInt64 is turned on, so that assertion I think we're all on board with.

Long.fromString currently allows a number of things that probably should be errors like

  • "-----------123" - the number of minus signs corresponds to the number of recursive fromString calls (bigint will throw on this input)
  • "00000000123" - leading zeros are ignored
  • "12.23" - becomes 12
  • '8'.repeat(25) - too large, can be determined by string length (gets truncated)
  • "9223372036854775808" - too large int64.max + 1 (gets truncated)

We can decide to apply all, some, or none of the above validations, I think the most valuable and least pedantic is applying a string length check, but they're all great if we want to improve things :)

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 agree that at the very least, we should add the string length check to inputs of fromExtendedJSON in both the bigInt and Long cases to keep with the canonical EJSON spec and to prevent hanging on strings that would be too long to be a valid 64-bit integer and may be the result of malicious user input.

Canonical EJSON representation of a BSON.Long:

{"$numberLong": <64-bit signed integer as a string>}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updating with results of Slack discussion;

Going to change bigint case to truncate rather than throw an error on encountering a bigint outside of int64 range to be consistent with behaviour of Long.toString

@@ -367,7 +364,7 @@ describe('BSON BigInt support', function () {
}
});

describe('relaxed double input where double is outside of int32 range and useBigInt64 is true', function () {
describe('relaxed double input where double is outside of int32 range and useBigInt64 is true', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

We can add a test that checks BigInt usage is all behind gated logic. Take a look at the example where we test the library with and without a crypto global here: https://github.com/mongodb/js-bson/blob/main/test/node/byte_utils.test.ts#L603

We can load the BSON library with BigInt set to undefined, that doesn't cover using trailing n syntax but it's something

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would it be appropriate to do this in another PR as just removing the BigInt field from the globalThis object isn't enough to be sure that our BigInt work doesn't break anything in environments that don't support BigInts. We'd have to go through the ES BigInt spec and make sure that we're not using any of the associated functionality and that seems outside the scope of this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

sure, let's file a follow up

src/long.ts Outdated Show resolved Hide resolved
src/long.ts Outdated Show resolved Hide resolved
test/node/long.test.ts Outdated Show resolved Hide resolved
test/node/long.test.ts Show resolved Hide resolved
src/long.ts Outdated Show resolved Hide resolved
src/extended_json.ts Outdated Show resolved Hide resolved
src/long.ts Outdated Show resolved Hide resolved
src/long.ts Outdated Show resolved Hide resolved
src/long.ts Outdated Show resolved Hide resolved
@nbbeeken nbbeeken added Team Review Needs review from team and removed Primary Review In Review with primary reviewer, not yet ready for team's eyes labels Jan 18, 2023
test/node/bigint.test.ts Outdated Show resolved Hide resolved
@nbbeeken nbbeeken changed the title feat(NODE-4874): Support EJSON parse for BigInt from $numberLong feat(NODE-4874): support EJSON parse for BigInt from $numberLong Jan 19, 2023
@nbbeeken nbbeeken merged commit 854aa70 into main Jan 19, 2023
@nbbeeken nbbeeken deleted the NODE-4874/support_ejson_parse_for_BigInt_from_numberLong branch January 19, 2023 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Team Review Needs review from team
Projects
None yet
3 participants