From 1d0e40b648a0d1c1820fead8adb69e86cbddd004 Mon Sep 17 00:00:00 2001
From: Jerry Lee <oldratlee@gmail.com>
Date: Fri, 13 Dec 2024 19:19:12 +0800
Subject: [PATCH] =?UTF-8?q?safety:=20add=20defensive=20copy=20for=20input?=
 =?UTF-8?q?=20array=20argument=20if=20it=20used=20asynchronously(e.g.=20us?=
 =?UTF-8?q?e=20in=20`thenCompose`),=20not=20thread=20safe=20if=20it=20muta?=
 =?UTF-8?q?tes=20=F0=9F=A7=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../cffu/CompletableFutureUtils.java          | 59 ++++++++++---------
 1 file changed, 31 insertions(+), 28 deletions(-)

diff --git a/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java b/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java
index 58378e08..de00e6f8 100644
--- a/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java
+++ b/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java
@@ -1513,9 +1513,12 @@ public static <T, U> CompletableFuture<List<U>> thenMApplyFailFastAsync(
             CompletableFuture<? extends T> cfThis, Executor executor, Function<? super T, ? extends U>... fns) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("fn", fns);
+        // Implementation Note: create defensive copy of input array argument
+        // since it is used asynchronously in `thenCompose` and could be mutated (NOT thread-safe).
+        // this same defensive copying pattern is used in similar methods below.
+        Function<? super T, ? extends U>[] copy = requireArrayAndEleNonNull("fn", fns).clone();
 
-        return cfThis.thenCompose(v -> allResultsOf0(true, wrapFunctions0(executor, v, fns)));
+        return cfThis.thenCompose(v -> allResultsOf0(true, wrapFunctions0(executor, v, copy)));
     }
 
     /**
@@ -1547,9 +1550,9 @@ public static <T, U> CompletableFuture<List<U>> thenMApplyAllSuccessAsync(
             @Nullable U valueIfFailed, Function<? super T, ? extends U>... fns) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("fn", fns);
+        Function<? super T, ? extends U>[] copy = requireArrayAndEleNonNull("fn", fns);
 
-        return cfThis.thenCompose(v -> allSuccessResultsOf0(valueIfFailed, wrapFunctions0(executor, v, fns)));
+        return cfThis.thenCompose(v -> allSuccessResultsOf0(valueIfFailed, wrapFunctions0(executor, v, copy)));
     }
 
     /**
@@ -1582,10 +1585,10 @@ public static <T, U> CompletableFuture<List<U>> thenMApplyMostSuccessAsync(
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
         requireNonNull(unit, "unit is null");
-        requireArrayAndEleNonNull("fn", fns);
+        Function<? super T, ? extends U>[] copy = requireArrayAndEleNonNull("fn", fns);
 
         return cfThis.thenCompose(v -> mostSuccessResultsOf0(
-                executor, valueIfNotSuccess, timeout, unit, wrapFunctions0(executor, v, fns)));
+                executor, valueIfNotSuccess, timeout, unit, wrapFunctions0(executor, v, copy)));
     }
 
     /**
@@ -1616,9 +1619,9 @@ public static <T, U> CompletableFuture<List<U>> thenMApplyAsync(
             CompletableFuture<? extends T> cfThis, Executor executor, Function<? super T, ? extends U>... fns) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("fn", fns);
+        Function<? super T, ? extends U>[] copy = requireArrayAndEleNonNull("fn", fns);
 
-        return cfThis.thenCompose(v -> allResultsOf0(false, wrapFunctions0(executor, v, fns)));
+        return cfThis.thenCompose(v -> allResultsOf0(false, wrapFunctions0(executor, v, copy)));
     }
 
     /**
@@ -1649,9 +1652,9 @@ public static <T, U> CompletableFuture<U> thenMApplyAnySuccessAsync(
             CompletableFuture<? extends T> cfThis, Executor executor, Function<? super T, ? extends U>... fns) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("fn", fns);
+        Function<? super T, ? extends U>[] copy = requireArrayAndEleNonNull("fn", fns);
 
-        return cfThis.thenCompose(v -> anySuccessOf0(wrapFunctions0(executor, v, fns)));
+        return cfThis.thenCompose(v -> anySuccessOf0(wrapFunctions0(executor, v, copy)));
     }
 
     /**
@@ -1682,9 +1685,9 @@ public static <T, U> CompletableFuture<U> thenMApplyAnyAsync(
             CompletableFuture<? extends T> cfThis, Executor executor, Function<? super T, ? extends U>... fns) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("fn", fns);
+        Function<? super T, ? extends U>[] copy = requireArrayAndEleNonNull("fn", fns);
 
-        return cfThis.thenCompose(v -> f_cast(CompletableFuture.anyOf(wrapFunctions0(executor, v, fns))));
+        return cfThis.thenCompose(v -> f_cast(CompletableFuture.anyOf(wrapFunctions0(executor, v, copy))));
     }
 
     private static <T, U> CompletableFuture<U>[] wrapFunctions0(
@@ -1725,9 +1728,9 @@ public static <T> CompletableFuture<Void> thenMAcceptFailFastAsync(
             CompletableFuture<? extends T> cfThis, Executor executor, Consumer<? super T>... actions) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("action", actions);
+        Consumer<? super T>[] copy = requireArrayAndEleNonNull("action", actions);
 
-        return cfThis.thenCompose(v -> allFailFastOf0(wrapConsumers0(executor, v, actions)));
+        return cfThis.thenCompose(v -> allFailFastOf0(wrapConsumers0(executor, v, copy)));
     }
 
     /**
@@ -1763,9 +1766,9 @@ public static <T> CompletableFuture<Void> thenMAcceptAsync(
             CompletableFuture<? extends T> cfThis, Executor executor, Consumer<? super T>... actions) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("action", actions);
+        Consumer<? super T>[] copy = requireArrayAndEleNonNull("action", actions);
 
-        return cfThis.thenCompose(v -> CompletableFuture.allOf(wrapConsumers0(executor, v, actions)));
+        return cfThis.thenCompose(v -> CompletableFuture.allOf(wrapConsumers0(executor, v, copy)));
     }
 
     /**
@@ -1801,9 +1804,9 @@ public static <T> CompletableFuture<Void> thenMAcceptAnySuccessAsync(
             CompletableFuture<? extends T> cfThis, Executor executor, Consumer<? super T>... actions) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("action", actions);
+        Consumer<? super T>[] copy = requireArrayAndEleNonNull("action", actions);
 
-        return cfThis.thenCompose(v -> anySuccessOf0(wrapConsumers0(executor, v, actions)));
+        return cfThis.thenCompose(v -> anySuccessOf0(wrapConsumers0(executor, v, copy)));
     }
 
     /**
@@ -1839,9 +1842,9 @@ public static <T> CompletableFuture<Void> thenMAcceptAnyAsync(
             CompletableFuture<? extends T> cfThis, Executor executor, Consumer<? super T>... actions) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("action", actions);
+        Consumer<? super T>[] copy = requireArrayAndEleNonNull("action", actions);
 
-        return cfThis.thenCompose(v -> f_cast(CompletableFuture.anyOf(wrapConsumers0(executor, v, actions))));
+        return cfThis.thenCompose(v -> f_cast(CompletableFuture.anyOf(wrapConsumers0(executor, v, copy))));
     }
 
     private static <T> CompletableFuture<Void>[] wrapConsumers0(Executor executor, T v, Consumer<? super T>[] actions) {
@@ -1868,9 +1871,9 @@ public static CompletableFuture<Void> thenMRunFailFastAsync(
             CompletableFuture<?> cfThis, Executor executor, Runnable... actions) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("action", actions);
+        Runnable[] copy = requireArrayAndEleNonNull("action", actions);
 
-        return cfThis.thenCompose(unused -> allFailFastOf0(wrapRunnables0(executor, actions)));
+        return cfThis.thenCompose(unused -> allFailFastOf0(wrapRunnables0(executor, copy)));
     }
 
     /**
@@ -1893,9 +1896,9 @@ public static CompletableFuture<Void> thenMRunAsync(
             CompletableFuture<?> cfThis, Executor executor, Runnable... actions) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("action", actions);
+        Runnable[] copy = requireArrayAndEleNonNull("action", actions);
 
-        return cfThis.thenCompose(unused -> CompletableFuture.allOf(wrapRunnables0(executor, actions)));
+        return cfThis.thenCompose(unused -> CompletableFuture.allOf(wrapRunnables0(executor, copy)));
     }
 
     /**
@@ -1918,9 +1921,9 @@ public static CompletableFuture<Void> thenMRunAnySuccessAsync(
             CompletableFuture<?> cfThis, Executor executor, Runnable... actions) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("action", actions);
+        Runnable[] copy = requireArrayAndEleNonNull("action", actions);
 
-        return cfThis.thenCompose(unused -> anySuccessOf0(wrapRunnables0(executor, actions)));
+        return cfThis.thenCompose(unused -> anySuccessOf0(wrapRunnables0(executor, copy)));
     }
 
     /**
@@ -1943,9 +1946,9 @@ public static CompletableFuture<Void> thenMRunAnyAsync(
             CompletableFuture<?> cfThis, Executor executor, Runnable... actions) {
         requireNonNull(cfThis, "cfThis is null");
         requireNonNull(executor, "executor is null");
-        requireArrayAndEleNonNull("action", actions);
+        Runnable[] copy = requireArrayAndEleNonNull("action", actions);
 
-        return cfThis.thenCompose(unused -> f_cast(CompletableFuture.anyOf(wrapRunnables0(executor, actions))));
+        return cfThis.thenCompose(unused -> f_cast(CompletableFuture.anyOf(wrapRunnables0(executor, copy))));
     }
 
     // endregion