blob: a3863306bfa3d412d987390c71cf4bc4c7d3f7b3 [file]
/*
* 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