From c7afbeb4bd67d7ce4d9ecacfce729382df312c79 Mon Sep 17 00:00:00 2001 From: Junnie18 Date: Thu, 17 Sep 2026 02:21:07 -0400 Subject: [PATCH] Fix ArrayStoreException in ObjectArrays.concat(element, T[]) and concat(T[], element). Both single-element concat overloads built the result array with the same runtime component type as the input array (via newArray/Arrays.copyOf), then stored the new element into it directly. When the element's runtime type wasn't assignable to that component type (e.g. concatenating a String[] with an Integer, both erased to Object at the call site), storing the element threw ArrayStoreException instead of succeeding, unlike concat(T[], T[], Class), which never has this problem since it builds its result array from an explicit Class. Now each method checks whether the element is storable in an array of the same type as the input before allocating; if not, it falls back to a plain Object[] (unchecked-cast back to T[], mirroring the existing unsoundlyCovariantArray pattern used elsewhere in this file), so no exception is thrown. Fixes https://github.com/google/guava/issues/3768 --- .../common/collect/ObjectArraysTest.java | 24 ++++++++++++++++++ .../google/common/collect/ObjectArrays.java | 25 ++++++++++++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/guava-tests/test/com/google/common/collect/ObjectArraysTest.java b/guava-tests/test/com/google/common/collect/ObjectArraysTest.java index a8e7effdaefd..7a5ea92cf2c8 100644 --- a/guava-tests/test/com/google/common/collect/ObjectArraysTest.java +++ b/guava-tests/test/com/google/common/collect/ObjectArraysTest.java @@ -194,6 +194,30 @@ public void testAppendTwoElements() { assertThat(array).asList().containsExactly("foo", "bar", "baz").inOrder(); } + public void testPrependZeroElements_withIncompatibleElementType() { + String[] array = {}; + Object[] result = ObjectArrays.concat(0, array); + assertThat(result).asList().containsExactly(0); + } + + public void testPrependOneElement_withIncompatibleElementType() { + String[] array = {"bar"}; + Object[] result = ObjectArrays.concat(0, array); + assertThat(result).asList().containsExactly(0, "bar").inOrder(); + } + + public void testAppendZeroElements_withIncompatibleElementType() { + String[] array = {}; + Object[] result = ObjectArrays.concat(array, 0); + assertThat(result).asList().containsExactly(0); + } + + public void testAppendOneElement_withIncompatibleElementType() { + String[] array = {"foo"}; + Object[] result = ObjectArrays.concat(array, 0); + assertThat(result).asList().containsExactly("foo", 0).inOrder(); + } + public void testEmptyArrayToEmpty() { doTestNewArrayEquals(new Object[0], 0); } diff --git a/guava/src/com/google/common/collect/ObjectArrays.java b/guava/src/com/google/common/collect/ObjectArrays.java index 3d02072e91d8..aa7da4edb6ab 100644 --- a/guava/src/com/google/common/collect/ObjectArrays.java +++ b/guava/src/com/google/common/collect/ObjectArrays.java @@ -93,10 +93,18 @@ private ObjectArrays() {} * the first position, and the elements of {@code array} occupying the remaining elements. */ public static T[] concat(@ParametricNullness T element, T[] array) { - T[] result = newArray(array, array.length + 1); + Object[] result; + if (element == null || array.getClass().getComponentType().isInstance(element)) { + result = newArray(array, array.length + 1); + } else { + result = new Object[array.length + 1]; + } result[0] = element; arraycopy(array, 0, result, 1, array.length); - return result; + @SuppressWarnings( + "unchecked") // safe: result's component type accepts `element` and all of `array` + T[] typedResult = (T[]) result; + return typedResult; } /** @@ -108,9 +116,18 @@ private ObjectArrays() {} * array}, plus {@code element} occupying the last position. */ public static T[] concat(T[] array, @ParametricNullness T element) { - T[] result = Arrays.copyOf(array, array.length + 1); + Object[] result; + if (element == null || array.getClass().getComponentType().isInstance(element)) { + result = Arrays.copyOf(array, array.length + 1); + } else { + result = new Object[array.length + 1]; + arraycopy(array, 0, result, 0, array.length); + } result[array.length] = element; - return result; + @SuppressWarnings( + "unchecked") // safe: result's component type accepts `element` and all of `array` + T[] typedResult = (T[]) result; + return typedResult; } /**