diff options
author | Jeff Davidson <jpd@google.com> | 2014-09-15 16:29:06 -0700 |
---|---|---|
committer | Jeff Davidson <jpd@google.com> | 2015-01-15 14:10:53 -0800 |
commit | a3b2a6da25a76f17c73d31def3952feb0fd2296e (patch) | |
tree | 586f7d5e9a7e05af45d0e821188097c0faa96219 /java/src/main/java/com/google/protobuf/CodedInputStream.java | |
parent | c7c25812eb19d080087b71e08bfe35aff9f21433 (diff) | |
download | external_protobuf-a3b2a6da25a76f17c73d31def3952feb0fd2296e.zip external_protobuf-a3b2a6da25a76f17c73d31def3952feb0fd2296e.tar.gz external_protobuf-a3b2a6da25a76f17c73d31def3952feb0fd2296e.tar.bz2 |
Update protobuf library from 2.3 to 2.6.
Copied in all files from the open source protobuf project at commit
edc5994525c79cd1919859a370837a6ff7c8e308, removing files which have
been renamed (COPYING.txt -> LICENSE, README.txt -> README.md).
Removed 2.3 prebuilts, which is an approach that will not work due to
incompatibility with the 2.6 runtime.
Merged in micro/nano-specific changes in the following files:
-Android.mk - updated list of C++/Java sources, bumped versions
-java/README.txt - merged in micro/nano instructions, bumped versions
-java/pom.xml - merged in micro/nano build rules, set packaging to jar
-src/Makefile.am - merged in references to micro/nano generators
-src/google/protobuf/compiler/javamicro/javamicro_file.h - imported
google/protobuf/compiler/code_generator.h and removed redundant
OutputDirectory class.
-src/google/protobuf/compiler/javanano/javanano_file.h - same
-Replaced instances of vector with std::vector as needed to get
libprotobuf-cpp-full to compile. Plan to upstream this fix per
discussion with protobuf maintainers.
Reran autogen.sh to update ./configure and associated scripts.
Change-Id: I949d32fb5126f1c05e2a6ed48f6636a4a9b15a48
Diffstat (limited to 'java/src/main/java/com/google/protobuf/CodedInputStream.java')
-rw-r--r-- | java/src/main/java/com/google/protobuf/CodedInputStream.java | 728 |
1 files changed, 587 insertions, 141 deletions
diff --git a/java/src/main/java/com/google/protobuf/CodedInputStream.java b/java/src/main/java/com/google/protobuf/CodedInputStream.java index 22995e9..b25b971 100644 --- a/java/src/main/java/com/google/protobuf/CodedInputStream.java +++ b/java/src/main/java/com/google/protobuf/CodedInputStream.java @@ -30,9 +30,12 @@ package com.google.protobuf; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -67,7 +70,72 @@ public final class CodedInputStream { */ public static CodedInputStream newInstance(final byte[] buf, final int off, final int len) { - return new CodedInputStream(buf, off, len); + CodedInputStream result = new CodedInputStream(buf, off, len); + try { + // Some uses of CodedInputStream can be more efficient if they know + // exactly how many bytes are available. By pushing the end point of the + // buffer as a limit, we allow them to get this information via + // getBytesUntilLimit(). Pushing a limit that we know is at the end of + // the stream can never hurt, since we can never past that point anyway. + result.pushLimit(len); + } catch (InvalidProtocolBufferException ex) { + // The only reason pushLimit() might throw an exception here is if len + // is negative. Normally pushLimit()'s parameter comes directly off the + // wire, so it's important to catch exceptions in case of corrupt or + // malicious data. However, in this case, we expect that len is not a + // user-supplied value, so we can assume that it being negative indicates + // a programming error. Therefore, throwing an unchecked exception is + // appropriate. + throw new IllegalArgumentException(ex); + } + return result; + } + + /** + * Create a new CodedInputStream wrapping the given ByteBuffer. The data + * starting from the ByteBuffer's current position to its limit will be read. + * The returned CodedInputStream may or may not share the underlying data + * in the ByteBuffer, therefore the ByteBuffer cannot be changed while the + * CodedInputStream is in use. + * Note that the ByteBuffer's position won't be changed by this function. + * Concurrent calls with the same ByteBuffer object are safe if no other + * thread is trying to alter the ByteBuffer's status. + */ + public static CodedInputStream newInstance(ByteBuffer buf) { + if (buf.hasArray()) { + return newInstance(buf.array(), buf.arrayOffset() + buf.position(), + buf.remaining()); + } else { + ByteBuffer temp = buf.duplicate(); + byte[] buffer = new byte[temp.remaining()]; + temp.get(buffer); + return newInstance(buffer); + } + } + + /** + * Create a new CodedInputStream wrapping a LiteralByteString. + */ + static CodedInputStream newInstance(LiteralByteString byteString) { + CodedInputStream result = new CodedInputStream(byteString); + try { + // Some uses of CodedInputStream can be more efficient if they know + // exactly how many bytes are available. By pushing the end point of the + // buffer as a limit, we allow them to get this information via + // getBytesUntilLimit(). Pushing a limit that we know is at the end of + // the stream can never hurt, since we can never past that point anyway. + result.pushLimit(byteString.size()); + } catch (InvalidProtocolBufferException ex) { + // The only reason pushLimit() might throw an exception here is if len + // is negative. Normally pushLimit()'s parameter comes directly off the + // wire, so it's important to catch exceptions in case of corrupt or + // malicious data. However, in this case, we expect that len is not a + // user-supplied value, so we can assume that it being negative indicates + // a programming error. Therefore, throwing an unchecked exception is + // appropriate. + throw new IllegalArgumentException(ex); + } + return result; } // ----------------------------------------------------------------- @@ -107,6 +175,10 @@ public final class CodedInputStream { } } + public int getLastTag() { + return lastTag; + } + /** * Reads and discards a single field, given its tag value. * @@ -116,10 +188,10 @@ public final class CodedInputStream { public boolean skipField(final int tag) throws IOException { switch (WireFormat.getTagWireType(tag)) { case WireFormat.WIRETYPE_VARINT: - readInt32(); + skipRawVarint(); return true; case WireFormat.WIRETYPE_FIXED64: - readRawLittleEndian64(); + skipRawBytes(8); return true; case WireFormat.WIRETYPE_LENGTH_DELIMITED: skipRawBytes(readRawVarint32()); @@ -133,7 +205,7 @@ public final class CodedInputStream { case WireFormat.WIRETYPE_END_GROUP: return false; case WireFormat.WIRETYPE_FIXED32: - readRawLittleEndian32(); + skipRawBytes(4); return true; default: throw InvalidProtocolBufferException.invalidWireType(); @@ -141,6 +213,57 @@ public final class CodedInputStream { } /** + * Reads a single field and writes it to output in wire format, + * given its tag value. + * + * @return {@code false} if the tag is an endgroup tag, in which case + * nothing is skipped. Otherwise, returns {@code true}. + */ + public boolean skipField(final int tag, final CodedOutputStream output) + throws IOException { + switch (WireFormat.getTagWireType(tag)) { + case WireFormat.WIRETYPE_VARINT: { + long value = readInt64(); + output.writeRawVarint32(tag); + output.writeUInt64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_FIXED64: { + long value = readRawLittleEndian64(); + output.writeRawVarint32(tag); + output.writeFixed64NoTag(value); + return true; + } + case WireFormat.WIRETYPE_LENGTH_DELIMITED: { + ByteString value = readBytes(); + output.writeRawVarint32(tag); + output.writeBytesNoTag(value); + return true; + } + case WireFormat.WIRETYPE_START_GROUP: { + output.writeRawVarint32(tag); + skipMessage(output); + int endtag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), + WireFormat.WIRETYPE_END_GROUP); + checkLastTagWas(endtag); + output.writeRawVarint32(endtag); + return true; + } + case WireFormat.WIRETYPE_END_GROUP: { + return false; + } + case WireFormat.WIRETYPE_FIXED32: { + int value = readRawLittleEndian32(); + output.writeRawVarint32(tag); + output.writeFixed32NoTag(value); + return true; + } + default: + throw InvalidProtocolBufferException.invalidWireType(); + } + } + + /** * Reads and discards an entire message. This will read either until EOF * or until an endgroup tag, whichever comes first. */ @@ -153,6 +276,51 @@ public final class CodedInputStream { } } + /** + * Reads an entire message and writes it to output in wire format. + * This will read either until EOF or until an endgroup tag, + * whichever comes first. + */ + public void skipMessage(CodedOutputStream output) throws IOException { + while (true) { + final int tag = readTag(); + if (tag == 0 || !skipField(tag, output)) { + return; + } + } + } + + /** + * Collects the bytes skipped and returns the data in a ByteBuffer. + */ + private class SkippedDataSink implements RefillCallback { + private int lastPos = bufferPos; + private ByteArrayOutputStream byteArrayStream; + + @Override + public void onRefill() { + if (byteArrayStream == null) { + byteArrayStream = new ByteArrayOutputStream(); + } + byteArrayStream.write(buffer, lastPos, bufferPos - lastPos); + lastPos = 0; + } + + /** + * Gets skipped data in a ByteBuffer. This method should only be + * called once. + */ + ByteBuffer getSkippedData() { + if (byteArrayStream == null) { + return ByteBuffer.wrap(buffer, lastPos, bufferPos - lastPos); + } else { + byteArrayStream.write(buffer, lastPos, bufferPos); + return ByteBuffer.wrap(byteArrayStream.toByteArray()); + } + } + } + + // ----------------------------------------------------------------- /** Read a {@code double} field value from the stream. */ @@ -192,10 +360,14 @@ public final class CodedInputStream { /** Read a {@code bool} field value from the stream. */ public boolean readBool() throws IOException { - return readRawVarint32() != 0; + return readRawVarint64() != 0; } - /** Read a {@code string} field value from the stream. */ + /** + * Read a {@code string} field value from the stream. + * If the stream contains malformed UTF-8, + * replace the offending bytes with the standard UTF-8 replacement character. + */ public String readString() throws IOException { final int size = readRawVarint32(); if (size <= (bufferSize - bufferPos) && size > 0) { @@ -204,10 +376,40 @@ public final class CodedInputStream { final String result = new String(buffer, bufferPos, size, "UTF-8"); bufferPos += size; return result; + } else if (size == 0) { + return ""; } else { // Slow path: Build a byte array first then copy it. - return new String(readRawBytes(size), "UTF-8"); + return new String(readRawBytesSlowPath(size), "UTF-8"); + } + } + + /** + * Read a {@code string} field value from the stream. + * If the stream contains malformed UTF-8, + * throw exception {@link InvalidProtocolBufferException}. + */ + public String readStringRequireUtf8() throws IOException { + final int size = readRawVarint32(); + final byte[] bytes; + int pos = bufferPos; + if (size <= (bufferSize - pos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + bytes = buffer; + bufferPos = pos + size; + } else if (size == 0) { + return ""; + } else { + // Slow path: Build a byte array first then copy it. + bytes = readRawBytesSlowPath(size); + pos = 0; + } + // TODO(martinrb): We could save a pass by validating while decoding. + if (!Utf8.isValidUtf8(bytes, pos, pos + size)) { + throw InvalidProtocolBufferException.invalidUtf8(); } + return new String(bytes, pos, size, "UTF-8"); } /** Read a {@code group} field value from the stream. */ @@ -225,6 +427,24 @@ public final class CodedInputStream { --recursionDepth; } + + /** Read a {@code group} field value from the stream. */ + public <T extends MessageLite> T readGroup( + final int fieldNumber, + final Parser<T> parser, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas( + WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP)); + --recursionDepth; + return result; + } + /** * Reads a {@code group} field value from the stream and merges it into the * given {@link UnknownFieldSet}. @@ -260,18 +480,80 @@ public final class CodedInputStream { popLimit(oldLimit); } + + /** Read an embedded message field value from the stream. */ + public <T extends MessageLite> T readMessage( + final Parser<T> parser, + final ExtensionRegistryLite extensionRegistry) + throws IOException { + int length = readRawVarint32(); + if (recursionDepth >= recursionLimit) { + throw InvalidProtocolBufferException.recursionLimitExceeded(); + } + final int oldLimit = pushLimit(length); + ++recursionDepth; + T result = parser.parsePartialFrom(this, extensionRegistry); + checkLastTagWas(0); + --recursionDepth; + popLimit(oldLimit); + return result; + } + /** Read a {@code bytes} field value from the stream. */ public ByteString readBytes() throws IOException { final int size = readRawVarint32(); if (size <= (bufferSize - bufferPos) && size > 0) { // Fast path: We already have the bytes in a contiguous buffer, so // just copy directly from it. - final ByteString result = ByteString.copyFrom(buffer, bufferPos, size); + final ByteString result = bufferIsImmutable && enableAliasing + ? new BoundedByteString(buffer, bufferPos, size) + : ByteString.copyFrom(buffer, bufferPos, size); bufferPos += size; return result; + } else if (size == 0) { + return ByteString.EMPTY; } else { // Slow path: Build a byte array first then copy it. - return ByteString.copyFrom(readRawBytes(size)); + return new LiteralByteString(readRawBytesSlowPath(size)); + } + } + + /** Read a {@code bytes} field value from the stream. */ + public byte[] readByteArray() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer, so + // just copy directly from it. + final byte[] result = + Arrays.copyOfRange(buffer, bufferPos, bufferPos + size); + bufferPos += size; + return result; + } else { + // Slow path: Build a byte array first then copy it. + return readRawBytesSlowPath(size); + } + } + + /** Read a {@code bytes} field value from the stream. */ + public ByteBuffer readByteBuffer() throws IOException { + final int size = readRawVarint32(); + if (size <= (bufferSize - bufferPos) && size > 0) { + // Fast path: We already have the bytes in a contiguous buffer. + // When aliasing is enabled, we can return a ByteBuffer pointing directly + // into the underlying byte array without copy if the CodedInputStream is + // constructed from a byte array. If aliasing is disabled or the input is + // from an InputStream or ByteString, we have to make a copy of the bytes. + ByteBuffer result = input == null && !bufferIsImmutable && enableAliasing + ? ByteBuffer.wrap(buffer, bufferPos, size).slice() + : ByteBuffer.wrap(Arrays.copyOfRange( + buffer, bufferPos, bufferPos + size)); + bufferPos += size; + return result; + } else if (size == 0) { + return Internal.EMPTY_BYTE_BUFFER; + } else { + // Slow path: Build a byte array first then copy it. + return ByteBuffer.wrap(readRawBytesSlowPath(size)); } } @@ -315,37 +597,67 @@ public final class CodedInputStream { * upper bits. */ public int readRawVarint32() throws IOException { - byte tmp = readRawByte(); - if (tmp >= 0) { - return tmp; - } - int result = tmp & 0x7f; - if ((tmp = readRawByte()) >= 0) { - result |= tmp << 7; - } else { - result |= (tmp & 0x7f) << 7; - if ((tmp = readRawByte()) >= 0) { - result |= tmp << 14; + // See implementation notes for readRawVarint64 + fastpath: { + int pos = bufferPos; + + if (bufferSize == pos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + int x; + if ((x = buffer[pos++]) >= 0) { + bufferPos = pos; + return x; + } else if (bufferSize - pos < 9) { + break fastpath; + } else if ((x ^= (buffer[pos++] << 7)) < 0L) { + x ^= (~0L << 7); + } else if ((x ^= (buffer[pos++] << 14)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14); + } else if ((x ^= (buffer[pos++] << 21)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21); } else { - result |= (tmp & 0x7f) << 14; - if ((tmp = readRawByte()) >= 0) { - result |= tmp << 21; - } else { - result |= (tmp & 0x7f) << 21; - result |= (tmp = readRawByte()) << 28; - if (tmp < 0) { - // Discard upper 32 bits. - for (int i = 0; i < 5; i++) { - if (readRawByte() >= 0) { - return result; - } - } - throw InvalidProtocolBufferException.malformedVarint(); - } + int y = buffer[pos++]; + x ^= y << 28; + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); + if (y < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0 && + buffer[pos++] < 0) { + break fastpath; // Will throw malformedVarint() } } + bufferPos = pos; + return x; } - return result; + return (int) readRawVarint64SlowPath(); + } + + private void skipRawVarint() throws IOException { + if (bufferSize - bufferPos >= 10) { + final byte[] buffer = this.buffer; + int pos = bufferPos; + for (int i = 0; i < 10; i++) { + if (buffer[pos++] >= 0) { + bufferPos = pos; + return; + } + } + } + skipRawVarintSlowPath(); + } + + private void skipRawVarintSlowPath() throws IOException { + for (int i = 0; i < 10; i++) { + if (readRawByte() >= 0) { + return; + } + } + throw InvalidProtocolBufferException.malformedVarint(); } /** @@ -368,8 +680,8 @@ public final class CodedInputStream { * has already read one byte. This allows the caller to determine if EOF * has been reached before attempting to read. */ - static int readRawVarint32(final int firstByte, - final InputStream input) throws IOException { + public static int readRawVarint32( + final int firstByte, final InputStream input) throws IOException { if ((firstByte & 0x80) == 0) { return firstByte; } @@ -401,49 +713,115 @@ public final class CodedInputStream { /** Read a raw Varint from the stream. */ public long readRawVarint64() throws IOException { - int shift = 0; + // Implementation notes: + // + // Optimized for one-byte values, expected to be common. + // The particular code below was selected from various candidates + // empirically, by winning VarintBenchmark. + // + // Sign extension of (signed) Java bytes is usually a nuisance, but + // we exploit it here to more easily obtain the sign of bytes read. + // Instead of cleaning up the sign extension bits by masking eagerly, + // we delay until we find the final (positive) byte, when we clear all + // accumulated bits with one xor. We depend on javac to constant fold. + fastpath: { + int pos = bufferPos; + + if (bufferSize == pos) { + break fastpath; + } + + final byte[] buffer = this.buffer; + long x; + int y; + if ((y = buffer[pos++]) >= 0) { + bufferPos = pos; + return y; + } else if (bufferSize - pos < 9) { + break fastpath; + } else if ((x = y ^ (buffer[pos++] << 7)) < 0L) { + x ^= (~0L << 7); + } else if ((x ^= (buffer[pos++] << 14)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14); + } else if ((x ^= (buffer[pos++] << 21)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21); + } else if ((x ^= ((long) buffer[pos++] << 28)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28); + } else if ((x ^= ((long) buffer[pos++] << 35)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35); + } else if ((x ^= ((long) buffer[pos++] << 42)) >= 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42); + } else if ((x ^= ((long) buffer[pos++] << 49)) < 0L) { + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) + ^ (~0L << 49); + } else { + x ^= ((long) buffer[pos++] << 56); + x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42) + ^ (~0L << 49) ^ (~0L << 56); + if (x < 0L) { + if (buffer[pos++] < 0L) { + break fastpath; // Will throw malformedVarint() + } + } + } + bufferPos = pos; + return x; + } + return readRawVarint64SlowPath(); + } + + /** Variant of readRawVarint64 for when uncomfortably close to the limit. */ + /* Visible for testing */ + long readRawVarint64SlowPath() throws IOException { long result = 0; - while (shift < 64) { + for (int shift = 0; shift < 64; shift += 7) { final byte b = readRawByte(); - result |= (long)(b & 0x7F) << shift; + result |= (long) (b & 0x7F) << shift; if ((b & 0x80) == 0) { return result; } - shift += 7; } throw InvalidProtocolBufferException.malformedVarint(); } /** Read a 32-bit little-endian integer from the stream. */ public int readRawLittleEndian32() throws IOException { - final byte b1 = readRawByte(); - final byte b2 = readRawByte(); - final byte b3 = readRawByte(); - final byte b4 = readRawByte(); - return (((int)b1 & 0xff) ) | - (((int)b2 & 0xff) << 8) | - (((int)b3 & 0xff) << 16) | - (((int)b4 & 0xff) << 24); + int pos = bufferPos; + + // hand-inlined ensureAvailable(4); + if (bufferSize - pos < 4) { + refillBuffer(4); + pos = bufferPos; + } + + final byte[] buffer = this.buffer; + bufferPos = pos + 4; + return (((buffer[pos] & 0xff)) | + ((buffer[pos + 1] & 0xff) << 8) | + ((buffer[pos + 2] & 0xff) << 16) | + ((buffer[pos + 3] & 0xff) << 24)); } /** Read a 64-bit little-endian integer from the stream. */ public long readRawLittleEndian64() throws IOException { - final byte b1 = readRawByte(); - final byte b2 = readRawByte(); - final byte b3 = readRawByte(); - final byte b4 = readRawByte(); - final byte b5 = readRawByte(); - final byte b6 = readRawByte(); - final byte b7 = readRawByte(); - final byte b8 = readRawByte(); - return (((long)b1 & 0xff) ) | - (((long)b2 & 0xff) << 8) | - (((long)b3 & 0xff) << 16) | - (((long)b4 & 0xff) << 24) | - (((long)b5 & 0xff) << 32) | - (((long)b6 & 0xff) << 40) | - (((long)b7 & 0xff) << 48) | - (((long)b8 & 0xff) << 56); + int pos = bufferPos; + + // hand-inlined ensureAvailable(8); + if (bufferSize - pos < 8) { + refillBuffer(8); + pos = bufferPos; + } + + final byte[] buffer = this.buffer; + bufferPos = pos + 8; + return ((((long) buffer[pos] & 0xffL)) | + (((long) buffer[pos + 1] & 0xffL) << 8) | + (((long) buffer[pos + 2] & 0xffL) << 16) | + (((long) buffer[pos + 3] & 0xffL) << 24) | + (((long) buffer[pos + 4] & 0xffL) << 32) | + (((long) buffer[pos + 5] & 0xffL) << 40) | + (((long) buffer[pos + 6] & 0xffL) << 48) | + (((long) buffer[pos + 7] & 0xffL) << 56)); } /** @@ -477,11 +855,13 @@ public final class CodedInputStream { // ----------------------------------------------------------------- private final byte[] buffer; + private final boolean bufferIsImmutable; private int bufferSize; private int bufferSizeAfterLimit; private int bufferPos; private final InputStream input; private int lastTag; + private boolean enableAliasing = false; /** * The total number of bytes read before the current buffer. The total @@ -512,6 +892,7 @@ public final class CodedInputStream { bufferPos = off; totalBytesRetired = -off; input = null; + bufferIsImmutable = false; } private CodedInputStream(final InputStream input) { @@ -520,6 +901,20 @@ public final class CodedInputStream { bufferPos = 0; totalBytesRetired = 0; this.input = input; + bufferIsImmutable = false; + } + + private CodedInputStream(final LiteralByteString byteString) { + buffer = byteString.bytes; + bufferPos = byteString.getOffsetIntoBytes(); + bufferSize = bufferPos + byteString.size(); + totalBytesRetired = -bufferPos; + input = null; + bufferIsImmutable = true; + } + + public void enableAliasing(boolean enabled) { + this.enableAliasing = enabled; } /** @@ -581,7 +976,7 @@ public final class CodedInputStream { * refreshing its buffer. If you need to prevent reading past a certain * point in the underlying {@code InputStream} (e.g. because you expect it to * contain more data after the end of the message which you need to handle - * differently) then you must place a wrapper around you {@code InputStream} + * differently) then you must place a wrapper around your {@code InputStream} * which limits the amount of data that can be read from it. * * @return the old limit. @@ -643,7 +1038,7 @@ public final class CodedInputStream { * if the stream has reached a limit created using {@link #pushLimit(int)}. */ public boolean isAtEnd() throws IOException { - return bufferPos == bufferSize && !refillBuffer(false); + return bufferPos == bufferSize && !tryRefillBuffer(1); } /** @@ -654,53 +1049,93 @@ public final class CodedInputStream { return totalBytesRetired + bufferPos; } + private interface RefillCallback { + void onRefill(); + } + + private RefillCallback refillCallback = null; + + /** + * Ensures that at least {@code n} bytes are available in the buffer, reading + * more bytes from the input if necessary to make it so. Caller must ensure + * that the requested space is less than BUFFER_SIZE. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + private void ensureAvailable(int n) throws IOException { + if (bufferSize - bufferPos < n) { + refillBuffer(n); + } + } + + /** + * Reads more bytes from the input, making at least {@code n} bytes available + * in the buffer. Caller must ensure that the requested space is not yet + * available, and that the requested space is less than BUFFER_SIZE. + * + * @throws InvalidProtocolBufferException The end of the stream or the current + * limit was reached. + */ + private void refillBuffer(int n) throws IOException { + if (!tryRefillBuffer(n)) { + throw InvalidProtocolBufferException.truncatedMessage(); + } + } + /** - * Called with {@code this.buffer} is empty to read more bytes from the - * input. If {@code mustSucceed} is true, refillBuffer() gurantees that - * either there will be at least one byte in the buffer when it returns - * or it will throw an exception. If {@code mustSucceed} is false, - * refillBuffer() returns false if no more bytes were available. + * Tries to read more bytes from the input, making at least {@code n} bytes + * available in the buffer. Caller must ensure that the requested space is + * not yet available, and that the requested space is less than BUFFER_SIZE. + * + * @return {@code true} if the bytes could be made available; {@code false} + * if the end of the stream or the current limit was reached. */ - private boolean refillBuffer(final boolean mustSucceed) throws IOException { - if (bufferPos < bufferSize) { + private boolean tryRefillBuffer(int n) throws IOException { + if (bufferPos + n <= bufferSize) { throw new IllegalStateException( - "refillBuffer() called when buffer wasn't empty."); + "refillBuffer() called when " + n + + " bytes were already available in buffer"); } - if (totalBytesRetired + bufferSize == currentLimit) { + if (totalBytesRetired + bufferPos + n > currentLimit) { // Oops, we hit a limit. - if (mustSucceed) { - throw InvalidProtocolBufferException.truncatedMessage(); - } else { - return false; - } + return false; } - totalBytesRetired += bufferSize; - - bufferPos = 0; - bufferSize = (input == null) ? -1 : input.read(buffer); - if (bufferSize == 0 || bufferSize < -1) { - throw new IllegalStateException( - "InputStream#read(byte[]) returned invalid result: " + bufferSize + - "\nThe InputStream implementation is buggy."); + if (refillCallback != null) { + refillCallback.onRefill(); } - if (bufferSize == -1) { - bufferSize = 0; - if (mustSucceed) { - throw InvalidProtocolBufferException.truncatedMessage(); - } else { - return false; + + if (input != null) { + int pos = bufferPos; + if (pos > 0) { + if (bufferSize > pos) { + System.arraycopy(buffer, pos, buffer, 0, bufferSize - pos); + } + totalBytesRetired += pos; + bufferSize -= pos; + bufferPos = 0; } - } else { - recomputeBufferSizeAfterLimit(); - final int totalBytesRead = - totalBytesRetired + bufferSize + bufferSizeAfterLimit; - if (totalBytesRead > sizeLimit || totalBytesRead < 0) { - throw InvalidProtocolBufferException.sizeLimitExceeded(); + + int bytesRead = input.read(buffer, bufferSize, buffer.length - bufferSize); + if (bytesRead == 0 || bytesRead < -1 || bytesRead > buffer.length) { + throw new IllegalStateException( + "InputStream#read(byte[]) returned invalid result: " + bytesRead + + "\nThe InputStream implementation is buggy."); + } + if (bytesRead > 0) { + bufferSize += bytesRead; + // Integer-overflow-conscious check against sizeLimit + if (totalBytesRetired + n - sizeLimit > 0) { + throw InvalidProtocolBufferException.sizeLimitExceeded(); + } + recomputeBufferSizeAfterLimit(); + return (bufferSize >= n) ? true : tryRefillBuffer(n); } - return true; } + + return false; } /** @@ -711,7 +1146,7 @@ public final class CodedInputStream { */ public byte readRawByte() throws IOException { if (bufferPos == bufferSize) { - refillBuffer(true); + refillBuffer(1); } return buffer[bufferPos++]; } @@ -723,8 +1158,26 @@ public final class CodedInputStream { * limit was reached. */ public byte[] readRawBytes(final int size) throws IOException { - if (size < 0) { - throw InvalidProtocolBufferException.negativeSize(); + final int pos = bufferPos; + if (size <= (bufferSize - pos) && size > 0) { + bufferPos = pos + size; + return Arrays.copyOfRange(buffer, pos, pos + size); + } else { + return readRawBytesSlowPath(size); + } + } + + /** + * Exactly like readRawBytes, but caller must have already checked the fast + * path: (size <= (bufferSize - pos) && size > 0) + */ + private byte[] readRawBytesSlowPath(final int size) throws IOException { + if (size <= 0) { + if (size == 0) { + return Internal.EMPTY_BYTE_ARRAY; + } else { + throw InvalidProtocolBufferException.negativeSize(); + } } if (totalBytesRetired + bufferPos + size > currentLimit) { @@ -734,13 +1187,7 @@ public final class CodedInputStream { throw InvalidProtocolBufferException.truncatedMessage(); } - if (size <= bufferSize - bufferPos) { - // We have all the bytes we need already. - final byte[] bytes = new byte[size]; - System.arraycopy(buffer, bufferPos, bytes, 0, size); - bufferPos += size; - return bytes; - } else if (size < BUFFER_SIZE) { + if (size < BUFFER_SIZE) { // Reading more bytes than are in the buffer, but not an excessive number // of bytes. We can safely allocate the resulting array ahead of time. @@ -750,18 +1197,10 @@ public final class CodedInputStream { System.arraycopy(buffer, bufferPos, bytes, 0, pos); bufferPos = bufferSize; - // We want to use refillBuffer() and then copy from the buffer into our + // We want to refill the buffer and then copy from the buffer into our // byte array rather than reading directly into our byte array because // the input may be unbuffered. - refillBuffer(true); - - while (size - pos > bufferSize) { - System.arraycopy(buffer, 0, bytes, pos, bufferSize); - pos += bufferSize; - bufferPos = bufferSize; - refillBuffer(true); - } - + ensureAvailable(size - pos); System.arraycopy(buffer, 0, bytes, pos, size - pos); bufferPos = size - pos; @@ -830,6 +1269,19 @@ public final class CodedInputStream { * limit was reached. */ public void skipRawBytes(final int size) throws IOException { + if (size <= (bufferSize - bufferPos) && size >= 0) { + // We have all the bytes we need already. + bufferPos += size; + } else { + skipRawBytesSlowPath(size); + } + } + + /** + * Exactly like skipRawBytes, but caller must have already checked the fast + * path: (size <= (bufferSize - pos) && size >= 0) + */ + private void skipRawBytesSlowPath(final int size) throws IOException { if (size < 0) { throw InvalidProtocolBufferException.negativeSize(); } @@ -841,25 +1293,19 @@ public final class CodedInputStream { throw InvalidProtocolBufferException.truncatedMessage(); } - if (size <= bufferSize - bufferPos) { - // We have all the bytes we need already. - bufferPos += size; - } else { - // Skipping more bytes than are in the buffer. First skip what we have. - int pos = bufferSize - bufferPos; - totalBytesRetired += bufferSize; - bufferPos = 0; - bufferSize = 0; + // Skipping more bytes than are in the buffer. First skip what we have. + int pos = bufferSize - bufferPos; + bufferPos = bufferSize; - // Then skip directly from the InputStream for the rest. - while (pos < size) { - final int n = (input == null) ? -1 : (int) input.skip(size - pos); - if (n <= 0) { - throw InvalidProtocolBufferException.truncatedMessage(); - } - pos += n; - totalBytesRetired += n; - } + // Keep refilling the buffer until we get to the point we wanted to skip to. + // This has the side effect of ensuring the limits are updated correctly. + refillBuffer(1); + while (size - pos > bufferSize) { + pos += bufferSize; + bufferPos = bufferSize; + refillBuffer(1); } + + bufferPos = size - pos; } } |