Skip to content

Commit 54f9f2f

Browse files
lauraharkercopybara-github
authored andcommitted
Always intern strings in TypedAst StringPool objects
PiperOrigin-RevId: 704719681
1 parent 4a6152d commit 54f9f2f

File tree

6 files changed

+338
-33
lines changed

6 files changed

+338
-33
lines changed

src/com/google/javascript/jscomp/j2clbuild/super/com/google/javascript/rhino/RhinoStringPool.java

+20
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package com.google.javascript.rhino;
1818

19+
import java.util.List;
20+
import java.util.stream.Stream;
21+
1922
/**
2023
* A JS compatible implementation of the string pool.
2124
*
@@ -33,4 +36,21 @@ public static String addOrGet(String s) {
3336
}
3437

3538
private RhinoStringPool() {}
39+
40+
/**
41+
* Stub implementation
42+
*
43+
* <p>Ok because it's only used for serialization/deserialization
44+
*/
45+
public static final class LazyInternedStringList {
46+
public LazyInternedStringList(List<String> strings) {}
47+
48+
public String get(int offset) {
49+
return null;
50+
}
51+
52+
public Stream<String> stream() {
53+
return Stream.empty();
54+
}
55+
}
3656
}

src/com/google/javascript/jscomp/serialization/ScriptNodeDeserializer.java

+28-20
Original file line numberDiff line numberDiff line change
@@ -332,12 +332,19 @@ private Node createSourceInfoTemplate(SourceFile file) {
332332

333333
private void setOriginalNameIfPresent(AstNode astNode, Node n) {
334334
if (astNode.getOriginalNamePointer() != 0) {
335-
n.setOriginalName(this.stringPool.get(astNode.getOriginalNamePointer()));
335+
n.setOriginalNameFromStringPool(
336+
this.stringPool.getInternedStrings(), astNode.getOriginalNamePointer());
336337
}
337338
}
338339

339-
private String getString(AstNode n) {
340-
return this.stringPool.get(n.getStringValuePointer());
340+
/**
341+
* Creates a new string node with the given token & string value of the AstNode
342+
*
343+
* <p>Prefer calling this method over calling a regular Node.* or IR.* method when possible. This
344+
* method integrates with {@link RhinoStringPool} to cache String interning results.
345+
*/
346+
private Node stringNode(Token token, AstNode n) {
347+
return Node.newString(token, this.stringPool.getInternedStrings(), n.getStringValuePointer());
341348
}
342349

343350
private Node deserializeSingleNode(AstNode n) {
@@ -348,9 +355,9 @@ private Node deserializeSingleNode(AstNode n) {
348355
case NUMBER_LITERAL:
349356
return IR.number(n.getDoubleValue());
350357
case STRING_LITERAL:
351-
return IR.string(getString(n));
358+
return stringNode(Token.STRINGLIT, n);
352359
case IDENTIFIER:
353-
return IR.name(getString(n));
360+
return stringNode(Token.NAME, n);
354361
case FALSE:
355362
return new Node(Token.FALSE);
356363
case TRUE:
@@ -362,7 +369,8 @@ private Node deserializeSingleNode(AstNode n) {
362369
case VOID:
363370
return new Node(Token.VOID);
364371
case BIGINT_LITERAL:
365-
return IR.bigint(new BigInteger(getString(n)));
372+
String bigintString = this.stringPool.get(n.getStringValuePointer());
373+
return IR.bigint(new BigInteger(bigintString));
366374
case REGEX_LITERAL:
367375
return new Node(Token.REGEXP);
368376
case ARRAY_LITERAL:
@@ -377,7 +385,7 @@ private Node deserializeSingleNode(AstNode n) {
377385
case NEW:
378386
return new Node(Token.NEW);
379387
case PROPERTY_ACCESS:
380-
return Node.newString(Token.GETPROP, getString(n));
388+
return stringNode(Token.GETPROP, n);
381389
case ELEMENT_ACCESS:
382390
return new Node(Token.GETELEM);
383391

@@ -497,10 +505,10 @@ private Node deserializeSingleNode(AstNode n) {
497505
case TEMPLATELIT_STRING:
498506
{
499507
TemplateStringValue templateStringValue = n.getTemplateStringValue();
500-
int cookedPointer = templateStringValue.getCookedStringPointer();
501-
String cookedString = cookedPointer == -1 ? null : this.stringPool.get(cookedPointer);
502-
String rawString = this.stringPool.get(templateStringValue.getRawStringPointer());
503-
return Node.newTemplateLitString(cookedString, rawString);
508+
return Node.newTemplateLitString(
509+
this.stringPool.getInternedStrings(),
510+
templateStringValue.getCookedStringPointer(),
511+
templateStringValue.getRawStringPointer());
504512
}
505513
case NEW_TARGET:
506514
return new Node(Token.NEW_TARGET);
@@ -509,7 +517,7 @@ private Node deserializeSingleNode(AstNode n) {
509517
case IMPORT_META:
510518
return new Node(Token.IMPORT_META);
511519
case OPTCHAIN_PROPERTY_ACCESS:
512-
return Node.newString(Token.OPTCHAIN_GETPROP, getString(n));
520+
return stringNode(Token.OPTCHAIN_GETPROP, n);
513521
case OPTCHAIN_CALL:
514522
return new Node(Token.OPTCHAIN_CALL);
515523
case OPTCHAIN_ELEMENT_ACCESS:
@@ -581,21 +589,21 @@ private Node deserializeSingleNode(AstNode n) {
581589
case LABELED_STATEMENT:
582590
return new Node(Token.LABEL);
583591
case LABELED_NAME:
584-
return IR.labelName(getString(n));
592+
return stringNode(Token.LABEL_NAME, n);
585593
case CLASS_MEMBERS:
586594
return new Node(Token.CLASS_MEMBERS);
587595
case METHOD_DECLARATION:
588-
return Node.newString(Token.MEMBER_FUNCTION_DEF, getString(n));
596+
return stringNode(Token.MEMBER_FUNCTION_DEF, n);
589597
case FIELD_DECLARATION:
590-
return Node.newString(Token.MEMBER_FIELD_DEF, getString(n));
598+
return stringNode(Token.MEMBER_FIELD_DEF, n);
591599
case COMPUTED_PROP_FIELD:
592600
return new Node(Token.COMPUTED_FIELD_DEF);
593601
case PARAMETER_LIST:
594602
return new Node(Token.PARAM_LIST);
595603
case RENAMABLE_STRING_KEY:
596-
return IR.stringKey(getString(n));
604+
return stringNode(Token.STRING_KEY, n);
597605
case QUOTED_STRING_KEY:
598-
Node quotedStringKey = IR.stringKey(getString(n));
606+
Node quotedStringKey = stringNode(Token.STRING_KEY, n);
599607
quotedStringKey.setQuotedStringKey();
600608
return quotedStringKey;
601609
case CASE:
@@ -617,14 +625,14 @@ private Node deserializeSingleNode(AstNode n) {
617625

618626
case RENAMABLE_GETTER_DEF:
619627
case QUOTED_GETTER_DEF:
620-
Node getterDef = Node.newString(Token.GETTER_DEF, getString(n));
628+
Node getterDef = stringNode(Token.GETTER_DEF, n);
621629
if (n.getKind().equals(NodeKind.QUOTED_GETTER_DEF)) {
622630
getterDef.setQuotedStringKey();
623631
}
624632
return getterDef;
625633
case RENAMABLE_SETTER_DEF:
626634
case QUOTED_SETTER_DEF:
627-
Node setterDef = Node.newString(Token.SETTER_DEF, getString(n));
635+
Node setterDef = stringNode(Token.SETTER_DEF, n);
628636
if (n.getKind().equals(NodeKind.QUOTED_SETTER_DEF)) {
629637
setterDef.setQuotedStringKey();
630638
}
@@ -635,7 +643,7 @@ private Node deserializeSingleNode(AstNode n) {
635643
case IMPORT_SPEC:
636644
return new Node(Token.IMPORT_SPEC);
637645
case IMPORT_STAR:
638-
return Node.newString(Token.IMPORT_STAR, getString(n));
646+
return stringNode(Token.IMPORT_STAR, n);
639647
case EXPORT_SPECS:
640648
return new Node(Token.EXPORT_SPECS);
641649
case EXPORT_SPEC:

src/com/google/javascript/jscomp/serialization/StringPool.java

+13-10
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
import static com.google.common.base.Preconditions.checkNotNull;
2020
import static com.google.common.base.Preconditions.checkState;
2121

22-
import com.google.common.collect.ImmutableList;
2322
import com.google.errorprone.annotations.CanIgnoreReturnValue;
24-
import com.google.errorprone.annotations.Immutable;
23+
import com.google.javascript.rhino.RhinoStringPool;
2524
import com.google.protobuf.ByteString;
25+
import java.util.ArrayList;
2626
import java.util.LinkedHashMap;
2727

2828
/**
@@ -34,31 +34,30 @@
3434
* <p>This implies default/unset/0-valued uuint32 StringPool pointers in protos are equivalent to
3535
* the empty string.
3636
*/
37-
@Immutable
3837
public final class StringPool {
3938

40-
private final int maxLength;
41-
private final ImmutableList<String> pool;
39+
private final int maxLength; // max length of any individual string in the pool
40+
private final RhinoStringPool.LazyInternedStringList pool;
4241

4342
public static StringPool fromProto(StringPoolProto proto) {
4443
Wtf8.Decoder decoder = Wtf8.decoder(proto.getMaxLength());
4544

46-
ImmutableList.Builder<String> pool = ImmutableList.builder();
45+
ArrayList<String> pool = new ArrayList<>();
4746
pool.add("");
4847
for (ByteString s : proto.getStringsList()) {
4948
pool.add(decoder.decode(s));
5049
}
5150

52-
return new StringPool(proto.getMaxLength(), pool.build());
51+
return new StringPool(proto.getMaxLength(), pool);
5352
}
5453

5554
public static StringPool empty() {
5655
return EMPTY;
5756
}
5857

59-
private StringPool(int maxLength, ImmutableList<String> pool) {
58+
private StringPool(int maxLength, ArrayList<String> pool) {
6059
this.maxLength = maxLength;
61-
this.pool = pool;
60+
this.pool = new RhinoStringPool.LazyInternedStringList(pool);
6261

6362
checkState(pool.get(0).isEmpty());
6463
}
@@ -67,6 +66,10 @@ public String get(int offset) {
6766
return this.pool.get(offset);
6867
}
6968

69+
public RhinoStringPool.LazyInternedStringList getInternedStrings() {
70+
return this.pool;
71+
}
72+
7073
public StringPoolProto toProto() {
7174
StringPoolProto.Builder builder = StringPoolProto.newBuilder().setMaxLength(this.maxLength);
7275
this.pool.stream().skip(1).map(Wtf8::encodeToWtf8).forEachOrdered(builder::addStrings);
@@ -104,7 +107,7 @@ public Builder putAnd(String string) {
104107
}
105108

106109
public StringPool build() {
107-
return new StringPool(this.maxLength, ImmutableList.copyOf(this.pool.keySet()));
110+
return new StringPool(this.maxLength, new ArrayList<>(this.pool.keySet()));
108111
}
109112
}
110113

src/com/google/javascript/rhino/Node.java

+42
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,11 @@ private StringNode(Token token) {
359359
setString(str);
360360
}
361361

362+
StringNode(Token token, RhinoStringPool.LazyInternedStringList stringPool, int offset) {
363+
super(token);
364+
setStringFromStringPool(stringPool, offset);
365+
}
366+
362367
@Override
363368
public boolean isEquivalentTo(
364369
Node node, boolean compareType, boolean recur, boolean jsDoc, boolean sideEffect) {
@@ -395,6 +400,16 @@ private static final class TemplateLiteralSubstringNode extends Node {
395400
this.raw = RhinoStringPool.addOrGet(raw);
396401
}
397402

403+
private TemplateLiteralSubstringNode(
404+
RhinoStringPool.LazyInternedStringList stringPool,
405+
int cookedOffsetOrNegativeOne,
406+
int rawOffset) {
407+
super(Token.TEMPLATELIT_STRING);
408+
this.cooked =
409+
(cookedOffsetOrNegativeOne == -1) ? null : stringPool.get(cookedOffsetOrNegativeOne);
410+
this.raw = stringPool.get(rawOffset);
411+
}
412+
398413
@Override
399414
public boolean isEquivalentTo(
400415
Node node, boolean compareType, boolean recur, boolean jsDoc, boolean sideEffect) {
@@ -557,10 +572,22 @@ public static Node newString(Token token, String str) {
557572
return new StringNode(token, str);
558573
}
559574

575+
public static Node newString(
576+
Token token, RhinoStringPool.LazyInternedStringList stringPool, int offset) {
577+
return new StringNode(token, stringPool, offset);
578+
}
579+
560580
public static Node newTemplateLitString(String cooked, String raw) {
561581
return new TemplateLiteralSubstringNode(cooked, raw);
562582
}
563583

584+
public static Node newTemplateLitString(
585+
RhinoStringPool.LazyInternedStringList stringPool,
586+
int cookedOffsetOrNegativeOne,
587+
int rawOffset) {
588+
return new TemplateLiteralSubstringNode(stringPool, cookedOffsetOrNegativeOne, rawOffset);
589+
}
590+
564591
public final Token getToken() {
565592
return token;
566593
}
@@ -1384,6 +1411,14 @@ public final void setString(String str) {
13841411
((StringNode) this).str = RhinoStringPool.addOrGet(str); // RhinoStringPool is null-hostile.
13851412
}
13861413

1414+
final void setStringFromStringPool(
1415+
RhinoStringPool.LazyInternedStringList stringPool, int offset) {
1416+
// RhinoStringPool.LazyInternedStringList guarantees that the result of calling get(offset) is
1417+
// interned. In some cases this is more performance than RhinoStringPool.addOrGet because the
1418+
// LazyInternedStringList caches interning results.
1419+
((StringNode) this).str = stringPool.get(offset);
1420+
}
1421+
13871422
public final String getRawString() {
13881423
return ((TemplateLiteralSubstringNode) this).raw;
13891424
}
@@ -1703,6 +1738,13 @@ public final void setOriginalName(String s) {
17031738
this.originalName = (s == null) ? null : RhinoStringPool.addOrGet(s);
17041739
}
17051740

1741+
public final void setOriginalNameFromStringPool(
1742+
RhinoStringPool.LazyInternedStringList stringPool, int offset) {
1743+
// RhinoStringPool.LazyInternedStringList guarantees that the result of calling get(offset) is
1744+
// interned, and sometimes caches results.
1745+
this.originalName = stringPool.get(offset);
1746+
}
1747+
17061748
// Copy the original
17071749
public final void setOriginalNameFromName(Node name) {
17081750
this.originalName = name.getString();

0 commit comments

Comments
 (0)