Replace usages of String.split
Bug: b/270510095
Change-Id: I7f890f8c32968c783067cfdc4369301fc9c65e0a
diff --git a/build.gradle b/build.gradle
index fee1ce4..b771939 100644
--- a/build.gradle
+++ b/build.gradle
@@ -700,14 +700,13 @@
options.errorprone.check('MultipleTopLevelClasses', CheckSeverity.ERROR)
options.errorprone.check('NarrowingCompoundAssignment', CheckSeverity.ERROR)
- // TODO(b/270534077): These should likely be fixed/suppressed and become hard failures.
+ // TODO(b/270510095): These should likely be fixed/suppressed and become hard failures.
options.errorprone.check('UnusedVariable', CheckSeverity.OFF)
options.errorprone.check('EqualsUnsafeCast', CheckSeverity.OFF)
options.errorprone.check('TypeParameterUnusedInFormals', CheckSeverity.OFF)
options.errorprone.check('LoopOverCharArray', CheckSeverity.OFF)
options.errorprone.check('ImmutableEnumChecker', CheckSeverity.OFF)
options.errorprone.check('BadImport', CheckSeverity.OFF)
- options.errorprone.check('StringSplitter', CheckSeverity.OFF)
options.errorprone.check('ComplexBooleanConstant', CheckSeverity.OFF)
options.errorprone.check('StreamToIterable', CheckSeverity.OFF)
options.errorprone.check('HidingField', CheckSeverity.OFF)
diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts
index fda414f..8334a71 100644
--- a/d8_r8/main/build.gradle.kts
+++ b/d8_r8/main/build.gradle.kts
@@ -47,14 +47,13 @@
options.errorprone.error("MultipleTopLevelClasses")
options.errorprone.error("NarrowingCompoundAssignment")
- // TODO(b/270534077): These should likely be fixed/suppressed and become hard failures.
+ // TODO(b/270510095): These should likely be fixed/suppressed and become hard failures.
options.errorprone.disable("UnusedVariable")
options.errorprone.disable("EqualsUnsafeCast")
options.errorprone.disable("TypeParameterUnusedInFormals")
options.errorprone.disable("LoopOverCharArray")
options.errorprone.disable("ImmutableEnumChecker")
options.errorprone.disable("BadImport")
- options.errorprone.disable("StringSplitter")
options.errorprone.disable("ComplexBooleanConstant")
options.errorprone.disable("StreamToIterable")
options.errorprone.disable("HidingField")
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index 0c538fa..6d5e880 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.startup.StartupProfileProvider;
import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -179,17 +180,19 @@
}
public static void parse(String content, DumpOptions.Builder builder) {
- String[] lines = content.split("\n");
- for (String line : lines) {
- String trimmed = line.trim();
- int i = trimmed.indexOf('=');
- if (i < 0) {
- throw new RuntimeException("Invalid dump line. Expected = in line: '" + trimmed + "'");
- }
- String key = trimmed.substring(0, i).trim();
- String value = trimmed.substring(i + 1).trim();
- parseKeyValue(builder, key, value);
- }
+ StringUtils.splitForEach(
+ content,
+ '\n',
+ line -> {
+ String trimmed = line.trim();
+ int i = trimmed.indexOf('=');
+ if (i < 0) {
+ throw new RuntimeException("Invalid dump line. Expected = in line: '" + trimmed + "'");
+ }
+ String key = trimmed.substring(0, i).trim();
+ String value = trimmed.substring(i + 1).trim();
+ parseKeyValue(builder, key, value);
+ });
}
private static void parseKeyValue(Builder builder, String key, String value) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
index d502339..45aa53f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateHtmlDoc.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.SupportedClass;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringUtils;
import java.io.PrintStream;
import java.nio.file.Files;
import java.util.ArrayList;
@@ -124,10 +126,10 @@
if (rewritten != null) {
return rewritten;
}
- String[] split = packageName.split("\\.");
- if (split.length > 2) {
- String prevPackage =
- packageName.substring(0, packageName.length() - split[split.length - 1].length() - 1);
+ List<String> split = StringUtils.split(packageName, '.');
+ if (split.size() > 2) {
+ String last = ListUtils.last(split);
+ String prevPackage = packageName.substring(0, packageName.length() - last.length() - 1);
return typeInPackage(typeName, prevPackage);
}
return null;
@@ -303,14 +305,14 @@
}
private String format(String s, int i) {
- String[] regexpSplit = s.split("\\.");
- if (regexpSplit.length < i) {
+ List<String> split = StringUtils.split(s, '.');
+ if (split.size() < i) {
return s;
}
int splitIndex = 0;
int mid = i / 2;
for (int j = 0; j < mid; j++) {
- splitIndex += regexpSplit[j].length();
+ splitIndex += split.get(j).length();
}
splitIndex += mid;
return s.substring(0, splitIndex) + HTML_SPLIT + s.substring(splitIndex);
@@ -340,17 +342,17 @@
return appendLiCode(s);
}
StringBuilder sb = new StringBuilder();
- String[] split = s.split("\\(");
- sb.append(split[0]).append('(').append(HTML_SPLIT);
- if (split[1].length() < MAX_LINE_CHARACTERS - 2) {
- sb.append(split[1]);
+ List<String> split = StringUtils.split(s, '(');
+ sb.append(split.get(0)).append('(').append(HTML_SPLIT);
+ if (split.get(1).length() < MAX_LINE_CHARACTERS - 2) {
+ sb.append(split.get(1));
return appendLiCode(sb.toString());
}
- String[] secondSplit = split[1].split(",");
+ List<String> secondSplit = StringUtils.split(split.get(1), ',');
sb.append(" ");
- for (int i = 0; i < secondSplit.length; i++) {
- sb.append(secondSplit[i]);
- if (i != secondSplit.length - 1) {
+ for (int i = 0; i < secondSplit.size(); i++) {
+ sb.append(secondSplit.get(i));
+ if (i != secondSplit.size() - 1) {
sb.append(',');
sb.append(HTML_SPLIT);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index c357c2a..31baed9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -14,7 +14,9 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion.LibraryValidator;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SemanticVersion;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
import java.util.List;
import java.util.Map;
@@ -251,8 +253,9 @@
if (leadingVersionNumberCache != -1) {
return leadingVersionNumberCache;
}
- String[] split = topLevelFlags.getIdentifier().split(":");
- return leadingVersionNumberCache = SemanticVersion.parse(split[split.length - 1]).getMajor();
+ List<String> split = StringUtils.split(topLevelFlags.getIdentifier(), ':');
+ String last = ListUtils.last(split);
+ return leadingVersionNumberCache = SemanticVersion.parse(last).getMajor();
}
public boolean includesJDK11Methods() {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
index 065ff92..6cf38cb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.naming.Range;
import com.android.tools.r8.utils.SegmentTree;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import java.io.BufferedReader;
import java.io.Closeable;
@@ -216,8 +217,8 @@
throws KotlinSourceDebugExtensionParserException {
// + <file_number_i> <file_name_i>
// <file_path_i>
- String[] entries = entryLine.trim().split(" ");
- if (entries.length != 3 || !entries[0].equals("+")) {
+ String[] entries = StringUtils.splitKnownSize(entryLine.trim(), ' ', 3);
+ if (entries == null || !entries[0].equals("+")) {
throw new KotlinSourceDebugExtensionParserException(
"Wrong number of entries on line " + entryLine);
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index 6e14907..3b022c7 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -34,6 +34,7 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.List;
@@ -340,15 +341,15 @@
String identifier = dexString.toString();
String typeIdentifier = null;
String memberIdentifier = null;
- String[] items = identifier.split("#");
+ List<String> items = StringUtils.split(identifier, '#');
// "x#y#z"
- if (items.length > 2) {
+ if (items.size() > 2) {
return null;
}
// "fully.qualified.ClassName#fieldOrMethodName"
- if (items.length == 2) {
- typeIdentifier = items[0];
- memberIdentifier = items[1];
+ if (items.size() == 2) {
+ typeIdentifier = items.get(0);
+ memberIdentifier = items.get(1);
} else {
int lastDot = identifier.lastIndexOf(".");
// "fully.qualified.ClassName.fieldOrMethodName"
diff --git a/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java b/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java
index 2f95428..c1817a7 100644
--- a/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java
+++ b/src/main/java/com/android/tools/r8/utils/DeterminismChecker.java
@@ -121,7 +121,7 @@
return;
}
if (method.hasCode()) {
- String[] lines = method.getCode().toString().split("\n");
+ List<String> lines = StringUtils.splitLines(method.getCode().toString());
for (String line : lines) {
if (!callback.onLine(line)) {
return;
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index b26527a..d3f4b4c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -938,9 +938,7 @@
String property = System.getProperty("com.android.tools.r8.extensiveLoggingFilter");
if (property != null) {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
- for (String method : property.split(";")) {
- builder.add(method);
- }
+ StringUtils.splitForEach(property, ';', builder::add);
return builder.build();
}
return ImmutableSet.of();
@@ -951,9 +949,7 @@
System.getProperty("com.android.tools.r8.extensiveInterfaceMethodMinifierLoggingFilter");
if (property != null) {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
- for (String method : property.split(";")) {
- builder.add(method);
- }
+ StringUtils.splitForEach(property, ';', builder::add);
return builder.build();
}
return ImmutableSet.of();
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index efe43d3..3ba639e 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -14,6 +14,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -65,6 +66,68 @@
return builder.toString();
}
+ // Utilities for splitting (and avoiding errorprone String.split).
+
+ /**
+ * Iterate over the substrings of a string split by a single char separator.
+ *
+ * <p>No special treatment of whitespace. No occurrence of the separator will appear in any
+ * split-off substring. Given N occurrences of the separator, the resulting callback will be
+ * called N+1 times.
+ */
+ public static void splitForEach(String string, char separator, Consumer<String> fn) {
+ int length = string.length();
+ int start = 0;
+ for (int i = 0; i < length; i++) {
+ char c = string.charAt(i);
+ if (c == separator) {
+ fn.accept(string.substring(start, i));
+ start = i + 1;
+ }
+ }
+ fn.accept(string.substring(start));
+ }
+
+ /**
+ * Split a string by a single char separator.
+ *
+ * <p>No special treatment of whitespace. No occurrence of the separator will appear in any
+ * split-off substring. Given N occurrences of the separator, the resulting split list will have
+ * size N+1.
+ */
+ public static List<String> split(String string, char separator) {
+ List<String> result = new ArrayList<>();
+ splitForEach(string, separator, result::add);
+ return result;
+ }
+
+ /**
+ * Split a string by a single char separator with the requirement on the split size.
+ *
+ * <p>No special treatment of whitespace. No occurrence of the separator will appear in any
+ * split-off substring. Given N occurrences of the separator, the resulting split list will have
+ * size N+1.
+ *
+ * <p>Thus for a valid split with size=N, the result will be an array of length N if and only if
+ * the input string has exactly N-1 occurrences of the separator. In any other case the return
+ * value is null.
+ */
+ public static String[] splitKnownSize(String string, char separator, int size) {
+ assert size > 1;
+ String[] result = new String[size];
+ IntBox box = new IntBox(0);
+ splitForEach(
+ string,
+ separator,
+ part -> {
+ int i = box.getAndIncrement();
+ if (i < size) {
+ result[i] = part;
+ }
+ });
+ return size == box.get() ? result : null;
+ }
+
public static boolean appendNonEmpty(
StringBuilder builder, String pre, Object item, String post) {
if (item == null) {
diff --git a/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java b/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java
index acadcc6..2237b07 100644
--- a/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java
@@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import com.android.tools.r8.TestBase;
@@ -47,6 +49,18 @@
assertListEquals(ImmutableList.of("\ra\r\rb\r"), StringUtils.splitLines("\ra\r\rb\r"));
}
+ @Test
+ public void testSplit() {
+ assertListEquals(ImmutableList.of(" "), StringUtils.split(" ", '.'));
+ assertListEquals(ImmutableList.of("", ""), StringUtils.split(".", '.'));
+ assertListEquals(ImmutableList.of("", "", "", ""), StringUtils.split("...", '.'));
+ assertListEquals(ImmutableList.of("a", "b", "c", "d"), StringUtils.split("a.b.c.d", '.'));
+
+ assertArrayEquals(new String[] {"a", "b", "c"}, StringUtils.splitKnownSize("a.b.c", '.', 3));
+ assertNull(StringUtils.splitKnownSize("a.b.c", '.', 2));
+ assertNull(StringUtils.splitKnownSize("a.b.c", '.', 4));
+ }
+
private void assertListEquals(List<String> xs, List<String> ys) {
assertEquals(
StringUtils.join(", ", xs, s -> '"' + StringUtils.toASCIIString(s) + '"', BraceType.SQUARE),