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