Skip to content

Commit 96dfcce

Browse files
Force re-computing the serialized size after the lazy fields with corrupted data are replaced with empty bytes
PiperOrigin-RevId: 698781661
1 parent d6b90bf commit 96dfcce

File tree

4 files changed

+65
-4
lines changed

4 files changed

+65
-4
lines changed

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

+6
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,12 @@ public Object getField(final T descriptor) {
269269
return o;
270270
}
271271

272+
/** Returns true if the field is a lazy field and it is corrupted. */
273+
boolean lazyFieldCorrupted(final T descriptor) {
274+
Object o = fields.get(descriptor);
275+
return o instanceof LazyField && ((LazyField) o).isCorrupted();
276+
}
277+
272278
/**
273279
* Useful for implementing {@link Message.Builder#setField(Descriptors.FieldDescriptor,Object)}.
274280
*/

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

+13-4
Original file line numberDiff line numberDiff line change
@@ -986,17 +986,26 @@ public final <T> T getExtension(final ExtensionLite<? extends MessageT, T> exten
986986
verifyExtensionContainingType(extension);
987987
FieldDescriptor descriptor = extension.getDescriptor();
988988
final Object value = extensions.getField(descriptor);
989+
T result = null;
989990
if (value == null) {
990991
if (descriptor.isRepeated()) {
991-
return (T) ProtobufArrayList.emptyList();
992+
result = (T) ProtobufArrayList.emptyList();
992993
} else if (descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
993-
return (T) extension.getMessageDefaultInstance();
994+
result = (T) extension.getMessageDefaultInstance();
994995
} else {
995-
return (T) extension.fromReflectionType(descriptor.getDefaultValue());
996+
result = (T) extension.fromReflectionType(descriptor.getDefaultValue());
996997
}
997998
} else {
998-
return (T) extension.fromReflectionType(value);
999+
result = (T) extension.fromReflectionType(value);
9991000
}
1001+
1002+
// If the lazy field is corrupted, we need to invalidate the memoized size in case the
1003+
// corrupted message data was replaced with an empty ByteString and yet a previous serialized
1004+
// size was memoized.
1005+
if (extensions.lazyFieldCorrupted(descriptor)) {
1006+
setMemoizedSerializedSize(-1);
1007+
}
1008+
return result;
10001009
}
10011010

10021011
/** Get one element of a repeated extension. */

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

+8
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ public class LazyFieldLite {
8989
*/
9090
private volatile ByteString memoizedBytes;
9191

92+
private volatile boolean corrupted;
93+
9294
/** Constructs a LazyFieldLite with bytes that will be parsed lazily. */
9395
public LazyFieldLite(ExtensionRegistryLite extensionRegistry, ByteString bytes) {
9496
checkArguments(extensionRegistry, bytes);
@@ -400,6 +402,7 @@ protected void ensureInitialized(MessageLite defaultInstance) {
400402
} catch (InvalidProtocolBufferException e) {
401403
// Nothing is logged and no exceptions are thrown. Clients will be unaware that this proto
402404
// was invalid.
405+
this.corrupted = true;
403406
this.value = defaultInstance;
404407
this.memoizedBytes = ByteString.EMPTY;
405408
}
@@ -414,4 +417,9 @@ private static void checkArguments(ExtensionRegistryLite extensionRegistry, Byte
414417
throw new NullPointerException("found null ByteString");
415418
}
416419
}
420+
421+
/** Returns whether the lazy field was corrupted and replaced with an empty message. */
422+
boolean isCorrupted() {
423+
return corrupted;
424+
}
417425
}

java/core/src/test/java/com/google/protobuf/LazilyParsedMessageSetTest.java

+38
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,42 @@ public void testLoadCorruptedLazyField_getsReplacedWithEmptyMessage() throws Exc
159159

160160
assertThat(actualRaw).isEqualTo(expectedRaw);
161161
}
162+
163+
@Test
164+
public void testLoadCorruptedLazyField_getSerializedSize() throws Exception {
165+
ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
166+
extensionRegistry.add(TestMessageSetExtension1.messageSetExtension);
167+
RawMessageSet inputRaw =
168+
RawMessageSet.newBuilder()
169+
.addItem(
170+
RawMessageSet.Item.newBuilder()
171+
.setTypeId(TYPE_ID_1)
172+
.setMessage(CORRUPTED_MESSAGE_PAYLOAD))
173+
.build();
174+
ByteString inputData = inputRaw.toByteString();
175+
TestMessageSet messageSet = TestMessageSet.parseFrom(inputData, extensionRegistry);
176+
177+
// Effectively cache the serialized size of the message set.
178+
assertThat(messageSet.getSerializedSize()).isEqualTo(9);
179+
180+
// getExtension should mark the memoized size as "dirty" (i.e. -1).
181+
assertThat(messageSet.getExtension(TestMessageSetExtension1.messageSetExtension))
182+
.isEqualTo(TestMessageSetExtension1.getDefaultInstance());
183+
184+
// toByteString calls getSerializedSize() which should re-compute the serialized size as the
185+
// message contains lazy fields.
186+
ByteString outputData = messageSet.toByteString();
187+
188+
// Re-parse as RawMessageSet
189+
RawMessageSet actualRaw =
190+
RawMessageSet.parseFrom(outputData, ExtensionRegistry.getEmptyRegistry());
191+
192+
RawMessageSet expectedRaw =
193+
RawMessageSet.newBuilder()
194+
.addItem(
195+
RawMessageSet.Item.newBuilder().setTypeId(TYPE_ID_1).setMessage(ByteString.empty()))
196+
.build();
197+
198+
assertThat(actualRaw).isEqualTo(expectedRaw);
199+
}
162200
}

0 commit comments

Comments
 (0)