Java fixes for 22.x (#12035)

* Fix mutability bug in Java proto lite: sub-messages inside of oneofs were not
being set as immutable. This would allow code to get a builder for a
sub-message and modify the original (supposedly immutable) copy.

PiperOrigin-RevId: 511598810

* Add casts to make protobuf compatible with Java 1.8 runtime.
Fix for: https://github.com/protocolbuffers/protobuf/issues/11393

PiperOrigin-RevId: 511807920

---------

Co-authored-by: Protobuf Team Bot <protobuf-github-bot@google.com>
diff --git a/java/core/BUILD.bazel b/java/core/BUILD.bazel
index 6113169..c0e52a4 100644
--- a/java/core/BUILD.bazel
+++ b/java/core/BUILD.bazel
@@ -49,6 +49,7 @@
     "src/main/java/com/google/protobuf/Internal.java",
     "src/main/java/com/google/protobuf/InvalidProtocolBufferException.java",
     "src/main/java/com/google/protobuf/IterableByteBufferInputStream.java",
+    "src/main/java/com/google/protobuf/Java8Compatibility.java",
     "src/main/java/com/google/protobuf/JavaType.java",
     "src/main/java/com/google/protobuf/LazyField.java",
     "src/main/java/com/google/protobuf/LazyFieldLite.java",
diff --git a/java/core/src/main/java/com/google/protobuf/AllocatedBuffer.java b/java/core/src/main/java/com/google/protobuf/AllocatedBuffer.java
index 94b0994..75de57a 100644
--- a/java/core/src/main/java/com/google/protobuf/AllocatedBuffer.java
+++ b/java/core/src/main/java/com/google/protobuf/AllocatedBuffer.java
@@ -189,7 +189,7 @@
 
       @Override
       public AllocatedBuffer position(int position) {
-        buffer.position(position);
+        Java8Compatibility.position(buffer, position);
         return this;
       }
 
diff --git a/java/core/src/main/java/com/google/protobuf/BinaryWriter.java b/java/core/src/main/java/com/google/protobuf/BinaryWriter.java
index 66cf51d..7eb7886 100644
--- a/java/core/src/main/java/com/google/protobuf/BinaryWriter.java
+++ b/java/core/src/main/java/com/google/protobuf/BinaryWriter.java
@@ -2019,8 +2019,8 @@
       buffers.addFirst(allocatedBuffer);
 
       buffer = nioBuffer;
-      buffer.limit(buffer.capacity());
-      buffer.position(0);
+      Java8Compatibility.limit(buffer, buffer.capacity());
+      Java8Compatibility.position(buffer, 0);
       // Set byte order to little endian for fast writing of fixed 32/64.
       buffer.order(ByteOrder.LITTLE_ENDIAN);
 
@@ -2046,7 +2046,7 @@
       if (buffer != null) {
         totalDoneBytes += bytesWrittenToCurrentBuffer();
         // Update the indices on the netty buffer.
-        buffer.position(pos + 1);
+        Java8Compatibility.position(buffer, pos + 1);
         buffer = null;
         pos = 0;
         limitMinusOne = 0;
@@ -2475,7 +2475,7 @@
       }
 
       pos -= length;
-      buffer.position(pos + 1);
+      Java8Compatibility.position(buffer, pos + 1);
       buffer.put(value, offset, length);
     }
 
@@ -2494,7 +2494,7 @@
       }
 
       pos -= length;
-      buffer.position(pos + 1);
+      Java8Compatibility.position(buffer, pos + 1);
       buffer.put(value, offset, length);
     }
 
@@ -2506,7 +2506,7 @@
       }
 
       pos -= length;
-      buffer.position(pos + 1);
+      Java8Compatibility.position(buffer, pos + 1);
       buffer.put(value);
     }
 
@@ -2526,7 +2526,7 @@
       }
 
       pos -= length;
-      buffer.position(pos + 1);
+      Java8Compatibility.position(buffer, pos + 1);
       buffer.put(value);
     }
 
@@ -2576,8 +2576,8 @@
       buffers.addFirst(allocatedBuffer);
 
       buffer = nioBuffer;
-      buffer.limit(buffer.capacity());
-      buffer.position(0);
+      Java8Compatibility.limit(buffer, buffer.capacity());
+      Java8Compatibility.position(buffer, 0);
 
       bufferOffset = UnsafeUtil.addressOffset(buffer);
       limitMinusOne = bufferOffset + (buffer.limit() - 1);
@@ -2602,7 +2602,7 @@
       if (buffer != null) {
         totalDoneBytes += bytesWrittenToCurrentBuffer();
         // Update the indices on the netty buffer.
-        buffer.position(bufferPos() + 1);
+        Java8Compatibility.position(buffer, bufferPos() + 1);
         buffer = null;
         pos = 0;
         limitMinusOne = 0;
@@ -3016,7 +3016,7 @@
       }
 
       pos -= length;
-      buffer.position(bufferPos() + 1);
+      Java8Compatibility.position(buffer, bufferPos() + 1);
       buffer.put(value, offset, length);
     }
 
@@ -3035,7 +3035,7 @@
       }
 
       pos -= length;
-      buffer.position(bufferPos() + 1);
+      Java8Compatibility.position(buffer, bufferPos() + 1);
       buffer.put(value, offset, length);
     }
 
@@ -3047,7 +3047,7 @@
       }
 
       pos -= length;
-      buffer.position(bufferPos() + 1);
+      Java8Compatibility.position(buffer, bufferPos() + 1);
       buffer.put(value);
     }
 
@@ -3067,7 +3067,7 @@
       }
 
       pos -= length;
-      buffer.position(bufferPos() + 1);
+      Java8Compatibility.position(buffer, bufferPos() + 1);
       buffer.put(value);
     }
 
diff --git a/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java b/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java
index 2cb3ada..3970b0e 100644
--- a/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java
+++ b/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java
@@ -107,7 +107,7 @@
       }
     } finally {
       // Restore the initial position.
-      buffer.position(initialPos);
+      Java8Compatibility.position(buffer, initialPos);
     }
   }
 
diff --git a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
index 4ad8309..44dd4dc 100644
--- a/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
+++ b/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
@@ -1271,7 +1271,7 @@
         write(value.array(), value.arrayOffset(), value.capacity());
       } else {
         ByteBuffer duplicated = value.duplicate();
-        duplicated.clear();
+        Java8Compatibility.clear(duplicated);
         write(duplicated);
       }
     }
@@ -1522,7 +1522,7 @@
     @Override
     public void flush() {
       // Update the position on the buffer.
-      byteBuffer.position(initialPosition + getTotalBytesWritten());
+      Java8Compatibility.position(byteBuffer, initialPosition + getTotalBytesWritten());
     }
   }
 
@@ -1684,7 +1684,7 @@
         write(value.array(), value.arrayOffset(), value.capacity());
       } else {
         ByteBuffer duplicated = value.duplicate();
-        duplicated.clear();
+        Java8Compatibility.clear(duplicated);
         write(duplicated);
       }
     }
@@ -1794,18 +1794,18 @@
           // Save the current position and increment past the length field. We'll come back
           // and write the length field after the encoding is complete.
           final int startOfBytes = buffer.position() + minLengthVarIntSize;
-          buffer.position(startOfBytes);
+          Java8Compatibility.position(buffer, startOfBytes);
 
           // Encode the string.
           encode(value);
 
           // Now go back to the beginning and write the length.
           int endOfBytes = buffer.position();
-          buffer.position(startPos);
+          Java8Compatibility.position(buffer, startPos);
           writeUInt32NoTag(endOfBytes - startOfBytes);
 
           // Reposition the buffer past the written data.
-          buffer.position(endOfBytes);
+          Java8Compatibility.position(buffer, endOfBytes);
         } else {
           final int length = Utf8.encodedLength(value);
           writeUInt32NoTag(length);
@@ -1813,7 +1813,7 @@
         }
       } catch (UnpairedSurrogateException e) {
         // Roll back the change and convert to an IOException.
-        buffer.position(startPos);
+        Java8Compatibility.position(buffer, startPos);
 
         // TODO(nathanmittler): We should throw an IOException here instead.
         inefficientWriteStringNoTag(value, e);
@@ -1826,7 +1826,7 @@
     @Override
     public void flush() {
       // Update the position of the original buffer.
-      originalBuffer.position(buffer.position());
+      Java8Compatibility.position(originalBuffer, buffer.position());
     }
 
     @Override
@@ -2014,7 +2014,7 @@
         write(value.array(), value.arrayOffset(), value.capacity());
       } else {
         ByteBuffer duplicated = value.duplicate();
-        duplicated.clear();
+        Java8Compatibility.clear(duplicated);
         write(duplicated);
       }
     }
@@ -2150,7 +2150,7 @@
           // Save the current position and increment past the length field. We'll come back
           // and write the length field after the encoding is complete.
           int stringStart = bufferPos(position) + minLengthVarIntSize;
-          buffer.position(stringStart);
+          Java8Compatibility.position(buffer, stringStart);
 
           // Encode the string.
           Utf8.encodeUtf8(value, buffer);
@@ -2187,7 +2187,7 @@
     @Override
     public void flush() {
       // Update the position of the original buffer.
-      originalBuffer.position(bufferPos(position));
+      Java8Compatibility.position(originalBuffer, bufferPos(position));
     }
 
     @Override
@@ -2201,7 +2201,7 @@
     }
 
     private void repositionBuffer(long pos) {
-      buffer.position(bufferPos(pos));
+      Java8Compatibility.position(buffer, bufferPos(pos));
     }
 
     private int bufferPos(long pos) {
@@ -2478,7 +2478,7 @@
         write(value.array(), value.arrayOffset(), value.capacity());
       } else {
         ByteBuffer duplicated = value.duplicate();
-        duplicated.clear();
+        Java8Compatibility.clear(duplicated);
         write(duplicated);
       }
     }
@@ -2792,7 +2792,7 @@
         write(value.array(), value.arrayOffset(), value.capacity());
       } else {
         ByteBuffer duplicated = value.duplicate();
-        duplicated.clear();
+        Java8Compatibility.clear(duplicated);
         write(duplicated);
       }
     }
diff --git a/java/core/src/main/java/com/google/protobuf/Internal.java b/java/core/src/main/java/com/google/protobuf/Internal.java
index 7a7270b..127a1656 100644
--- a/java/core/src/main/java/com/google/protobuf/Internal.java
+++ b/java/core/src/main/java/com/google/protobuf/Internal.java
@@ -313,7 +313,11 @@
     }
     // ByteBuffer.equals() will only compare the remaining bytes, but we want to
     // compare all the content.
-    return a.duplicate().clear().equals(b.duplicate().clear());
+    ByteBuffer aDuplicate = a.duplicate();
+    Java8Compatibility.clear(aDuplicate);
+    ByteBuffer bDuplicate = b.duplicate();
+    Java8Compatibility.clear(bDuplicate);
+    return aDuplicate.equals(bDuplicate);
   }
 
   /** Helper method for implementing {@link Message#equals(Object)} for bytes field. */
@@ -353,7 +357,7 @@
           bytes.capacity() > DEFAULT_BUFFER_SIZE ? DEFAULT_BUFFER_SIZE : bytes.capacity();
       final byte[] buffer = new byte[bufferSize];
       final ByteBuffer duplicated = bytes.duplicate();
-      duplicated.clear();
+      Java8Compatibility.clear(duplicated);
       int h = bytes.capacity();
       while (duplicated.remaining() > 0) {
         final int length =
diff --git a/java/core/src/main/java/com/google/protobuf/IterableByteBufferInputStream.java b/java/core/src/main/java/com/google/protobuf/IterableByteBufferInputStream.java
index 713e806..1e571cf 100644
--- a/java/core/src/main/java/com/google/protobuf/IterableByteBufferInputStream.java
+++ b/java/core/src/main/java/com/google/protobuf/IterableByteBufferInputStream.java
@@ -140,9 +140,9 @@
       updateCurrentByteBufferPos(length);
     } else {
       int prevPos = currentByteBuffer.position();
-      currentByteBuffer.position(currentByteBufferPos);
+      Java8Compatibility.position(currentByteBuffer, currentByteBufferPos);
       currentByteBuffer.get(output, offset, length);
-      currentByteBuffer.position(prevPos);
+      Java8Compatibility.position(currentByteBuffer, prevPos);
       updateCurrentByteBufferPos(length);
     }
     return length;
diff --git a/java/core/src/main/java/com/google/protobuf/Java8Compatibility.java b/java/core/src/main/java/com/google/protobuf/Java8Compatibility.java
new file mode 100644
index 0000000..ef181c2
--- /dev/null
+++ b/java/core/src/main/java/com/google/protobuf/Java8Compatibility.java
@@ -0,0 +1,67 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.nio.Buffer;
+
+/**
+ * Wrappers around {@link Buffer} methods that are covariantly overridden in Java 9+. See
+ * https://github.com/protocolbuffers/protobuf/issues/11393
+ *
+ * <p>TODO(b/270454719) remove when Java 8 support is no longer needed.
+ */
+final class Java8Compatibility {
+  static void clear(Buffer b) {
+    b.clear();
+  }
+
+  static void flip(Buffer b) {
+    b.flip();
+  }
+
+  static void limit(Buffer b, int limit) {
+    b.limit(limit);
+  }
+
+  static void mark(Buffer b) {
+    b.mark();
+  }
+
+  static void position(Buffer b, int position) {
+    b.position(position);
+  }
+
+  static void reset(Buffer b) {
+    b.reset();
+  }
+
+  private Java8Compatibility() {}
+}
diff --git a/java/core/src/main/java/com/google/protobuf/MessageSchema.java b/java/core/src/main/java/com/google/protobuf/MessageSchema.java
index 084d687..ae5dddc 100644
--- a/java/core/src/main/java/com/google/protobuf/MessageSchema.java
+++ b/java/core/src/main/java/com/google/protobuf/MessageSchema.java
@@ -5510,6 +5510,12 @@
             getMessageFieldSchema(pos).makeImmutable(UNSAFE.getObject(message, offset));
           }
           break;
+        case 60: // ONEOF_MESSAGE
+        case 68: // ONEOF_GROUP
+          if (isOneofPresent(message, numberAt(pos), pos)) {
+            getMessageFieldSchema(pos).makeImmutable(UNSAFE.getObject(message, offset));
+          }
+          break;
         case 18: // DOUBLE_LIST:
         case 19: // FLOAT_LIST:
         case 20: // INT64_LIST:
diff --git a/java/core/src/main/java/com/google/protobuf/NioByteString.java b/java/core/src/main/java/com/google/protobuf/NioByteString.java
index 1e594ff..f625eda 100644
--- a/java/core/src/main/java/com/google/protobuf/NioByteString.java
+++ b/java/core/src/main/java/com/google/protobuf/NioByteString.java
@@ -37,7 +37,6 @@
 import java.io.InvalidObjectException;
 import java.io.ObjectInputStream;
 import java.io.OutputStream;
-import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.InvalidMarkException;
@@ -110,7 +109,7 @@
   protected void copyToInternal(
       byte[] target, int sourceOffset, int targetOffset, int numberToCopy) {
     ByteBuffer slice = buffer.slice();
-    ((Buffer) slice).position(sourceOffset);
+    Java8Compatibility.position(slice, sourceOffset);
     slice.get(target, targetOffset, numberToCopy);
   }
 
@@ -224,7 +223,7 @@
 
       @Override
       public void mark(int readlimit) {
-        buf.mark();
+        Java8Compatibility.mark(buf);
       }
 
       @Override
@@ -235,7 +234,7 @@
       @Override
       public void reset() throws IOException {
         try {
-          buf.reset();
+          Java8Compatibility.reset(buf);
         } catch (InvalidMarkException e) {
           throw new IOException(e);
         }
@@ -286,8 +285,8 @@
     }
 
     ByteBuffer slice = buffer.slice();
-    ((Buffer) slice).position(beginIndex - buffer.position());
-    ((Buffer) slice).limit(endIndex - buffer.position());
+    Java8Compatibility.position(slice, beginIndex - buffer.position());
+    Java8Compatibility.limit(slice, endIndex - buffer.position());
     return slice;
   }
 }
diff --git a/java/core/src/main/java/com/google/protobuf/TextFormat.java b/java/core/src/main/java/com/google/protobuf/TextFormat.java
index 8c3ac5c..9d28d06 100644
--- a/java/core/src/main/java/com/google/protobuf/TextFormat.java
+++ b/java/core/src/main/java/com/google/protobuf/TextFormat.java
@@ -1710,7 +1710,7 @@
         if (n == -1) {
           break;
         }
-        buffer.flip();
+        Java8Compatibility.flip(buffer);
         text.append(buffer, 0, n);
       }
       return text;
diff --git a/java/core/src/main/java/com/google/protobuf/Utf8.java b/java/core/src/main/java/com/google/protobuf/Utf8.java
index c74497c..7c6823d 100644
--- a/java/core/src/main/java/com/google/protobuf/Utf8.java
+++ b/java/core/src/main/java/com/google/protobuf/Utf8.java
@@ -770,7 +770,7 @@
       if (out.hasArray()) {
         final int offset = out.arrayOffset();
         int endIndex = Utf8.encode(in, out.array(), offset + out.position(), out.remaining());
-        out.position(endIndex - offset);
+        Java8Compatibility.position(out, endIndex - offset);
       } else if (out.isDirect()) {
         encodeUtf8Direct(in, out);
       } else {
@@ -801,7 +801,7 @@
         }
         if (inIx == inLength) {
           // Successfully encoded the entire string.
-          out.position(outIx + inIx);
+          Java8Compatibility.position(out, outIx + inIx);
           return;
         }
 
@@ -844,7 +844,7 @@
         }
 
         // Successfully encoded the entire string.
-        out.position(outIx);
+        Java8Compatibility.position(out, outIx);
       } catch (IndexOutOfBoundsException e) {
         // TODO(nathanmittler): Consider making the API throw IndexOutOfBoundsException instead.
 
@@ -1545,7 +1545,7 @@
       }
       if (inIx == inLimit) {
         // We're done, it was ASCII encoded.
-        out.position((int) (outIx - address));
+        Java8Compatibility.position(out, (int) (outIx - address));
         return;
       }
 
@@ -1585,7 +1585,7 @@
       }
 
       // All bytes have been encoded.
-      out.position((int) (outIx - address));
+      Java8Compatibility.position(out, (int) (outIx - address));
     }
 
     /**
diff --git a/java/lite/pom.xml b/java/lite/pom.xml
index 716ff74..d472b36 100644
--- a/java/lite/pom.xml
+++ b/java/lite/pom.xml
@@ -125,6 +125,7 @@
                     <include>Internal.java</include>
                     <include>InvalidProtocolBufferException.java</include>
                     <include>IterableByteBufferInputStream.java</include>
+                    <include>Java8Compatibility.java</include>
                     <include>JavaType.java</include>
                     <include>LazyField.java</include>
                     <include>LazyFieldLite.java</include>
diff --git a/java/lite/src/test/java/com/google/protobuf/LiteTest.java b/java/lite/src/test/java/com/google/protobuf/LiteTest.java
index a4f95a2..a58ce95 100644
--- a/java/lite/src/test/java/com/google/protobuf/LiteTest.java
+++ b/java/lite/src/test/java/com/google/protobuf/LiteTest.java
@@ -191,6 +191,18 @@
   }
 
   @Test
+  public void testParsedOneofSubMessageIsImmutable() throws InvalidProtocolBufferException {
+    TestAllTypesLite message =
+        TestAllTypesLite.parseFrom(
+            TestAllTypesLite.newBuilder()
+                .setOneofNestedMessage(NestedMessage.newBuilder().addDd(1234).build())
+                .build()
+                .toByteArray());
+    IntArrayList subList = (IntArrayList) message.getOneofNestedMessage().getDdList();
+    assertThat(subList.isModifiable()).isFalse();
+  }
+
+  @Test
   public void testMemoization() throws Exception {
     GeneratedMessageLite<?, ?> message = TestUtilLite.getAllLiteExtensionsSet();
 
@@ -2365,8 +2377,7 @@
     Foo fooWithOnlyValue = Foo.newBuilder().setValue(1).build();
 
     Foo fooWithValueAndExtension =
-        fooWithOnlyValue
-            .toBuilder()
+        fooWithOnlyValue.toBuilder()
             .setValue(1)
             .setExtension(Bar.fooExt, Bar.newBuilder().setName("name").build())
             .build();
@@ -2382,8 +2393,7 @@
     Foo fooWithOnlyValue = Foo.newBuilder().setValue(1).build();
 
     Foo fooWithValueAndExtension =
-        fooWithOnlyValue
-            .toBuilder()
+        fooWithOnlyValue.toBuilder()
             .setValue(1)
             .setExtension(Bar.fooExt, Bar.newBuilder().setName("name").build())
             .build();
@@ -2515,9 +2525,9 @@
       assertWithMessage("expected exception").fail();
     } catch (InvalidProtocolBufferException expected) {
       assertThat(
-          TestAllExtensionsLite.newBuilder()
-              .setExtension(UnittestLite.optionalInt32ExtensionLite, 123)
-              .build())
+              TestAllExtensionsLite.newBuilder()
+                  .setExtension(UnittestLite.optionalInt32ExtensionLite, 123)
+                  .build())
           .isEqualTo(expected.getUnfinishedMessage());
     }
   }
diff --git a/src/google/protobuf/unittest_lite.proto b/src/google/protobuf/unittest_lite.proto
index 010d4a9..e2730c6 100644
--- a/src/google/protobuf/unittest_lite.proto
+++ b/src/google/protobuf/unittest_lite.proto
@@ -47,6 +47,7 @@
   message NestedMessage {
     optional int32 bb = 1;
     optional int64 cc = 2;
+    repeated int32 dd = 3 [packed = true];
   }
 
   message NestedMessage2 {