Skip to content

Commit efca121

Browse files
cushoncopybara-github
authored andcommitted
Use String#encodedLength on JDK versions that support it
See https://bugs.openjdk.org/browse/JDK-8372353 PiperOrigin-RevId: 910554784
1 parent c4e2cdf commit efca121

1 file changed

Lines changed: 88 additions & 40 deletions

File tree

  • java/core/src/main/java/com/google/protobuf

java/core/src/main/java/com/google/protobuf/Utf8.java

Lines changed: 88 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
import static java.lang.Character.isSurrogatePair;
1919
import static java.lang.Character.toCodePoint;
2020

21+
import java.lang.reflect.Method;
2122
import java.nio.BufferOverflowException;
2223
import java.nio.ByteBuffer;
24+
import java.nio.charset.Charset;
2325
import java.nio.charset.StandardCharsets;
2426
import java.util.Arrays;
2527

@@ -52,10 +54,20 @@ final class Utf8 {
5254
* depending on what is available on the platform. The processor is the platform-optimized
5355
* delegate for which all methods are delegated directly to.
5456
*/
55-
private static final Processor processor =
56-
(!Android.isOnAndroidDevice() && UnsafeProcessor.isAvailable())
57-
? new UnsafeProcessor()
58-
: new SafeProcessor();
57+
private static final Processor processor = createProcessor();
58+
59+
private static Processor createProcessor() {
60+
if (Android.isOnAndroidDevice()) {
61+
return new SafeProcessor();
62+
}
63+
if (UnsafeProcessorWithEncodedLength.isAvailable()) {
64+
return new UnsafeProcessorWithEncodedLength();
65+
}
66+
if (UnsafeProcessor.isAvailable()) {
67+
return new UnsafeProcessor();
68+
}
69+
return new SafeProcessor();
70+
}
5971

6072
/**
6173
* A mask used when performing unsafe reads to determine if a long value contains any non-ASCII
@@ -139,41 +151,7 @@ private static class UnpairedSurrogateException extends Exception {
139151
* time and space.
140152
*/
141153
static int encodedLength(String string) {
142-
// Warning to maintainers: this implementation is highly optimized.
143-
int utf16Length = string.length();
144-
int utf8Length = utf16Length;
145-
int i = 0;
146-
147-
// This loop optimizes for pure ASCII.
148-
while (i < utf16Length && string.charAt(i) < 0x80) {
149-
i++;
150-
}
151-
152-
// This loop optimizes for chars less than 0x800.
153-
for (; i < utf16Length; i++) {
154-
char c = string.charAt(i);
155-
if (c < 0x800) {
156-
utf8Length += ((0x7f - c) >>> 31); // branch free!
157-
} else {
158-
try {
159-
utf8Length += encodedLengthGeneral(string, i);
160-
} catch (UnpairedSurrogateException e) {
161-
// Our hand rolled loops don't handle unpaired surrogates here. This should be
162-
// exceptionally rare, so we fallback to the naive implementation to find out the
163-
// length that the Java internal implementation will return for this string after
164-
// replacement characters.
165-
return string.getBytes(StandardCharsets.UTF_8).length;
166-
}
167-
break;
168-
}
169-
}
170-
171-
if (utf8Length < utf16Length) {
172-
// Necessary and sufficient condition for overflow because of maximum 3x expansion
173-
throw new IllegalArgumentException(
174-
"UTF-8 length does not fit in int: " + (utf8Length + (1L << 32)));
175-
}
176-
return utf8Length;
154+
return processor.encodedLength(string);
177155
}
178156

179157
private static int encodedLengthGeneral(String string, int start)
@@ -543,6 +521,44 @@ final void encodeUtf8(String in, ByteBuffer out) {
543521

544522
/** Encodes the input character sequence to a direct {@link ByteBuffer} instance. */
545523
protected abstract void encodeUtf8Internal(String in, ByteBuffer out);
524+
525+
int encodedLength(String string) {
526+
// Warning to maintainers: this implementation is highly optimized.
527+
int utf16Length = string.length();
528+
int utf8Length = utf16Length;
529+
int i = 0;
530+
531+
// This loop optimizes for pure ASCII.
532+
while (i < utf16Length && string.charAt(i) < 0x80) {
533+
i++;
534+
}
535+
536+
// This loop optimizes for chars less than 0x800.
537+
for (; i < utf16Length; i++) {
538+
char c = string.charAt(i);
539+
if (c < 0x800) {
540+
utf8Length += ((0x7f - c) >>> 31); // branch free!
541+
} else {
542+
try {
543+
utf8Length += encodedLengthGeneral(string, i);
544+
} catch (UnpairedSurrogateException e) {
545+
// Our hand rolled loops don't handle unpaired surrogates here. This should be
546+
// exceptionally rare, so we fallback to the naive implementation to find out the
547+
// length that the Java internal implementation will return for this string after
548+
// replacement characters.
549+
return string.getBytes(StandardCharsets.UTF_8).length;
550+
}
551+
break;
552+
}
553+
}
554+
555+
if (utf8Length < utf16Length) {
556+
// Necessary and sufficient condition for overflow because of maximum 3x expansion
557+
throw new IllegalArgumentException(
558+
"UTF-8 length does not fit in int: " + (utf8Length + (1L << 32)));
559+
}
560+
return utf8Length;
561+
}
546562
}
547563

548564
/** {@link Processor} implementation that does not use any {@code sun.misc.Unsafe} methods. */
@@ -832,7 +848,7 @@ private static boolean isValidUtf8NonAscii(byte[] bytes, int index, int limit) {
832848
}
833849

834850
/** {@link Processor} that uses {@code sun.misc.Unsafe} where possible to improve performance. */
835-
static final class UnsafeProcessor extends Processor {
851+
static class UnsafeProcessor extends Processor {
836852
/** Indicates whether or not all required unsafe operations are supported on this platform. */
837853
static boolean isAvailable() {
838854
if (!hasUnsafeArrayOperations() || !hasUnsafeByteBufferOperations()) {
@@ -1206,6 +1222,38 @@ private static int unsafeEstimateConsecutiveAscii(long address, final int maxCha
12061222
}
12071223
}
12081224

1225+
/**
1226+
* {@link Processor} that extends {@link UnsafeProcessor} and uses {@code
1227+
* java.lang.String#encodedLength(Charset)} on JDK versions that support it.
1228+
*/
1229+
static final class UnsafeProcessorWithEncodedLength extends UnsafeProcessor {
1230+
1231+
private static final Method encodedLengthMethod = createEncodedLengthMethod();
1232+
1233+
private static Method createEncodedLengthMethod() {
1234+
try {
1235+
// This method was added in JDK 27 in https://bugs.openjdk.org/browse/JDK-8372353
1236+
// Use reflection to allow compiling against JDK and AOSP versions that don't support it
1237+
return String.class.getMethod("encodedLength", Charset.class);
1238+
} catch (ReflectiveOperationException e) {
1239+
return null;
1240+
}
1241+
}
1242+
1243+
static boolean isAvailable() {
1244+
return encodedLengthMethod != null && UnsafeProcessor.isAvailable();
1245+
}
1246+
1247+
@Override
1248+
int encodedLength(String in) {
1249+
try {
1250+
return (int) encodedLengthMethod.invoke(in, StandardCharsets.UTF_8);
1251+
} catch (ReflectiveOperationException e) {
1252+
throw new IllegalStateException(e);
1253+
}
1254+
}
1255+
}
1256+
12091257
/**
12101258
* Utility methods for decoding bytes into {@link String}. Callers are responsible for extracting
12111259
* bytes (possibly using Unsafe methods), and checking remaining bytes. All other UTF-8 validity

0 commit comments

Comments
 (0)