LOG4J2-1663 Ensure SortedArrayStringMap can be serialized and deserialized 
without errors regardless of content


Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/4df78d0a
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/4df78d0a
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/4df78d0a

Branch: refs/heads/LOG4J2-1661
Commit: 4df78d0ad5343593ff1a8b929d277d1f17f3f30f
Parents: dee36ee
Author: rpopma <[email protected]>
Authored: Sat Nov 5 22:11:12 2016 +0900
Committer: rpopma <[email protected]>
Committed: Sat Nov 5 22:11:12 2016 +0900

----------------------------------------------------------------------
 .../log4j/util/SortedArrayStringMap.java        | 23 ++++++-
 .../logging/log4j/util/DeserializerHelper.java  | 47 +++++++++++++
 .../log4j/util/SortedArrayStringMapTest.java    | 71 +++++++++++++++++++-
 src/changes/changes.xml                         |  3 +
 4 files changed, 140 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/4df78d0a/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java
index 07f8f0b..7b602b8 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java
@@ -18,12 +18,15 @@ package org.apache.logging.log4j.util;
 
 import java.io.IOException;
 import java.io.InvalidObjectException;
+import java.rmi.MarshalledObject;
 import java.util.Arrays;
 import java.util.ConcurrentModificationException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 
+import org.apache.logging.log4j.status.StatusLogger;
+
 /**
  * <em>Consider this class private.</em>
  * Array-based implementation of the {@code ReadOnlyStringMap} interface. Keys 
are held in a sorted array.
@@ -468,12 +471,16 @@ public class SortedArrayStringMap implements StringMap {
         if (size > 0) {
             for (int i = 0; i < size; i++) {
                 s.writeObject(keys[i]);
-                s.writeObject(values[i]);
+                try {
+                    s.writeObject(new MarshalledObject<>(values[i]));
+                } catch (final Exception e) {
+                    handleSerializationException(e, i, keys[i]);
+                    s.writeObject(null);
+                }
             }
         }
     }
 
-
     /**
      * Calculate the next power of 2, greater than or equal to x.
      * <p>
@@ -521,8 +528,18 @@ public class SortedArrayStringMap implements StringMap {
         // Read the keys and values, and put the mappings in the arrays
         for (int i = 0; i < mappings; i++) {
             keys[i] = (String) s.readObject();
-            values[i] = s.readObject();
+            try {
+                final MarshalledObject<Object> marshalledObject = 
(MarshalledObject<Object>) s.readObject();
+                values[i] = marshalledObject == null ? null : 
marshalledObject.get();
+            } catch (final Exception | LinkageError error) {
+                handleSerializationException(error, i, keys[i]);
+                values[i] = null;
+            }
         }
         size = mappings;
     }
+
+    private void handleSerializationException(final Throwable t, final int i, 
final String key) {
+        StatusLogger.getLogger().warn("Ignoring {} for key[{}] ('{}')", 
String.valueOf(t), i, keys[i]);
+    }
 }

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/4df78d0a/log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java 
b/log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java
new file mode 100644
index 0000000..1fc8307
--- /dev/null
+++ 
b/log4j-api/src/test/java/org/apache/logging/log4j/util/DeserializerHelper.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.logging.log4j.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.ObjectInputStream;
+
+/**
+ * Deserializes a specified file.
+ *
+ * @see SortedArrayStringMapTest#testDeserializationOfUnknownClass()
+ */
+public class DeserializerHelper {
+    public static void main(String... args) throws Exception {
+        final File file = new File(args[0]);
+        ObjectInputStream in = null;
+        try {
+            in = new ObjectInputStream(new FileInputStream(file));
+            final Object result = in.readObject();
+            System.out.println(result);
+        } catch (Throwable t) {
+            System.err.println("Could not deserialize: ");
+            t.printStackTrace();
+        } finally {
+            try {
+                in.close();
+            } catch (Throwable t) {
+                System.err.println("Error while closing: " + t);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/4df78d0a/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java
 
b/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java
index f4f0710..58386d2 100644
--- 
a/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java
+++ 
b/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java
@@ -1,4 +1,4 @@
-package org.apache.logging.log4j.util;/*
+/*
  * Licensed to the Apache Software Foundation (ASF) under one or more
  * contributor license agreements. See the NOTICE file distributed with
  * this work for additional information regarding copyright ownership.
@@ -14,13 +14,17 @@ package org.apache.logging.log4j.util;/*
  * See the license for the specific language governing permissions and
  * limitations under the license.
  */
+package org.apache.logging.log4j.util;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.lang.reflect.Field;
+import java.net.URL;
 import java.util.ConcurrentModificationException;
 import java.util.HashMap;
 import java.util.Map;
@@ -70,6 +74,71 @@ public class SortedArrayStringMapTest {
         assertEquals(original, copy);
     }
 
+    @Test
+    public void testSerializationOfNonSerializableValue() throws Exception {
+        final SortedArrayStringMap original = new SortedArrayStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("B", "Bvalue");
+        original.putValue("unserializable", new Object());
+
+        final byte[] binary = serialize(original);
+        final SortedArrayStringMap copy = deserialize(binary);
+
+        final SortedArrayStringMap expected = new SortedArrayStringMap();
+        expected.putValue("a", "avalue");
+        expected.putValue("B", "Bvalue");
+        expected.putValue("unserializable", null);
+        assertEquals(expected, copy);
+    }
+
+    @Test
+    public void testDeserializationOfUnknownClass() throws Exception {
+        final SortedArrayStringMap original = new SortedArrayStringMap();
+        original.putValue("a", "avalue");
+        original.putValue("serializableButNotInClasspathOfDeserializer", new 
org.junit.runner.Result());
+        original.putValue("zz", "last");
+
+        final File file = new File("target/SortedArrayStringMap.ser");
+        try (FileOutputStream fout = new FileOutputStream(file, false)) {
+            fout.write(serialize(original));
+            fout.flush();
+        }
+        final Process process = new ProcessBuilder("java", "-cp",
+                createClassPath(SortedArrayStringMap.class, 
DeserializerHelper.class),
+                DeserializerHelper.class.getName(), 
file.getPath()).inheritIO().start();
+        int exitValue = process.waitFor();
+
+//        file.delete();
+        assertEquals("no error", 0, exitValue);
+    }
+
+    private String createClassPath(Class<?>... classes) {
+        final StringBuilder result = new StringBuilder();
+        for (final Class<?> cls : classes) {
+            if (result.length() > 0) {
+                result.append(File.pathSeparator);
+            }
+            result.append(createClassPath(cls));
+        }
+        return result.toString();
+    }
+
+    private String createClassPath(Class<?> cls) {
+        final String resource = "/" + cls.getName().replace('.', '/') + 
".class";
+        final URL url = cls.getResource(resource);
+        String location = url.toString();
+        if (location.startsWith("jar:")) {
+            location = location.substring("jar:".length(), 
location.indexOf('!'));
+        }
+        if (location.startsWith("file:/")) {
+            location = location.substring("file:/".length());
+        }
+        if (location.endsWith(resource)) {
+            location = location.substring(0, location.length() - 
resource.length());
+        }
+        return location.isEmpty() ? "." : location;
+    }
+
     private byte[] serialize(final SortedArrayStringMap data) throws 
IOException {
         final ByteArrayOutputStream arr = new ByteArrayOutputStream();
         final ObjectOutputStream out = new ObjectOutputStream(arr);

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/4df78d0a/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d017257..f2b9fa5 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -24,6 +24,9 @@
   </properties>
   <body>
     <release version="2.8" date="2016-MM-DD" description="GA Release 2.8">
+      <action issue="LOG4J2-1663" dev="rpopma" type="fix">
+        Ensure SortedArrayStringMap can be serialized and deserialized without 
errors regardless of content.
+      </action>
       <action issue="LOG4J2-1660" dev="rpopma" type="add">
         Added public method ThreadContext::getThreadContextMap; removed class 
ThreadContextAccess.
       </action>

Reply via email to