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

util.format('%s', o) fails to call String(o) in certain cases #30333

Closed
cpcallen opened this issue Nov 8, 2019 · 3 comments
Closed

util.format('%s', o) fails to call String(o) in certain cases #30333

cpcallen opened this issue Nov 8, 2019 · 3 comments
Labels
confirmed-bug Issues with confirmed bugs. util Issues and PRs related to the built-in util module.

Comments

@cpcallen
Copy link

cpcallen commented Nov 8, 2019

  • Version: v12.12.0
  • Platform: Darwin [redacted] 18.7.0 Darwin Kernel Version 18.7.0: Sat Oct 12 00:02:19 PDT 2019; root:xnu-4903.278.12~1/RELEASE_X86_64 x86_64
  • Subsystem: util

I was very surprised to discover that in node v12, util.format('%s', v) does not consistently call String(v). Checking the docs, I read:

%s: String will be used to convert all values except BigInt, Object and -0. BigInt values will be represented with an n and Objects that have no user defined toString function are inspected using util.inspect() with options { depth: 0, colors: false, compact: 3 }.

This is a surprising (and in my view undesirable) change from the previous behaviour, which was to consistently call String in response to %s. It also appears to be a semantically breaking change introduced, via #27621 and #27799, in 12.3.0, despite that not being a new major version. If so, it should be entirely reverted (and good riddance, in my opinion).

If there is some compelling argument for why the new behaviour is actually desirable, then at least the code should be made consistent with the documentation or vice versa.

In particular, whether a user defined toString function is called depends in non-documented and non-obvious ways on the value of the .constructor property of the object, which is otherwise normally of no significance:

// ES6 subclassing:
class A {
  toString() { return 'custom A'; }
}
class B extends A {}

// ES5 subclassing:
function C() {}
C.prototype.toString = function() { return 'custom C'; };

function D() { C.call(this); }
D.prototype = Object.create(C.prototype);
// What if we forget to set the .constructor?
// D.prototype.constructor = D;

console.log('%s', new A());
console.log('%s', new B());
console.log('%s', new C());
console.log('%s', new D());

// Fix forgotten .constructor:
D.prototype.constructor = D;
console.log('%s', new D());

Actual output:

custom A
B {}
custom C
custom C
D {}

Expected / documented output:

custom A
custom A
custom C
custom C
custom C

Consistently applying the documented behaviour would go some way to remedying the breaking nature of this change, since at least user supplied .toString implementations would always be called, as was previously the case.

cpcallen added a commit to cpcallen/bugs that referenced this issue Nov 8, 2019
The documentation says that util.format('%s', obj) will call String(obj) iff obj has a user-defined .toString method, but in v12.12.0 it only calls String(obj) if either obj or obj.constructor.prototype has an own property named toString, causing inherited toString implementations to be ignored.

Submitted as nodejs/node#30333
@BridgeAR BridgeAR added confirmed-bug Issues with confirmed bugs. util Issues and PRs related to the built-in util module. labels Nov 8, 2019
@BridgeAR
Copy link
Member

BridgeAR commented Nov 8, 2019

@cpcallen thank you for the bug report! This is indeed not intended and it seems like we are missing a couple of test cases (so thank you very much for highlighting the different solutions).

I will have a look at it next week.

@himself65
Copy link
Member

himself65 commented Nov 15, 2019

It's okay on node v10.17.0 and the same problem on v13.1.0

@cpcallen
Copy link
Author

It's okay on node v10.17.0 and the same problem on v13.1.0

Yes; as noted above it was introduced in v12.3.0.

cpcallen added a commit to google/CodeCity that referenced this issue Nov 22, 2019
Work around nodejs/node#30333, introduced in node.js v12.3.0.
cpcallen added a commit to google/CodeCity that referenced this issue Nov 28, 2019
Work around nodejs/node#30333, introduced in node.js v12.3.0.
cpcallen added a commit to google/CodeCity that referenced this issue Nov 28, 2019
Work around nodejs/node#30333, introduced in node.js v12.3.0.

(cherry picked from commit de7e974)
cpcallen added a commit to google/CodeCity that referenced this issue Nov 29, 2019
Work around nodejs/node#30333, introduced in node.js v12.3.0.

(cherry picked from commit de7e974)
targos pushed a commit that referenced this issue Dec 1, 2019
This makes sure that `util.format('%s', object)` will always call
a user defined `toString` function. It was formerly not the case
when the object had the function declared on the super class.

At the same time this also makes sure that getters won't be
triggered accessing the `constructor` property.

PR-URL: #30343
Fixes: #30333
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
MylesBorins pushed a commit that referenced this issue Jan 30, 2020
This makes sure that `util.format('%s', object)` will always call
a user defined `toString` function. It was formerly not the case
when the object had the function declared on the super class.

At the same time this also makes sure that getters won't be
triggered accessing the `constructor` property.

Backport-PR-URL: #31431
PR-URL: #30343
Fixes: #30333
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
BethGriggs pushed a commit that referenced this issue Feb 6, 2020
This makes sure that `util.format('%s', object)` will always call
a user defined `toString` function. It was formerly not the case
when the object had the function declared on the super class.

At the same time this also makes sure that getters won't be
triggered accessing the `constructor` property.

Backport-PR-URL: #31431
PR-URL: #30343
Fixes: #30333
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
abhishekumar-tyagi pushed a commit to abhishekumar-tyagi/node that referenced this issue May 5, 2024
This makes sure that `util.format('%s', object)` will always call
a user defined `toString` function. It was formerly not the case
when the object had the function declared on the super class.

At the same time this also makes sure that getters won't be
triggered accessing the `constructor` property.

PR-URL: nodejs/node#30343
Fixes: nodejs/node#30333
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
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
confirmed-bug Issues with confirmed bugs. util Issues and PRs related to the built-in util module.
Projects
None yet
3 participants