diff options
| author | 2022-10-25 00:23:38 +0000 | |
|---|---|---|
| committer | 2022-10-25 00:23:38 +0000 | |
| commit | f8a4152cd96d92e1511e6a1d1724fa243d66a12c (patch) | |
| tree | 1382e1d9c8101e2d086dcfc0c8fe923f82c3c21c | |
| parent | e363a3fea1791891bc8266f9596a53aa342b17fd (diff) | |
| parent | b90e6818c32504d925ee2d95252a7d212f6e7639 (diff) | |
Merge "Extract four byte sequence modified UTF-8 into separate classes."
12 files changed, 358 insertions, 202 deletions
diff --git a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java index 76656bd67afa..b43ec10e1fc5 100644 --- a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java +++ b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java @@ -68,7 +68,7 @@ public class FastDataPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { os.reset(); - final FastDataOutput out = FastDataOutput.obtainUsing4ByteSequences(os); + final FastDataOutput out = ArtFastDataOutput.obtain(os); try { doWrite(out); out.flush(); @@ -84,7 +84,7 @@ public class FastDataPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { os.reset(); - final FastDataOutput out = FastDataOutput.obtainUsing3ByteSequences(os); + final FastDataOutput out = FastDataOutput.obtain(os); try { doWrite(out); out.flush(); @@ -116,7 +116,7 @@ public class FastDataPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { is.reset(); - final FastDataInput in = FastDataInput.obtainUsing4ByteSequences(is); + final FastDataInput in = ArtFastDataInput.obtain(is); try { doRead(in); } finally { @@ -131,7 +131,7 @@ public class FastDataPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { is.reset(); - final FastDataInput in = FastDataInput.obtainUsing3ByteSequences(is); + final FastDataInput in = FastDataInput.obtain(is); try { doRead(in); } finally { diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java index 76ee097c8c93..f8e25584c419 100644 --- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java +++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java @@ -37,7 +37,7 @@ import android.os.Environment; import android.util.AtomicFile; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.FastDataInput; +import com.android.internal.util.ArtFastDataInput; import libcore.io.IoUtils; @@ -254,7 +254,7 @@ public class NetworkStatsDataMigrationUtils { private static void readPlatformCollection(@NonNull NetworkStatsCollection.Builder builder, @NonNull File file) throws IOException { final FileInputStream is = new FileInputStream(file); - final FastDataInput dataIn = new FastDataInput(is, BUFFER_SIZE); + final ArtFastDataInput dataIn = new ArtFastDataInput(is, BUFFER_SIZE); try { readPlatformCollection(builder, dataIn); } finally { diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java index 38decf9951a7..242806db5344 100644 --- a/core/java/android/util/Xml.java +++ b/core/java/android/util/Xml.java @@ -22,7 +22,8 @@ import android.os.SystemProperties; import android.system.ErrnoException; import android.system.Os; -import com.android.internal.util.BinaryXmlPullParser; +import com.android.internal.util.ArtBinaryXmlPullParser; +import com.android.internal.util.ArtBinaryXmlSerializer; import com.android.internal.util.BinaryXmlSerializer; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; @@ -146,7 +147,7 @@ public class Xml { * @hide */ public static @NonNull TypedXmlPullParser newBinaryPullParser() { - return new BinaryXmlPullParser(); + return new ArtBinaryXmlPullParser(); } /** @@ -225,7 +226,7 @@ public class Xml { * @hide */ public static @NonNull TypedXmlSerializer newBinarySerializer() { - return new BinaryXmlSerializer(); + return new ArtBinaryXmlSerializer(); } /** diff --git a/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java b/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java new file mode 100644 index 000000000000..5de69a6310b9 --- /dev/null +++ b/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.annotation.NonNull; + +import java.io.DataInput; +import java.io.InputStream; + +/** + * {@inheritDoc} + * <p> + * This decodes large code-points using 4-byte sequences, and <em>is not</em> compatible with the + * {@link DataInput} API contract, which specifies that large code-points must be encoded with + * 3-byte sequences. + */ +public class ArtBinaryXmlPullParser extends BinaryXmlPullParser { + @NonNull + protected FastDataInput obtainFastDataInput(@NonNull InputStream is) { + return ArtFastDataInput.obtain(is); + } +} diff --git a/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java b/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java new file mode 100644 index 000000000000..cd534c96ca9d --- /dev/null +++ b/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.annotation.NonNull; + +import java.io.DataOutput; +import java.io.OutputStream; + +/** + * {@inheritDoc} + * <p> + * This encodes large code-points using 4-byte sequences and <em>is not</em> compatible with the + * {@link DataOutput} API contract, which specifies that large code-points must be encoded with + * 3-byte sequences. + */ +public class ArtBinaryXmlSerializer extends BinaryXmlSerializer { + @NonNull + @Override + protected FastDataOutput obtainFastDataOutput(@NonNull OutputStream os) { + return ArtFastDataOutput.obtain(os); + } +} diff --git a/core/java/com/android/internal/util/ArtFastDataInput.java b/core/java/com/android/internal/util/ArtFastDataInput.java new file mode 100644 index 000000000000..25490215c146 --- /dev/null +++ b/core/java/com/android/internal/util/ArtFastDataInput.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.annotation.NonNull; +import android.util.CharsetUtils; + +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicReference; + +/** + * {@inheritDoc} + * <p> + * This decodes large code-points using 4-byte sequences, and <em>is not</em> compatible with the + * {@link DataInput} API contract, which specifies that large code-points must be encoded with + * 3-byte sequences. + */ +public class ArtFastDataInput extends FastDataInput { + private static AtomicReference<ArtFastDataInput> sInCache = new AtomicReference<>(); + + private final long mBufferPtr; + + public ArtFastDataInput(@NonNull InputStream in, int bufferSize) { + super(in, bufferSize); + + mBufferPtr = mRuntime.addressOf(mBuffer); + } + + /** + * Obtain a {@link ArtFastDataInput} configured with the given + * {@link InputStream} and which decodes large code-points using 4-byte + * sequences. + * <p> + * This <em>is not</em> compatible with the {@link DataInput} API contract, + * which specifies that large code-points must be encoded with 3-byte + * sequences. + */ + public static ArtFastDataInput obtain(@NonNull InputStream in) { + ArtFastDataInput instance = sInCache.getAndSet(null); + if (instance != null) { + instance.setInput(in); + return instance; + } + return new ArtFastDataInput(in, DEFAULT_BUFFER_SIZE); + } + + /** + * Release a {@link ArtFastDataInput} to potentially be recycled. You must not + * interact with the object after releasing it. + */ + public void release() { + super.release(); + + if (mBufferCap == DEFAULT_BUFFER_SIZE) { + // Try to return to the cache. + sInCache.compareAndSet(null, this); + } + } + + @Override + public String readUTF() throws IOException { + // Attempt to read directly from buffer space if there's enough room, + // otherwise fall back to chunking into place + final int len = readUnsignedShort(); + if (mBufferCap > len) { + if (mBufferLim - mBufferPos < len) fill(len); + final String res = CharsetUtils.fromModifiedUtf8Bytes(mBufferPtr, mBufferPos, len); + mBufferPos += len; + return res; + } else { + final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1); + readFully(tmp, 0, len); + return CharsetUtils.fromModifiedUtf8Bytes(mRuntime.addressOf(tmp), 0, len); + } + } +} diff --git a/core/java/com/android/internal/util/ArtFastDataOutput.java b/core/java/com/android/internal/util/ArtFastDataOutput.java new file mode 100644 index 000000000000..de9e93bea7f1 --- /dev/null +++ b/core/java/com/android/internal/util/ArtFastDataOutput.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import android.annotation.NonNull; +import android.util.CharsetUtils; + +import java.io.DataOutput; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicReference; + +/** + * {@inheritDoc} + * <p> + * This encodes large code-points using 4-byte sequences and <em>is not</em> compatible with the + * {@link DataOutput} API contract, which specifies that large code-points must be encoded with + * 3-byte sequences. + */ +public class ArtFastDataOutput extends FastDataOutput { + private static AtomicReference<ArtFastDataOutput> sOutCache = new AtomicReference<>(); + + private final long mBufferPtr; + + public ArtFastDataOutput(@NonNull OutputStream out, int bufferSize) { + super(out, bufferSize); + + mBufferPtr = mRuntime.addressOf(mBuffer); + } + + /** + * Obtain an {@link ArtFastDataOutput} configured with the given + * {@link OutputStream} and which encodes large code-points using 4-byte + * sequences. + * <p> + * This <em>is not</em> compatible with the {@link DataOutput} API contract, + * which specifies that large code-points must be encoded with 3-byte + * sequences. + */ + public static ArtFastDataOutput obtain(@NonNull OutputStream out) { + ArtFastDataOutput instance = sOutCache.getAndSet(null); + if (instance != null) { + instance.setOutput(out); + return instance; + } + return new ArtFastDataOutput(out, DEFAULT_BUFFER_SIZE); + } + + @Override + public void release() { + super.release(); + + if (mBufferCap == DEFAULT_BUFFER_SIZE) { + // Try to return to the cache. + sOutCache.compareAndSet(null, this); + } + } + + @Override + public void writeUTF(String s) throws IOException { + // Attempt to write directly to buffer space if there's enough room, + // otherwise fall back to chunking into place + if (mBufferCap - mBufferPos < 2 + s.length()) drain(); + + // Magnitude of this returned value indicates the number of bytes + // required to encode the string; sign indicates success/failure + int len = CharsetUtils.toModifiedUtf8Bytes(s, mBufferPtr, mBufferPos + 2, mBufferCap); + if (Math.abs(len) > MAX_UNSIGNED_SHORT) { + throw new IOException("Modified UTF-8 length too large: " + len); + } + + if (len >= 0) { + // Positive value indicates the string was encoded into the buffer + // successfully, so we only need to prefix with length + writeShort(len); + mBufferPos += len; + } else { + // Negative value indicates buffer was too small and we need to + // allocate a temporary buffer for encoding + len = -len; + final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1); + CharsetUtils.toModifiedUtf8Bytes(s, mRuntime.addressOf(tmp), 0, tmp.length); + writeShort(len); + write(tmp, 0, len); + } + } +} diff --git a/core/java/com/android/internal/util/BinaryXmlPullParser.java b/core/java/com/android/internal/util/BinaryXmlPullParser.java index d3abac9f8877..f2e61be94d60 100644 --- a/core/java/com/android/internal/util/BinaryXmlPullParser.java +++ b/core/java/com/android/internal/util/BinaryXmlPullParser.java @@ -72,7 +72,7 @@ import java.util.Objects; * <li>Namespaces, prefixes, properties, and options are unsupported. * </ul> */ -public final class BinaryXmlPullParser implements TypedXmlPullParser { +public class BinaryXmlPullParser implements TypedXmlPullParser { private FastDataInput mIn; private int mCurrentToken = START_DOCUMENT; @@ -99,7 +99,7 @@ public final class BinaryXmlPullParser implements TypedXmlPullParser { mIn = null; } - mIn = FastDataInput.obtainUsing4ByteSequences(is); + mIn = obtainFastDataInput(is); mCurrentToken = START_DOCUMENT; mCurrentDepth = 0; @@ -129,6 +129,11 @@ public final class BinaryXmlPullParser implements TypedXmlPullParser { } } + @NonNull + protected FastDataInput obtainFastDataInput(@NonNull InputStream is) { + return FastDataInput.obtain(is); + } + @Override public void setInput(Reader in) throws XmlPullParserException { throw new UnsupportedOperationException(); diff --git a/core/java/com/android/internal/util/BinaryXmlSerializer.java b/core/java/com/android/internal/util/BinaryXmlSerializer.java index 485430a43768..a9230e67c6fa 100644 --- a/core/java/com/android/internal/util/BinaryXmlSerializer.java +++ b/core/java/com/android/internal/util/BinaryXmlSerializer.java @@ -63,7 +63,7 @@ import java.util.Arrays; * <li>Namespaces, prefixes, properties, and options are unsupported. * </ul> */ -public final class BinaryXmlSerializer implements TypedXmlSerializer { +public class BinaryXmlSerializer implements TypedXmlSerializer { /** * The wire protocol always begins with a well-known magic value of * {@code ABX_}, representing "Android Binary XML." The final byte is a @@ -118,13 +118,18 @@ public final class BinaryXmlSerializer implements TypedXmlSerializer { throw new UnsupportedOperationException(); } - mOut = FastDataOutput.obtainUsing4ByteSequences(os); + mOut = obtainFastDataOutput(os); mOut.write(PROTOCOL_MAGIC_VERSION_0); mTagCount = 0; mTagNames = new String[8]; } + @NonNull + protected FastDataOutput obtainFastDataOutput(@NonNull OutputStream os) { + return FastDataOutput.obtain(os); + } + @Override public void setOutput(Writer writer) { throw new UnsupportedOperationException(); diff --git a/core/java/com/android/internal/util/FastDataInput.java b/core/java/com/android/internal/util/FastDataInput.java index 5117034815fc..ed6697deb6af 100644 --- a/core/java/com/android/internal/util/FastDataInput.java +++ b/core/java/com/android/internal/util/FastDataInput.java @@ -17,7 +17,6 @@ package com.android.internal.util; import android.annotation.NonNull; -import android.util.CharsetUtils; import dalvik.system.VMRuntime; @@ -30,7 +29,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; /** * Optimized implementation of {@link DataInput} which buffers data in memory @@ -40,22 +38,18 @@ import java.util.concurrent.atomic.AtomicReference; * {@link DataInputStream} with a {@link BufferedInputStream}. */ public class FastDataInput implements DataInput, Closeable { - private static final int MAX_UNSIGNED_SHORT = 65_535; + protected static final int MAX_UNSIGNED_SHORT = 65_535; - private static final int DEFAULT_BUFFER_SIZE = 32_768; + protected static final int DEFAULT_BUFFER_SIZE = 32_768; - private static AtomicReference<FastDataInput> sInCache = new AtomicReference<>(); + protected final VMRuntime mRuntime; - private final VMRuntime mRuntime; - - private final byte[] mBuffer; - private final long mBufferPtr; - private final int mBufferCap; - private final boolean mUse4ByteSequence; + protected final byte[] mBuffer; + protected final int mBufferCap; private InputStream mIn; - private int mBufferPos; - private int mBufferLim; + protected int mBufferPos; + protected int mBufferLim; /** * Values that have been "interned" by {@link #readInternedUTF()}. @@ -63,18 +57,7 @@ public class FastDataInput implements DataInput, Closeable { private int mStringRefCount = 0; private String[] mStringRefs = new String[32]; - /** - * @deprecated callers must specify {@code use4ByteSequence} so they make a - * clear choice about working around a long-standing ART bug, as - * described by the {@code kUtfUse4ByteSequence} comments in - * {@code art/runtime/jni/jni_internal.cc}. - */ - @Deprecated public FastDataInput(@NonNull InputStream in, int bufferSize) { - this(in, bufferSize, true /* use4ByteSequence */); - } - - public FastDataInput(@NonNull InputStream in, int bufferSize, boolean use4ByteSequence) { mRuntime = VMRuntime.getRuntime(); mIn = Objects.requireNonNull(in); if (bufferSize < 8) { @@ -82,9 +65,7 @@ public class FastDataInput implements DataInput, Closeable { } mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize); - mBufferPtr = mRuntime.addressOf(mBuffer); mBufferCap = mBuffer.length; - mUse4ByteSequence = use4ByteSequence; } /** @@ -96,26 +77,8 @@ public class FastDataInput implements DataInput, Closeable { * which specifies that large code-points must be encoded with 3-byte * sequences. */ - public static FastDataInput obtainUsing3ByteSequences(@NonNull InputStream in) { - return new FastDataInput(in, DEFAULT_BUFFER_SIZE, false /* use4ByteSequence */); - } - - /** - * Obtain a {@link FastDataInput} configured with the given - * {@link InputStream} and which decodes large code-points using 4-byte - * sequences. - * <p> - * This <em>is not</em> compatible with the {@link DataInput} API contract, - * which specifies that large code-points must be encoded with 3-byte - * sequences. - */ - public static FastDataInput obtainUsing4ByteSequences(@NonNull InputStream in) { - FastDataInput instance = sInCache.getAndSet(null); - if (instance != null) { - instance.setInput(in); - return instance; - } - return new FastDataInput(in, DEFAULT_BUFFER_SIZE, true /* use4ByteSequence */); + public static FastDataInput obtain(@NonNull InputStream in) { + return new FastDataInput(in, DEFAULT_BUFFER_SIZE); } /** @@ -127,24 +90,23 @@ public class FastDataInput implements DataInput, Closeable { mBufferPos = 0; mBufferLim = 0; mStringRefCount = 0; - - if (mBufferCap == DEFAULT_BUFFER_SIZE && mUse4ByteSequence) { - // Try to return to the cache. - sInCache.compareAndSet(null, this); - } } /** * Re-initializes the object for the new input. */ - private void setInput(@NonNull InputStream in) { + protected void setInput(@NonNull InputStream in) { + if (mIn != null) { + throw new IllegalStateException("setInput() called before calling release()"); + } + mIn = Objects.requireNonNull(in); mBufferPos = 0; mBufferLim = 0; mStringRefCount = 0; } - private void fill(int need) throws IOException { + protected void fill(int need) throws IOException { final int remain = mBufferLim - mBufferPos; System.arraycopy(mBuffer, mBufferPos, mBuffer, 0, remain); mBufferPos = 0; @@ -202,30 +164,6 @@ public class FastDataInput implements DataInput, Closeable { @Override public String readUTF() throws IOException { - if (mUse4ByteSequence) { - return readUTFUsing4ByteSequences(); - } else { - return readUTFUsing3ByteSequences(); - } - } - - private String readUTFUsing4ByteSequences() throws IOException { - // Attempt to read directly from buffer space if there's enough room, - // otherwise fall back to chunking into place - final int len = readUnsignedShort(); - if (mBufferCap > len) { - if (mBufferLim - mBufferPos < len) fill(len); - final String res = CharsetUtils.fromModifiedUtf8Bytes(mBufferPtr, mBufferPos, len); - mBufferPos += len; - return res; - } else { - final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1); - readFully(tmp, 0, len); - return CharsetUtils.fromModifiedUtf8Bytes(mRuntime.addressOf(tmp), 0, len); - } - } - - private String readUTFUsing3ByteSequences() throws IOException { // Attempt to read directly from buffer space if there's enough room, // otherwise fall back to chunking into place final int len = readUnsignedShort(); diff --git a/core/java/com/android/internal/util/FastDataOutput.java b/core/java/com/android/internal/util/FastDataOutput.java index 5b6075ec54ce..e394c7449801 100644 --- a/core/java/com/android/internal/util/FastDataOutput.java +++ b/core/java/com/android/internal/util/FastDataOutput.java @@ -17,7 +17,6 @@ package com.android.internal.util; import android.annotation.NonNull; -import android.util.CharsetUtils; import dalvik.system.VMRuntime; @@ -30,7 +29,6 @@ import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; /** * Optimized implementation of {@link DataOutput} which buffers data in memory @@ -40,48 +38,31 @@ import java.util.concurrent.atomic.AtomicReference; * {@link DataOutputStream} with a {@link BufferedOutputStream}. */ public class FastDataOutput implements DataOutput, Flushable, Closeable { - private static final int MAX_UNSIGNED_SHORT = 65_535; + protected static final int MAX_UNSIGNED_SHORT = 65_535; - private static final int DEFAULT_BUFFER_SIZE = 32_768; + protected static final int DEFAULT_BUFFER_SIZE = 32_768; - private static AtomicReference<FastDataOutput> sOutCache = new AtomicReference<>(); + protected final VMRuntime mRuntime; - private final VMRuntime mRuntime; - - private final byte[] mBuffer; - private final long mBufferPtr; - private final int mBufferCap; - private final boolean mUse4ByteSequence; + protected final byte[] mBuffer; + protected final int mBufferCap; private OutputStream mOut; - private int mBufferPos; + protected int mBufferPos; /** * Values that have been "interned" by {@link #writeInternedUTF(String)}. */ private final HashMap<String, Integer> mStringRefs = new HashMap<>(); - /** - * @deprecated callers must specify {@code use4ByteSequence} so they make a - * clear choice about working around a long-standing ART bug, as - * described by the {@code kUtfUse4ByteSequence} comments in - * {@code art/runtime/jni/jni_internal.cc}. - */ - @Deprecated public FastDataOutput(@NonNull OutputStream out, int bufferSize) { - this(out, bufferSize, true /* use4ByteSequence */); - } - - public FastDataOutput(@NonNull OutputStream out, int bufferSize, boolean use4ByteSequence) { mRuntime = VMRuntime.getRuntime(); if (bufferSize < 8) { throw new IllegalArgumentException(); } mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize); - mBufferPtr = mRuntime.addressOf(mBuffer); mBufferCap = mBuffer.length; - mUse4ByteSequence = use4ByteSequence; setOutput(out); } @@ -95,26 +76,8 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable { * which specifies that large code-points must be encoded with 3-byte * sequences. */ - public static FastDataOutput obtainUsing3ByteSequences(@NonNull OutputStream out) { - return new FastDataOutput(out, DEFAULT_BUFFER_SIZE, false /* use4ByteSequence */); - } - - /** - * Obtain a {@link FastDataOutput} configured with the given - * {@link OutputStream} and which encodes large code-points using 4-byte - * sequences. - * <p> - * This <em>is not</em> compatible with the {@link DataOutput} API contract, - * which specifies that large code-points must be encoded with 3-byte - * sequences. - */ - public static FastDataOutput obtainUsing4ByteSequences(@NonNull OutputStream out) { - FastDataOutput instance = sOutCache.getAndSet(null); - if (instance != null) { - instance.setOutput(out); - return instance; - } - return new FastDataOutput(out, DEFAULT_BUFFER_SIZE, true /* use4ByteSequence */); + public static FastDataOutput obtain(@NonNull OutputStream out) { + return new FastDataOutput(out, DEFAULT_BUFFER_SIZE); } /** @@ -129,23 +92,22 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable { mOut = null; mBufferPos = 0; mStringRefs.clear(); - - if (mBufferCap == DEFAULT_BUFFER_SIZE && mUse4ByteSequence) { - // Try to return to the cache. - sOutCache.compareAndSet(null, this); - } } /** * Re-initializes the object for the new output. */ - private void setOutput(@NonNull OutputStream out) { + protected void setOutput(@NonNull OutputStream out) { + if (mOut != null) { + throw new IllegalStateException("setOutput() called before calling release()"); + } + mOut = Objects.requireNonNull(out); mBufferPos = 0; mStringRefs.clear(); } - private void drain() throws IOException { + protected void drain() throws IOException { if (mBufferPos > 0) { mOut.write(mBuffer, 0, mBufferPos); mBufferPos = 0; @@ -188,42 +150,6 @@ public class FastDataOutput implements DataOutput, Flushable, Closeable { @Override public void writeUTF(String s) throws IOException { - if (mUse4ByteSequence) { - writeUTFUsing4ByteSequences(s); - } else { - writeUTFUsing3ByteSequences(s); - } - } - - private void writeUTFUsing4ByteSequences(String s) throws IOException { - // Attempt to write directly to buffer space if there's enough room, - // otherwise fall back to chunking into place - if (mBufferCap - mBufferPos < 2 + s.length()) drain(); - - // Magnitude of this returned value indicates the number of bytes - // required to encode the string; sign indicates success/failure - int len = CharsetUtils.toModifiedUtf8Bytes(s, mBufferPtr, mBufferPos + 2, mBufferCap); - if (Math.abs(len) > MAX_UNSIGNED_SHORT) { - throw new IOException("Modified UTF-8 length too large: " + len); - } - - if (len >= 0) { - // Positive value indicates the string was encoded into the buffer - // successfully, so we only need to prefix with length - writeShort(len); - mBufferPos += len; - } else { - // Negative value indicates buffer was too small and we need to - // allocate a temporary buffer for encoding - len = -len; - final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1); - CharsetUtils.toModifiedUtf8Bytes(s, mRuntime.addressOf(tmp), 0, tmp.length); - writeShort(len); - write(tmp, 0, len); - } - } - - private void writeUTFUsing3ByteSequences(String s) throws IOException { final int len = (int) ModifiedUtf8.countBytes(s, false); if (len > MAX_UNSIGNED_SHORT) { throw new IOException("Modified UTF-8 length too large: " + len); diff --git a/core/tests/coretests/src/com/android/internal/util/FastDataTest.java b/core/tests/coretests/src/com/android/internal/util/FastDataTest.java index 04dfd6ee30e2..512ece38a229 100644 --- a/core/tests/coretests/src/com/android/internal/util/FastDataTest.java +++ b/core/tests/coretests/src/com/android/internal/util/FastDataTest.java @@ -39,6 +39,8 @@ import java.io.DataOutput; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; @@ -61,14 +63,32 @@ public class FastDataTest { this.use4ByteSequence = use4ByteSequence; } + @NonNull + private FastDataInput createFastDataInput(@NonNull InputStream in, int bufferSize) { + if (use4ByteSequence) { + return new ArtFastDataInput(in, bufferSize); + } else { + return new FastDataInput(in, bufferSize); + } + } + + @NonNull + private FastDataOutput createFastDataOutput(@NonNull OutputStream out, int bufferSize) { + if (use4ByteSequence) { + return new ArtFastDataOutput(out, bufferSize); + } else { + return new FastDataOutput(out, bufferSize); + } + } + @Test public void testEndOfFile_Int() throws Exception { - try (FastDataInput in = new FastDataInput(new ByteArrayInputStream( - new byte[] { 1 }), 1000, use4ByteSequence)) { + try (FastDataInput in = createFastDataInput(new ByteArrayInputStream( + new byte[] { 1 }), 1000)) { assertThrows(EOFException.class, () -> in.readInt()); } - try (FastDataInput in = new FastDataInput(new ByteArrayInputStream( - new byte[] { 1, 1, 1, 1 }), 1000, use4ByteSequence)) { + try (FastDataInput in = createFastDataInput(new ByteArrayInputStream( + new byte[] { 1, 1, 1, 1 }), 1000)) { assertEquals(1, in.readByte()); assertThrows(EOFException.class, () -> in.readInt()); } @@ -76,25 +96,25 @@ public class FastDataTest { @Test public void testEndOfFile_String() throws Exception { - try (FastDataInput in = new FastDataInput(new ByteArrayInputStream( - new byte[] { 1 }), 1000, use4ByteSequence)) { + try (FastDataInput in = createFastDataInput(new ByteArrayInputStream( + new byte[] { 1 }), 1000)) { assertThrows(EOFException.class, () -> in.readUTF()); } - try (FastDataInput in = new FastDataInput(new ByteArrayInputStream( - new byte[] { 1, 1, 1, 1 }), 1000, use4ByteSequence)) { + try (FastDataInput in = createFastDataInput(new ByteArrayInputStream( + new byte[] { 1, 1, 1, 1 }), 1000)) { assertThrows(EOFException.class, () -> in.readUTF()); } } @Test public void testEndOfFile_Bytes_Small() throws Exception { - try (FastDataInput in = new FastDataInput(new ByteArrayInputStream( - new byte[] { 1, 1, 1, 1 }), 1000, use4ByteSequence)) { + try (FastDataInput in = createFastDataInput(new ByteArrayInputStream( + new byte[] { 1, 1, 1, 1 }), 1000)) { final byte[] tmp = new byte[10]; assertThrows(EOFException.class, () -> in.readFully(tmp)); } - try (FastDataInput in = new FastDataInput(new ByteArrayInputStream( - new byte[] { 1, 1, 1, 1 }), 1000, use4ByteSequence)) { + try (FastDataInput in = createFastDataInput(new ByteArrayInputStream( + new byte[] { 1, 1, 1, 1 }), 1000)) { final byte[] tmp = new byte[10_000]; assertThrows(EOFException.class, () -> in.readFully(tmp)); } @@ -103,8 +123,7 @@ public class FastDataTest { @Test public void testUTF_Bounds() throws Exception { final char[] buf = new char[65_534]; - try (FastDataOutput out = new FastDataOutput(new ByteArrayOutputStream(), - BOUNCE_SIZE, use4ByteSequence)) { + try (FastDataOutput out = createFastDataOutput(new ByteArrayOutputStream(), BOUNCE_SIZE)) { // Writing simple string will fit fine Arrays.fill(buf, '!'); final String simple = new String(buf); @@ -132,17 +151,15 @@ public class FastDataTest { doTranscodeWrite(out); out.flush(); - final FastDataInput in = new FastDataInput( - new ByteArrayInputStream(outStream.toByteArray()), - BOUNCE_SIZE, use4ByteSequence); + final FastDataInput in = createFastDataInput( + new ByteArrayInputStream(outStream.toByteArray()), BOUNCE_SIZE); doTranscodeRead(in); } // Verify that fast data can be read by upstream { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - final FastDataOutput out = new FastDataOutput(outStream, - BOUNCE_SIZE, use4ByteSequence); + final FastDataOutput out = createFastDataOutput(outStream, BOUNCE_SIZE); doTranscodeWrite(out); out.flush(); @@ -299,7 +316,7 @@ public class FastDataTest { final DataOutput slowData = new DataOutputStream(slowStream); final ByteArrayOutputStream fastStream = new ByteArrayOutputStream(); - final FastDataOutput fastData = FastDataOutput.obtainUsing3ByteSequences(fastStream); + final FastDataOutput fastData = FastDataOutput.obtain(fastStream); for (int cp = Character.MIN_CODE_POINT; cp < Character.MAX_CODE_POINT; cp++) { if (Character.isValidCodePoint(cp)) { @@ -416,16 +433,14 @@ public class FastDataTest { private void doBounce(@NonNull ThrowingConsumer<FastDataOutput> out, @NonNull ThrowingConsumer<FastDataInput> in, int count) throws Exception { final ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - final FastDataOutput outData = new FastDataOutput(outStream, - BOUNCE_SIZE, use4ByteSequence); + final FastDataOutput outData = createFastDataOutput(outStream, BOUNCE_SIZE); for (int i = 0; i < count; i++) { out.accept(outData); } outData.flush(); final ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray()); - final FastDataInput inData = new FastDataInput(inStream, - BOUNCE_SIZE, use4ByteSequence); + final FastDataInput inData = createFastDataInput(inStream, BOUNCE_SIZE); for (int i = 0; i < count; i++) { in.accept(inData); } |