Skip to content

Commit f01ceb1

Browse files
Merge pull request #4308 from KimlikDAO-bot:toStringRadix
PiperOrigin-RevId: 908417323
2 parents e5f7a10 + dd78669 commit f01ceb1

4 files changed

Lines changed: 45 additions & 12 deletions

File tree

src/com/google/javascript/jscomp/AstAnalyzer.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ public class AstAnalyzer {
6666
private static final ImmutableSet<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS =
6767
ImmutableSet.of(
6868
"Object", "Array", "String", "Number", "BigInt", "Boolean", "RegExp", "Error");
69-
private static final ImmutableSet<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS =
70-
ImmutableSet.of("toString", "valueOf");
7169
private static final ImmutableSet<String> REGEXP_METHODS = ImmutableSet.of("test", "exec");
7270
private static final ImmutableSet<String> STRING_REGEXP_METHODS =
7371
ImmutableSet.of("match", "replace", "search", "split");
@@ -163,9 +161,12 @@ boolean functionCallHasSideEffects(Node callNode) {
163161
return false;
164162
}
165163
} else if (callee.isGetProp() || callee.isOptChainGetProp()) {
166-
if (callNode.hasOneChild()
167-
&& assumeKnownBuiltinsArePure
168-
&& OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains(callee.getString())) {
164+
String method = callee.getString();
165+
if (assumeKnownBuiltinsArePure
166+
&& ("valueOf".equals(method)
167+
? callNode.hasOneChild()
168+
: "toString".equals(method)
169+
&& (callNode.hasOneChild() || callNode.hasTwoChildren()))) {
169170
return false;
170171
}
171172

@@ -181,7 +182,7 @@ boolean functionCallHasSideEffects(Node callNode) {
181182
&& callee.getFirstChild().isName()
182183
&& callee.isQualifiedName()
183184
&& callee.getFirstChild().getString().equals("Math")) {
184-
switch (callee.getString()) {
185+
switch (method) {
185186
case "abs",
186187
"acos",
187188
"acosh",
@@ -226,13 +227,12 @@ boolean functionCallHasSideEffects(Node callNode) {
226227
}
227228

228229
if (!hasRegexpGlobalReferences && assumeKnownBuiltinsArePure) {
229-
if (callee.getFirstChild().isRegExp() && REGEXP_METHODS.contains(callee.getString())) {
230+
if (callee.getFirstChild().isRegExp() && REGEXP_METHODS.contains(method)) {
230231
return false;
231232
} else if (isTypedAsString(callee.getFirstChild())) {
232233
// Unlike regexs, string methods don't need to be hosted on a string literal
233234
// to avoid leaking mutating global state changes, it is just necessary that
234235
// the regex object can't be referenced.
235-
String method = callee.getString();
236236
Node param = callee.getNext();
237237
if (param != null) {
238238
if (param.isStringLit()) {

test/com/google/javascript/jscomp/AstAnalyzerTest.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -654,13 +654,26 @@ public static ImmutableList<AnalysisCase> cases() {
654654
kase().expect(false).token(SUPER).js("super()"),
655655
kase().expect(false).token(SUPER).js("super.foo()"),
656656

657-
// IObject methods
658-
// "toString" and "valueOf" are by default assumed to be side-effect free
657+
// Some common Object methods are assumed to be side-effect free when called
658+
// with their standard builtin arity.
659659
kase().expect(false).js("o.toString()"),
660+
kase().expect(false).js("o.toString(16)"),
661+
kase().expect(false).js("(0).toString(16)"),
662+
kase().expect(false).js("(0n).toString(16)"),
663+
kase().expect(false).js("o?.toString(16)"),
664+
kase().expect(false).js("(0)?.toString(16)"),
665+
kase().expect(false).js("(0n)?.toString(16)"),
660666
kase().expect(false).js("o.valueOf()"),
661-
// "toString" and "valueOf" are not assumed to be side-effect free when
662-
// assumeKnownBuiltinsArePure is false.
667+
kase().expect(true).js("o.toString(16, 2)"),
668+
kase().expect(true).js("o.valueOf(1)"),
669+
// These builtin-method assumptions are disabled when assumeKnownBuiltinsArePure is false.
663670
kase().expect(true).js("o.toString()").assumeBuiltinsPure(false),
671+
kase().expect(true).js("o.toString(16)").assumeBuiltinsPure(false),
672+
kase().expect(true).js("(0).toString(16)").assumeBuiltinsPure(false),
673+
kase().expect(true).js("(0n).toString(16)").assumeBuiltinsPure(false),
674+
kase().expect(true).js("o?.toString(16)").assumeBuiltinsPure(false),
675+
kase().expect(true).js("(0)?.toString(16)").assumeBuiltinsPure(false),
676+
kase().expect(true).js("(0n)?.toString(16)").assumeBuiltinsPure(false),
664677
kase().expect(true).js("o.valueOf()").assumeBuiltinsPure(false),
665678
// other methods depend on the extern definitions
666679
kase().expect(true).js("o.watch()"),

test/com/google/javascript/jscomp/CheckSideEffectsTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,4 +452,14 @@ public void testIgnoresRuntimeLibRequireStatementsOnly() {
452452
srcs(SourceFile.fromCode("other/file.js", "'require base'")),
453453
warning(CheckSideEffects.USELESS_CODE_ERROR));
454454
}
455+
456+
@Test
457+
public void testToStringWithRadixIsUselessCode() {
458+
testWarning("({}).toString(16);", e);
459+
}
460+
461+
@Test
462+
public void testOptionalChainToStringWithRadixIsUselessCode() {
463+
testWarning("({})?.toString(16);", e);
464+
}
455465
}

test/com/google/javascript/jscomp/PureFunctionIdentifierTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3668,6 +3668,16 @@ public void testCallCache_propagatesSideEffects() {
36683668
});
36693669
}
36703670

3671+
@Test
3672+
public void testToStringWithRadixIsPure() {
3673+
assertPureCallsMarked("var x = {}; x.toString(16);", ImmutableList.of("x.toString"));
3674+
}
3675+
3676+
@Test
3677+
public void testOptionalChainToStringWithRadixIsPure() {
3678+
assertPureCallsMarked("var x = {}; x?.toString(16);", ImmutableList.of("x?.toString"));
3679+
}
3680+
36713681
@Test
36723682
public void testDynamicImport() {
36733683
ignoreWarnings(DiagnosticGroups.MODULE_LOAD);

0 commit comments

Comments
 (0)