From 8a783597241852700a14491c4cf990d791fa4934 Mon Sep 17 00:00:00 2001
From: Georgios Andrianakis <geoand@gmail.com>
Date: Mon, 11 Apr 2022 12:54:32 +0300
Subject: [PATCH] Avoid XStream causing illegal access issues for internal JDK
 collections

Relates to: #24492
---
 .../junit/internal/CustomListConverter.java   | 43 +++++++++++++++++++
 .../junit/internal/CustomSetConverter.java    | 40 +++++++++++++++++
 .../test/junit/internal/XStreamDeepClone.java |  3 +-
 3 files changed, 85 insertions(+), 1 deletion(-)
 create mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java
 create mode 100644 test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java

diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java
new file mode 100644
index 00000000000000..f934c0fd661288
--- /dev/null
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomListConverter.java
@@ -0,0 +1,43 @@
+package io.quarkus.test.junit.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import com.thoughtworks.xstream.converters.collections.CollectionConverter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * A custom List converter that always uses ArrayList for unmarshalling.
+ * This is probably not semantically correct 100% of the time, but it's likely fine
+ * for all the cases where we are using marshalling / unmarshalling.
+ *
+ * The reason for doing this is to avoid XStream causing illegal access issues
+ * for internal JDK lists
+ */
+public class CustomListConverter extends CollectionConverter {
+
+    // if we wanted to be 100% sure, we'd list all the List.of methods, but I think it's pretty safe to say
+    // that the JDK won't add custom implementations for the other classes
+    private final Set<String> SUPPORTED_CLASS_NAMES = Set.of(
+            List.of().getClass().getName(),
+            List.of(new Object()).getClass().getName(),
+            Arrays.asList(new Object()).getClass().getName(),
+            Collections.emptyList().getClass().getName());
+
+    public CustomListConverter(Mapper mapper) {
+        super(mapper);
+    }
+
+    @Override
+    public boolean canConvert(Class type) {
+        return (type != null) && SUPPORTED_CLASS_NAMES.contains(type.getName());
+    }
+
+    @Override
+    protected Object createCollection(Class type) {
+        return new ArrayList<>();
+    }
+}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java
new file mode 100644
index 00000000000000..5510ceefdc4638
--- /dev/null
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/CustomSetConverter.java
@@ -0,0 +1,40 @@
+package io.quarkus.test.junit.internal;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.thoughtworks.xstream.converters.collections.CollectionConverter;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * A custom Set converter that always uses HashSet for unmarshalling.
+ * This is probably not semantically correct 100% of the time, but it's likely fine
+ * for all the cases where we are using marshalling / unmarshalling.
+ *
+ * The reason for doing this is to avoid XStream causing illegal access issues
+ * for internal JDK sets
+ */
+public class CustomSetConverter extends CollectionConverter {
+
+    // if we wanted to be 100% sure, we'd list all the Set.of methods, but I think it's pretty safe to say
+    // that the JDK won't add custom implementations for the other classes
+    private final Set<String> SUPPORTED_CLASS_NAMES = Set.of(
+            Set.of().getClass().getName(),
+            Set.of(new Object()).getClass().getName(),
+            Collections.emptySet().getClass().getName());
+
+    public CustomSetConverter(Mapper mapper) {
+        super(mapper);
+    }
+
+    @Override
+    public boolean canConvert(Class type) {
+        return (type != null) && SUPPORTED_CLASS_NAMES.contains(type.getName());
+    }
+
+    @Override
+    protected Object createCollection(Class type) {
+        return new HashSet<>();
+    }
+}
diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java
index dc945e5bf7f7c4..a200e0a8412659 100644
--- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java
+++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/internal/XStreamDeepClone.java
@@ -15,9 +15,10 @@ class XStreamDeepClone implements DeepClone {
         // avoid doing any work eagerly since the cloner is rarely used
         xStreamSupplier = () -> {
             XStream result = new XStream();
-            XStream.setupDefaultSecurity(result);
             result.allowTypesByRegExp(new String[] { ".*" });
             result.setClassLoader(classLoader);
+            result.registerConverter(new CustomListConverter(result.getMapper()));
+            result.registerConverter(new CustomSetConverter(result.getMapper()));
             return result;
         };
     }