diff --git a/core/src/main/java/org/projectnessie/cel/common/types/BytesT.java b/core/src/main/java/org/projectnessie/cel/common/types/BytesT.java index 0b1fa188..1a105601 100644 --- a/core/src/main/java/org/projectnessie/cel/common/types/BytesT.java +++ b/core/src/main/java/org/projectnessie/cel/common/types/BytesT.java @@ -50,21 +50,21 @@ public final class BytesT extends BaseVal implements Adder, Comparer, Sizer { TypeT.newTypeValue(TypeEnum.Bytes, Trait.AdderType, Trait.ComparerType, Trait.SizerType); public static BytesT bytesOf(byte[] b) { - return new BytesT(b); + return new BytesT(b, true); } public static Val bytesOf(ByteString value) { - return bytesOf(value.toByteArray()); + return new BytesT(value.toByteArray(), false); } public static BytesT bytesOf(String s) { - return new BytesT(s.getBytes(StandardCharsets.UTF_8)); + return new BytesT(s.getBytes(StandardCharsets.UTF_8), false); } private final byte[] b; - private BytesT(byte[] b) { - this.b = b; + private BytesT(byte[] b, boolean copy) { + this.b = copy ? Arrays.copyOf(b, b.length) : b; } /** Add implements traits.Adder interface method by concatenating byte sequences. */ @@ -76,7 +76,7 @@ public Val add(Val other) { byte[] o = ((BytesT) other).b; byte[] n = Arrays.copyOf(b, b.length + o.length); System.arraycopy(o, 0, n, b.length, o.length); - return bytesOf(n); + return new BytesT(n, false); } /** Compare implments traits.Comparer interface method by lexicographic ordering. */ @@ -109,7 +109,7 @@ public T convertToNative(Class typeDesc) { return (T) ByteString.copyFrom(this.b); } if (typeDesc == byte[].class) { - return (T) b; + return (T) Arrays.copyOf(b, b.length); } if (typeDesc == String.class) { try { @@ -125,7 +125,7 @@ public T convertToNative(Class typeDesc) { return (T) BytesValue.of(ByteString.copyFrom(this.b)); } if (typeDesc == ByteBuffer.class) { - return (T) ByteBuffer.wrap(this.b); + return (T) ByteBuffer.wrap(Arrays.copyOf(this.b, this.b.length)).asReadOnlyBuffer(); } if (typeDesc == Val.class || typeDesc == BytesT.class) { return (T) this; @@ -188,7 +188,7 @@ public Type type() { /** Value implements the ref.Val interface method. */ @Override public Object value() { - return b; + return Arrays.copyOf(b, b.length); } @Override diff --git a/core/src/test/java/org/projectnessie/cel/common/types/BytesTest.java b/core/src/test/java/org/projectnessie/cel/common/types/BytesTest.java index 44d46815..5494ea72 100644 --- a/core/src/test/java/org/projectnessie/cel/common/types/BytesTest.java +++ b/core/src/test/java/org/projectnessie/cel/common/types/BytesTest.java @@ -16,6 +16,7 @@ package org.projectnessie.cel.common.types; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.projectnessie.cel.common.types.BoolT.True; import static org.projectnessie.cel.common.types.BytesT.BytesType; import static org.projectnessie.cel.common.types.BytesT.bytesOf; @@ -33,6 +34,7 @@ import com.google.protobuf.BytesValue; import com.google.protobuf.Value; import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import org.projectnessie.cel.common.types.ref.Val; @@ -78,6 +80,15 @@ void bytesConvertToNative_ByteBuffer() { assertThat(val).isEqualTo(ByteBuffer.wrap(new byte[] {49, 50, 51})); } + @Test + void bytesConvertToNative_ByteBufferReadOnlyCopy() { + BytesT bytes = bytesOf("123"); + ByteBuffer val = bytes.convertToNative(ByteBuffer.class); + assertThat(val.isReadOnly()).isTrue(); + assertThatThrownBy(() -> val.put(0, (byte) 57)).isInstanceOf(ReadOnlyBufferException.class); + assertThat(bytes).isEqualTo(bytesOf("123")); + } + @Test void bytesConvertToNative_Error() { assertThat(bytesOf("123").convertToNative(String.class)).isEqualTo("123"); @@ -97,6 +108,29 @@ void bytesConvertToNative_Wrapper() { assertThat(val).containsExactly(want); } + @Test + void bytesDefensivelyCopiesInputArray() { + byte[] input = new byte[] {49, 50, 51}; + BytesT bytes = bytesOf(input); + input[0] = 57; + assertThat(bytes).isEqualTo(bytesOf("123")); + } + + @Test + void bytesDefensivelyCopiesOutputArrays() { + BytesT bytes = bytesOf("123"); + + byte[] nativeBytes = bytes.convertToNative(byte[].class); + nativeBytes[0] = 57; + + byte[] valueBytes = (byte[]) bytes.value(); + valueBytes[1] = 57; + + assertThat(bytes).isEqualTo(bytesOf("123")); + assertThat(bytes.convertToNative(byte[].class)).containsExactly(49, 50, 51); + assertThat((byte[]) bytes.value()).containsExactly(49, 50, 51); + } + @Test void bytesConvertToType() { assertThat(bytesOf("hello world").convertToType(BytesType).equal(bytesOf("hello world")))