8026344: j.u.c.a *Adder and *Accumulator extend a package private class that is Serializable
Reviewed-by: rriggs, psandoz, chegar
Contributed-by: dl@cs.oswego.edu, alan.bateman@oracle.com
--- a/jdk/src/share/classes/java/util/concurrent/atomic/DoubleAccumulator.java Tue Oct 15 12:53:54 2013 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/atomic/DoubleAccumulator.java Thu Oct 24 13:24:32 2013 +0100
@@ -224,18 +224,71 @@
return (float)get();
}
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- s.defaultWriteObject();
- s.writeDouble(get());
+ /**
+ * Serialization proxy, used to avoid reference to the non-public
+ * Striped64 superclass in serialized forms.
+ * @serial include
+ */
+ private static class SerializationProxy implements Serializable {
+ private static final long serialVersionUID = 7249069246863182397L;
+
+ /**
+ * The current value returned by get().
+ * @serial
+ */
+ private final double value;
+ /**
+ * The function used for updates.
+ * @serial
+ */
+ private final DoubleBinaryOperator function;
+ /**
+ * The identity value
+ * @serial
+ */
+ private final long identity;
+
+ SerializationProxy(DoubleAccumulator a) {
+ function = a.function;
+ identity = a.identity;
+ value = a.get();
+ }
+
+ /**
+ * Returns a {@code DoubleAccumulator} object with initial state
+ * held by this proxy.
+ *
+ * @return a {@code DoubleAccumulator} object with initial state
+ * held by this proxy.
+ */
+ private Object readResolve() {
+ double d = Double.longBitsToDouble(identity);
+ DoubleAccumulator a = new DoubleAccumulator(function, d);
+ a.base = Double.doubleToRawLongBits(value);
+ return a;
+ }
}
+ /**
+ * Returns a
+ * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.DoubleAccumulator.SerializationProxy">
+ * SerializationProxy</a>
+ * representing the state of this instance.
+ *
+ * @return a {@link SerializationProxy}
+ * representing the state of this instance
+ */
+ private Object writeReplace() {
+ return new SerializationProxy(this);
+ }
+
+ /**
+ * @param s the stream
+ * @throws java.io.InvalidObjectException always
+ */
private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- s.defaultReadObject();
- cellsBusy = 0;
- cells = null;
- base = Double.doubleToRawLongBits(s.readDouble());
+ throws java.io.InvalidObjectException {
+ throw new java.io.InvalidObjectException("Proxy required");
}
}
--- a/jdk/src/share/classes/java/util/concurrent/atomic/DoubleAdder.java Tue Oct 15 12:53:54 2013 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/atomic/DoubleAdder.java Thu Oct 24 13:24:32 2013 +0100
@@ -210,18 +210,58 @@
return (float)sum();
}
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- s.defaultWriteObject();
- s.writeDouble(sum());
+ /**
+ * Serialization proxy, used to avoid reference to the non-public
+ * Striped64 superclass in serialized forms.
+ * @serial include
+ */
+ private static class SerializationProxy implements Serializable {
+ private static final long serialVersionUID = 7249069246863182397L;
+
+ /**
+ * The current value returned by sum().
+ * @serial
+ */
+ private final double value;
+
+ SerializationProxy(DoubleAdder a) {
+ value = a.sum();
+ }
+
+ /**
+ * Returns a {@code DoubleAdder} object with initial state
+ * held by this proxy.
+ *
+ * @return a {@code DoubleAdder} object with initial state
+ * held by this proxy.
+ */
+ private Object readResolve() {
+ DoubleAdder a = new DoubleAdder();
+ a.base = Double.doubleToRawLongBits(value);
+ return a;
+ }
}
+ /**
+ * Returns a
+ * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.DoubleAdder.SerializationProxy">
+ * SerializationProxy</a>
+ * representing the state of this instance.
+ *
+ * @return a {@link SerializationProxy}
+ * representing the state of this instance
+ */
+ private Object writeReplace() {
+ return new SerializationProxy(this);
+ }
+
+ /**
+ * @param s the stream
+ * @throws java.io.InvalidObjectException always
+ */
private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- s.defaultReadObject();
- cellsBusy = 0;
- cells = null;
- base = Double.doubleToRawLongBits(s.readDouble());
+ throws java.io.InvalidObjectException {
+ throw new java.io.InvalidObjectException("Proxy required");
}
}
--- a/jdk/src/share/classes/java/util/concurrent/atomic/LongAccumulator.java Tue Oct 15 12:53:54 2013 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/atomic/LongAccumulator.java Thu Oct 24 13:24:32 2013 +0100
@@ -221,18 +221,70 @@
return (double)get();
}
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- s.defaultWriteObject();
- s.writeLong(get());
+ /**
+ * Serialization proxy, used to avoid reference to the non-public
+ * Striped64 superclass in serialized forms.
+ * @serial include
+ */
+ private static class SerializationProxy implements Serializable {
+ private static final long serialVersionUID = 7249069246863182397L;
+
+ /**
+ * The current value returned by get().
+ * @serial
+ */
+ private final long value;
+ /**
+ * The function used for updates.
+ * @serial
+ */
+ private final LongBinaryOperator function;
+ /**
+ * The identity value
+ * @serial
+ */
+ private final long identity;
+
+ SerializationProxy(LongAccumulator a) {
+ function = a.function;
+ identity = a.identity;
+ value = a.get();
+ }
+
+ /**
+ * Returns a {@code LongAccumulator} object with initial state
+ * held by this proxy.
+ *
+ * @return a {@code LongAccumulator} object with initial state
+ * held by this proxy.
+ */
+ private Object readResolve() {
+ LongAccumulator a = new LongAccumulator(function, identity);
+ a.base = value;
+ return a;
+ }
}
+ /**
+ * Returns a
+ * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.LongAccumulator.SerializationProxy">
+ * SerializationProxy</a>
+ * representing the state of this instance.
+ *
+ * @return a {@link SerializationProxy}
+ * representing the state of this instance
+ */
+ private Object writeReplace() {
+ return new SerializationProxy(this);
+ }
+
+ /**
+ * @param s the stream
+ * @throws java.io.InvalidObjectException always
+ */
private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- s.defaultReadObject();
- cellsBusy = 0;
- cells = null;
- base = s.readLong();
+ throws java.io.InvalidObjectException {
+ throw new java.io.InvalidObjectException("Proxy required");
}
}
--- a/jdk/src/share/classes/java/util/concurrent/atomic/LongAdder.java Tue Oct 15 12:53:54 2013 +0200
+++ b/jdk/src/share/classes/java/util/concurrent/atomic/LongAdder.java Thu Oct 24 13:24:32 2013 +0100
@@ -211,18 +211,58 @@
return (double)sum();
}
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- s.defaultWriteObject();
- s.writeLong(sum());
+ /**
+ * Serialization proxy, used to avoid reference to the non-public
+ * Striped64 superclass in serialized forms.
+ * @serial include
+ */
+ private static class SerializationProxy implements Serializable {
+ private static final long serialVersionUID = 7249069246863182397L;
+
+ /**
+ * The current value returned by sum().
+ * @serial
+ */
+ private final long value;
+
+ SerializationProxy(LongAdder a) {
+ value = a.sum();
+ }
+
+ /**
+ * Return a {@code LongAdder} object with initial state
+ * held by this proxy.
+ *
+ * @return a {@code LongAdder} object with initial state
+ * held by this proxy.
+ */
+ private Object readResolve() {
+ LongAdder a = new LongAdder();
+ a.base = value;
+ return a;
+ }
}
+ /**
+ * Returns a
+ * <a href="../../../../serialized-form.html#java.util.concurrent.atomic.LongAdder.SerializationProxy">
+ * SerializationProxy</a>
+ * representing the state of this instance.
+ *
+ * @return a {@link SerializationProxy}
+ * representing the state of this instance
+ */
+ private Object writeReplace() {
+ return new SerializationProxy(this);
+ }
+
+ /**
+ * @param s the stream
+ * @throws java.io.InvalidObjectException always
+ */
private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- s.defaultReadObject();
- cellsBusy = 0;
- cells = null;
- base = s.readLong();
+ throws java.io.InvalidObjectException {
+ throw new java.io.InvalidObjectException("Proxy required");
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/concurrent/atomic/Serial.java Thu Oct 24 13:24:32 2013 +0100
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/* @test
+ * @bug 8026344
+ * @summary Exercise classes in j.u.c.atomic that use serialization proxies
+ */
+
+import java.util.concurrent.atomic.DoubleAdder;
+import java.util.concurrent.atomic.DoubleAccumulator;
+import java.util.concurrent.atomic.LongAdder;
+import java.util.concurrent.atomic.LongAccumulator;
+import java.util.function.DoubleBinaryOperator;
+import java.util.function.LongBinaryOperator;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.Serializable;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.IOException;
+
+/**
+ * Basic test to exercise the j.u.c.atomic classes that use serialization
+ * proxies.
+ */
+
+public class Serial {
+
+ public static void main(String[] args) {
+ testDoubleAdder();
+ testDoubleAccumulator();
+ testLongAdder();
+ testLongAccumulator();
+ }
+
+ static void testDoubleAdder() {
+ DoubleAdder a = new DoubleAdder();
+ a.add(20.1d);
+ DoubleAdder result = echo(a);
+ if (result.doubleValue() != a.doubleValue())
+ throw new RuntimeException("Unexpected doubleValue");
+
+ checkSerialClassName(a, "java.util.concurrent.atomic.DoubleAdder$SerializationProxy");
+ }
+
+ static void testDoubleAccumulator() {
+ DoubleBinaryOperator plus = (DoubleBinaryOperator & Serializable) (x, y) -> x + y;
+ DoubleAccumulator a = new DoubleAccumulator(plus, 13.9d);
+ a.accumulate(17.5d);
+ DoubleAccumulator result = echo(a);
+ if (result.get() != a.get())
+ throw new RuntimeException("Unexpected value");
+ a.reset();
+ result.reset();
+ if (result.get() != a.get())
+ throw new RuntimeException("Unexpected value after reset");
+
+ checkSerialClassName(a, "java.util.concurrent.atomic.DoubleAccumulator$SerializationProxy");
+ }
+
+ static void testLongAdder() {
+ LongAdder a = new LongAdder();
+ a.add(45);
+ LongAdder result = echo(a);
+ if (result.longValue() != a.longValue())
+ throw new RuntimeException("Unexpected longValue");
+
+ checkSerialClassName(a, "java.util.concurrent.atomic.LongAdder$SerializationProxy");
+ }
+
+ static void testLongAccumulator() {
+ LongBinaryOperator plus = (LongBinaryOperator & Serializable) (x, y) -> x + y;
+ LongAccumulator a = new LongAccumulator(plus, -2);
+ a.accumulate(34);
+ LongAccumulator result = echo(a);
+ if (result.get() != a.get())
+ throw new RuntimeException("Unexpected value");
+ a.reset();
+ result.reset();
+ if (result.get() != a.get())
+ throw new RuntimeException("Unexpected value after reset");
+
+ checkSerialClassName(a, "java.util.concurrent.atomic.LongAccumulator$SerializationProxy");
+ }
+
+ /**
+ * Serialize the given object, returning the reconstituted object.
+ */
+ @SuppressWarnings("unchecked")
+ static <T extends Serializable> T echo(T obj) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
+ oos.writeObject(obj);
+ } catch (IOException e) {
+ throw new RuntimeException("Serialization failed: " + e);
+ }
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ try (ObjectInputStream ois = new ObjectInputStream(in)) {
+ return (T) ois.readObject();
+ } catch (IOException | ClassNotFoundException e) {
+ throw new RuntimeException("Deserialization failed: " + e);
+ }
+ }
+
+ /**
+ * Checks that the given object serializes to the expected class.
+ */
+ static void checkSerialClassName(Serializable obj, String expected) {
+ String cn = serialClassName(obj);
+ if (!cn.equals(expected))
+ throw new RuntimeException(obj.getClass() + " serialized as " + cn
+ + ", expected " + expected);
+ }
+
+ /**
+ * Returns the class name that the given object serializes as.
+ */
+ static String serialClassName(Serializable obj) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
+ oos.writeObject(obj);
+ } catch (IOException e) {
+ throw new RuntimeException("Serialization failed: " + e);
+ }
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ try (DataInputStream dis = new DataInputStream(in)) {
+ dis.readShort(); // STREAM_MAGIC
+ dis.readShort(); // STREAM_VERSION
+ dis.readByte(); // TC_OBJECT
+ dis.readByte(); // TC_CLASSDESC
+ return dis.readUTF(); // className
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}