| /* |
| * Copyright (C) 2026 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include <wtf/EscapableByteSpan.h> |
| |
| #include "Helpers/Test.h" |
| #include <optional> |
| #include <wtf/Vector.h> |
| |
| // These tests exercise the C++ side of the byte views: the ~Escapable Borrowed* pair, |
| // and the Escapable* types with the Swift reference count that outlives-the-borrow |
| // bugs trip over. The Swift-side guarantees (that a ~Escapable view cannot be stored |
| // or outlive its call) are enforced by the compiler and so are not testable here. |
| |
| namespace TestWebKitAPI { |
| |
| #if ASSERT_ENABLED |
| #define EXPECTED_BORROW_CRASH "ASSERTION FAILED" |
| #else |
| #define EXPECTED_BORROW_CRASH "" |
| #endif |
| |
| // MARK: - EscapableByteSpan |
| |
| TEST(WTF_EscapableByteSpan, ExposesBytes) |
| { |
| Vector<uint8_t> source { 10, 20, 30, 40, 50 }; |
| auto span = source.span(); |
| EscapableByteSpan bytes(span); |
| |
| EXPECT_EQ(bytes.size(), span.size()); |
| EXPECT_EQ(bytes.span().data(), span.data()); |
| EXPECT_EQ(bytes.span().size(), span.size()); |
| for (size_t i = 0; i < span.size(); ++i) |
| EXPECT_EQ(bytes.span()[i], span[i]); |
| } |
| |
| TEST(WTF_EscapableByteSpan, OverSubspan) |
| { |
| Vector<uint8_t> source { 0, 1, 2, 3, 4, 5 }; |
| auto subspan = source.span().subspan(2, 3); |
| EscapableByteSpan bytes(subspan); |
| |
| EXPECT_EQ(bytes.size(), 3u); |
| EXPECT_EQ(bytes.span().data(), subspan.data()); |
| EXPECT_EQ(bytes.span()[0], 2u); |
| EXPECT_EQ(bytes.span()[2], 4u); |
| } |
| |
| TEST(WTF_EscapableByteSpan, VectorBytesExposesBytes) |
| { |
| Vector<uint8_t> vector { 1, 2, 3, 4 }; |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| |
| EXPECT_EQ(bytes.size(), vector.size()); |
| EXPECT_EQ(bytes.span().data(), vector.span().data()); |
| for (size_t i = 0; i < vector.size(); ++i) |
| EXPECT_EQ(bytes.span()[i], vector[i]); |
| } |
| |
| TEST(WTF_EscapableByteSpan, EmptySpan) |
| { |
| EscapableByteSpan bytes(std::span<const uint8_t> { }); |
| EXPECT_EQ(bytes.size(), 0u); |
| } |
| |
| TEST(WTF_EscapableByteSpan, EmptyVector) |
| { |
| Vector<uint8_t> vector; |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| EXPECT_EQ(bytes.size(), 0u); |
| } |
| |
| // A transient copy, as Swift makes for the duration of a synchronous call, is fine as |
| // long as it is destroyed before the borrow ends. Copy construction and destruction are |
| // what Swift's value copies land on. |
| TEST(WTF_EscapableByteSpan, TransientCopyIsFine) |
| { |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| |
| { |
| EscapableByteSpan copy(bytes); |
| EXPECT_EQ(copy.size(), vector.size()); |
| EXPECT_EQ(copy.span().data(), vector.span().data()); |
| |
| // A copy of a copy still counts against the same root. |
| EscapableByteSpan copyOfCopy(copy); |
| EXPECT_EQ(copyOfCopy.span().data(), vector.span().data()); |
| } |
| |
| EXPECT_EQ(bytes.size(), vector.size()); |
| } |
| |
| // Nested borrows of the same Vector are permitted; the inner borrow restores |
| // the outer borrow's state on destruction. |
| TEST(WTF_EscapableByteSpan, NestedVectorBorrows) |
| { |
| Vector<uint8_t> vector { 9, 8, 7 }; |
| auto outerBorrow = borrow(vector); |
| EscapableByteSpan outer = escapableSpan(outerBorrow->span()); |
| { |
| auto innerBorrow = borrow(vector); |
| EscapableByteSpan inner = escapableSpan(innerBorrow->span()); |
| EXPECT_EQ(inner.span().data(), vector.span().data()); |
| } |
| EXPECT_EQ(outer.size(), vector.size()); |
| } |
| |
| TEST(WTF_EscapableByteSpan, ViewIdentityIsPerBorrow) |
| { |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| auto outerBorrow = borrow(vector); |
| EscapableByteSpan outer = escapableSpan(outerBorrow->span()); |
| EXPECT_EQ(&outer, &outer); |
| |
| { |
| auto innerBorrow = borrow(vector); |
| EscapableByteSpan inner = escapableSpan(innerBorrow->span()); |
| EXPECT_NE(&inner, &outer); |
| EscapableByteSpan copy(inner); |
| EXPECT_EQ(copy.span().data(), vector.span().data()); |
| } |
| |
| // The inner borrow ended, but the outer view is untouched and still readable. |
| EXPECT_EQ(outer.span().data(), vector.span().data()); |
| EXPECT_EQ(outer.size(), vector.size()); |
| } |
| |
| // MARK: - The stash check |
| // |
| // A copy stashed beyond the root's lifetime is caught when the root is destroyed, so |
| // the crash stack points at the too-early end of the borrow rather than later at an |
| // innocent reader. These are RELEASE_ASSERTs, so unlike most of the Borrow machinery |
| // they fire in release builds too. |
| |
| TEST(WTF_EscapableByteSpanDeathTest, StashedCopyCrashesWhenTheBorrowEnds) |
| { |
| auto shouldCrash = [] { |
| // Outlives the borrow, as a Swift stored property would. |
| std::optional<EscapableByteSpan> stash; |
| { |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| stash.emplace(bytes); |
| } |
| }; |
| ASSERT_DEATH_IF_SUPPORTED(shouldCrash(), EXPECTED_BORROW_CRASH); |
| } |
| |
| TEST(WTF_EscapableByteSpanDeathTest, StashedCopyOfACopyCrashesWhenTheBorrowEnds) |
| { |
| auto shouldCrash = [] { |
| std::optional<EscapableByteSpan> stash; |
| { |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| // The intermediate copy dies first, so this only crashes if the copy of a |
| // copy counted itself against the root rather than against its parent. |
| { |
| EscapableByteSpan intermediate(bytes); |
| stash.emplace(intermediate); |
| } |
| } |
| }; |
| ASSERT_DEATH_IF_SUPPORTED(shouldCrash(), EXPECTED_BORROW_CRASH); |
| } |
| |
| TEST(WTF_EscapableByteSpanDeathTest, StashedCopyOverASpanCrashesWhenTheBorrowEnds) |
| { |
| auto shouldCrash = [] { |
| std::optional<EscapableByteSpan> stash; |
| { |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| auto span = vector.span(); |
| EscapableByteSpan bytes(span); |
| stash.emplace(bytes); |
| } |
| }; |
| ASSERT_DEATH_IF_SUPPORTED(shouldCrash(), EXPECTED_BORROW_CRASH); |
| } |
| |
| TEST(WTF_EscapableByteSpanDeathTest, CopyEscapedToTheHeapCrashesWhenTheBorrowEnds) |
| { |
| auto shouldCrash = [] { |
| struct Escapee { |
| std::optional<EscapableByteSpan> bytes; |
| }; |
| auto escapee = makeUniqueWithoutFastMallocCheck<Escapee>(); |
| { |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| escapee->bytes.emplace(bytes); |
| } |
| }; |
| ASSERT_DEATH_IF_SUPPORTED(shouldCrash(), EXPECTED_BORROW_CRASH); |
| } |
| |
| TEST(WTF_EscapableByteSpan, CopyEscapedToTheHeapAndReleasedInTimeIsFine) |
| { |
| struct Escapee { |
| std::optional<EscapableByteSpan> bytes; |
| }; |
| auto escapee = makeUniqueWithoutFastMallocCheck<Escapee>(); |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| { |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| escapee->bytes.emplace(bytes); |
| EXPECT_EQ(escapee->bytes->size(), vector.size()); |
| escapee->bytes.reset(); |
| } |
| EXPECT_FALSE(escapee->bytes); |
| } |
| |
| TEST(WTF_EscapableByteSpanDeathTest, MAYBE_ASSERT_ENABLED_DEATH_TEST(VectorBytesReallocationWhileBorrowedCrashes)) |
| { |
| auto shouldCrash = [] { |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| vector.reserveCapacity(vector.capacity() + 1024); |
| }; |
| ASSERT_DEATH_IF_SUPPORTED(shouldCrash(), ""); |
| } |
| |
| TEST(WTF_EscapableByteSpanDeathTest, MAYBE_ASSERT_ENABLED_DEATH_TEST(VectorBytesDestroyedVectorWhileBorrowedCrashes)) |
| { |
| auto shouldCrash = [] { |
| auto* vector = new Vector<uint8_t> { 1, 2, 3 }; |
| auto bytesBorrow = borrow(*vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| delete vector; |
| }; |
| ASSERT_DEATH_IF_SUPPORTED(shouldCrash(), ""); |
| } |
| |
| // A mutation that does not reallocate is permitted: the borrow guards the interior |
| // buffer, not the contents. |
| // Swift receives this by value, so this is what a call across the boundary does: bind a |
| // reference and copy it. |
| static size_t takesByReferenceAsSwiftWould(const EscapableByteSpan& bytes) |
| { |
| EscapableByteSpan copy(bytes); |
| return copy.size(); |
| } |
| |
| // The scope holds a Borrow on the Vector. If ending it failed to relinquish that Borrow, |
| // this reallocating mutation would crash. |
| TEST(WTF_EscapableByteSpan, VectorBytesReleasesItsBorrowWhenTheScopeEnds) |
| { |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| { |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| EXPECT_EQ(takesByReferenceAsSwiftWould(bytes), vector.size()); |
| } |
| |
| vector.reserveCapacity(vector.capacity() + 1024); |
| vector.append(4); |
| EXPECT_EQ(vector.size(), 4u); |
| } |
| |
| // Same, for a borrow that is copied repeatedly before the scope ends. |
| TEST(WTF_EscapableByteSpan, VectorBytesReleasesItsBorrowAfterRepeatedCopies) |
| { |
| Vector<uint8_t> vector { 9, 8, 7 }; |
| { |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| for (unsigned i = 0; i < 4; ++i) |
| EXPECT_EQ(takesByReferenceAsSwiftWould(bytes), vector.size()); |
| } |
| |
| Vector<uint8_t> other { 1 }; |
| vector.swap(other); |
| EXPECT_EQ(vector.size(), 1u); |
| } |
| |
| TEST(WTF_EscapableByteSpan, VectorBytesNonReallocatingMutationIsFine) |
| { |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| vector.reserveCapacity(16); |
| { |
| auto bytesBorrow = borrow(vector); |
| EscapableByteSpan bytes = escapableSpan(bytesBorrow->span()); |
| vector[0] = 9; |
| EXPECT_EQ(bytes.span()[0], 9u); |
| } |
| vector.append(4); |
| EXPECT_EQ(vector.size(), 4u); |
| } |
| |
| #if OS(DARWIN) |
| TEST(WTF_EscapableByteSpanDeathTest, MAYBE_ASSERT_ENABLED_DEATH_TEST(HeapAllocatedBytesCrashes)) |
| { |
| auto shouldCrash = [] { |
| struct Holder { |
| explicit Holder(std::span<const uint8_t> bytes) |
| : bytes(bytes) |
| { |
| } |
| EscapableByteSpan bytes; |
| }; |
| Vector<uint8_t> vector { 1, 2, 3 }; |
| // Deliberately leaked: the constructor is expected to crash, and if it |
| // somehow does not, destroying the Holder would crash for another reason. |
| SUPPRESS_UNCOUNTED_LOCAL auto* holder = new Holder(vector.span()); |
| EXPECT_EQ(holder->bytes.size(), vector.size()); |
| }; |
| ASSERT_DEATH_IF_SUPPORTED(shouldCrash(), ""); |
| } |
| #endif |
| |
| } // namespace TestWebKitAPI |