From fbc33babbe08561c686363e4dc61dd216aa6c483 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Sat, 14 Dec 2024 18:52:04 +0800 Subject: [PATCH] =?UTF-8?q?safety:=20defensive=20copy=20input=20array=20ar?= =?UTF-8?q?gument=20when=20it=20used=20asynchronously(e.g.=20use=20in=20`t?= =?UTF-8?q?henCompose`),=20not=20thread-safe,=20it=20could=20be=20mutated?= =?UTF-8?q?=20by=20caller=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 4d2954c1..d850b5fb 100644 --- a/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java +++ b/cffu-core/src/main/java/io/foldright/cffu/CompletableFutureUtils.java @@ -1512,9 +1512,12 @@ public static CompletableFuture> thenMApplyFailFastAsync( CompletableFuture cfThis, Executor executor, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + // Defensive copy input array argument: + // since it is used asynchronously in `thenCompose` and could be mutated by caller (NOT thread-safe) + // this same defensive copying pattern is used in similar methods below. + Function[] 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))); } /** @@ -1546,9 +1549,9 @@ public static CompletableFuture> thenMApplyAllSuccessAsync( @Nullable U valueIfFailed, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns).clone(); - return cfThis.thenCompose(v -> allSuccessResultsOf0(valueIfFailed, wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> allSuccessResultsOf0(valueIfFailed, wrapFunctions0(executor, v, copy))); } /** @@ -1581,10 +1584,10 @@ public static CompletableFuture> thenMApplyMostSuccessAsync( requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); requireNonNull(unit, "unit is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns).clone(); return cfThis.thenCompose(v -> mostSuccessResultsOf0( - executor, valueIfNotSuccess, timeout, unit, wrapFunctions0(executor, v, fns))); + executor, valueIfNotSuccess, timeout, unit, wrapFunctions0(executor, v, copy))); } /** @@ -1615,9 +1618,9 @@ public static CompletableFuture> thenMApplyAsync( CompletableFuture cfThis, Executor executor, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns).clone(); - return cfThis.thenCompose(v -> allResultsOf0(false, wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> allResultsOf0(false, wrapFunctions0(executor, v, copy))); } /** @@ -1648,9 +1651,9 @@ public static CompletableFuture thenMApplyAnySuccessAsync( CompletableFuture cfThis, Executor executor, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns).clone(); - return cfThis.thenCompose(v -> anySuccessOf0(wrapFunctions0(executor, v, fns))); + return cfThis.thenCompose(v -> anySuccessOf0(wrapFunctions0(executor, v, copy))); } /** @@ -1681,9 +1684,9 @@ public static CompletableFuture thenMApplyAnyAsync( CompletableFuture cfThis, Executor executor, Function... fns) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("fn", fns); + Function[] copy = requireArrayAndEleNonNull("fn", fns).clone(); - 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 CompletableFuture[] wrapFunctions0( @@ -1724,9 +1727,9 @@ public static CompletableFuture thenMAcceptFailFastAsync( CompletableFuture cfThis, Executor executor, Consumer... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Consumer[] copy = requireArrayAndEleNonNull("action", actions).clone(); - return cfThis.thenCompose(v -> allFailFastOf0(wrapConsumers0(executor, v, actions))); + return cfThis.thenCompose(v -> allFailFastOf0(wrapConsumers0(executor, v, copy))); } /** @@ -1762,9 +1765,9 @@ public static CompletableFuture thenMAcceptAsync( CompletableFuture cfThis, Executor executor, Consumer... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Consumer[] copy = requireArrayAndEleNonNull("action", actions).clone(); - return cfThis.thenCompose(v -> CompletableFuture.allOf(wrapConsumers0(executor, v, actions))); + return cfThis.thenCompose(v -> CompletableFuture.allOf(wrapConsumers0(executor, v, copy))); } /** @@ -1800,9 +1803,9 @@ public static CompletableFuture thenMAcceptAnySuccessAsync( CompletableFuture cfThis, Executor executor, Consumer... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Consumer[] copy = requireArrayAndEleNonNull("action", actions).clone(); - return cfThis.thenCompose(v -> anySuccessOf0(wrapConsumers0(executor, v, actions))); + return cfThis.thenCompose(v -> anySuccessOf0(wrapConsumers0(executor, v, copy))); } /** @@ -1838,9 +1841,9 @@ public static CompletableFuture thenMAcceptAnyAsync( CompletableFuture cfThis, Executor executor, Consumer... actions) { requireNonNull(cfThis, "cfThis is null"); requireNonNull(executor, "executor is null"); - requireArrayAndEleNonNull("action", actions); + Consumer[] copy = requireArrayAndEleNonNull("action", actions).clone(); - 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 CompletableFuture[] wrapConsumers0(Executor executor, T v, Consumer[] actions) { @@ -1867,9 +1870,9 @@ public static CompletableFuture 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).clone(); - return cfThis.thenCompose(unused -> allFailFastOf0(wrapRunnables0(executor, actions))); + return cfThis.thenCompose(unused -> allFailFastOf0(wrapRunnables0(executor, copy))); } /** @@ -1892,9 +1895,9 @@ public static CompletableFuture 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).clone(); - return cfThis.thenCompose(unused -> CompletableFuture.allOf(wrapRunnables0(executor, actions))); + return cfThis.thenCompose(unused -> CompletableFuture.allOf(wrapRunnables0(executor, copy))); } /** @@ -1917,9 +1920,9 @@ public static CompletableFuture 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).clone(); - return cfThis.thenCompose(unused -> anySuccessOf0(wrapRunnables0(executor, actions))); + return cfThis.thenCompose(unused -> anySuccessOf0(wrapRunnables0(executor, copy))); } /** @@ -1942,9 +1945,9 @@ public static CompletableFuture 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).clone(); - return cfThis.thenCompose(unused -> f_cast(CompletableFuture.anyOf(wrapRunnables0(executor, actions)))); + return cfThis.thenCompose(unused -> f_cast(CompletableFuture.anyOf(wrapRunnables0(executor, copy)))); } // endregion