blob: b6d85f279f4f9309e23f9d95b8d02777eb1db2ab [file] [edit]
//===-- copyprof_interceptors_test.cpp ------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include <string.h>
#include "copyprof/copyprof_interface_internal.h"
#include "copyprof/copyprof_shadow.h"
#include "copyprof/copyprof_state.h"
#include "gtest/gtest.h"
namespace __copyprof {
namespace {
// The intercepted functions are called through volatile function pointers, so
// that the compiler cannot replace the calls with inline stores, which would
// bypass the interceptors.
void* (*volatile memset_fn)(void*, int, size_t) = memset;
void* (*volatile memcpy_fn)(void*, const void*, size_t) = memcpy;
void* (*volatile memmove_fn)(void*, const void*, size_t) = memmove;
class CopyProfInterceptorsTest : public testing::Test {
protected:
void SetUp() override {
__copyprof_init_once();
__copyprof_state = PerThreadState();
}
};
TEST_F(CopyProfInterceptorsTest, MemsetMarksDestinationAsNonCopy) {
alignas(8) char buf[32] = {0};
MarkApplicationMemory(buf, sizeof(buf), /*is_copy=*/true);
memset_fn(buf + 8, /*c=*/1, /*size=*/8);
EXPECT_TRUE(IsMarkedAsCopy(buf, /*num_bytes=*/8));
EXPECT_FALSE(IsMarkedAsCopy(buf + 8, /*num_bytes=*/8));
EXPECT_TRUE(IsMarkedAsCopy(buf + 16, /*num_bytes=*/16));
}
TEST_F(CopyProfInterceptorsTest, MemcpyMarksOnlyDestinationAsNonCopy) {
alignas(8) char dst[32] = {0};
alignas(8) char src[32] = {0};
MarkApplicationMemory(dst, sizeof(dst), /*is_copy=*/true);
MarkApplicationMemory(src, sizeof(src), /*is_copy=*/true);
memcpy_fn(dst, src, sizeof(dst));
EXPECT_FALSE(IsMarkedAsCopy(dst, sizeof(dst)));
EXPECT_TRUE(IsMarkedAsCopy(src, sizeof(src)));
}
TEST_F(CopyProfInterceptorsTest, MemmoveMarksOnlyDestinationAsNonCopy) {
alignas(8) char buf[32] = {0};
MarkApplicationMemory(buf, sizeof(buf), /*is_copy=*/true);
// Overlapping move of `[0, 16)` to `[8, 24)`.
memmove_fn(buf + 8, buf, /*size=*/16);
EXPECT_TRUE(IsMarkedAsCopy(buf, /*num_bytes=*/8));
EXPECT_FALSE(IsMarkedAsCopy(buf + 8, /*num_bytes=*/16));
EXPECT_TRUE(IsMarkedAsCopy(buf + 24, /*num_bytes=*/8));
}
// Zero-sized writes must not touch shadow memory.
TEST_F(CopyProfInterceptorsTest, ZeroSizedWriteLeavesShadowUnchanged) {
alignas(8) char buf[16] = {0};
MarkApplicationMemory(buf, sizeof(buf), /*is_copy=*/true);
memset_fn(buf + 3, /*c=*/1, /*size=*/0);
memcpy_fn(buf + 3, buf, /*size=*/0);
EXPECT_TRUE(IsMarkedAsCopy(buf, sizeof(buf)));
}
// A copy c'tor may copy (trivially copyable) members via `memcpy`, which must
// mark the copy as such.
TEST_F(CopyProfInterceptorsTest, WriteDuringCopyMarksAsCopy) {
alignas(8) char obj[32] = {0};
alignas(8) char other[32] = {0};
__copyprof_copy_ctor_enter_callback(obj, other, sizeof(obj));
memcpy_fn(obj, other, sizeof(obj));
__copyprof_copy_ctor_exit_callback(obj, other, sizeof(obj));
EXPECT_TRUE(IsMarkedAsCopy(obj, sizeof(obj)));
}
// Like stores, writes by library functions during destruction must not clear
// the copy shadow bits.
TEST_F(CopyProfInterceptorsTest, WriteDuringDtorLeavesShadowUnchanged) {
alignas(8) char obj[32] = {0};
alignas(8) char other[32] = {0};
__copyprof_copy_ctor_enter_callback(obj, other, sizeof(obj));
__copyprof_copy_ctor_exit_callback(obj, other, sizeof(obj));
__copyprof_dtor_enter_callback(obj, sizeof(obj));
memset_fn(obj, /*c=*/0, sizeof(obj));
EXPECT_TRUE(IsMarkedAsCopy(obj, sizeof(obj)));
__copyprof_dtor_exit_callback(obj, sizeof(obj));
EXPECT_TRUE(__copyprof_state.is_transitive_copy);
}
// Modifying a copy via a library function makes it a necessary copy.
TEST_F(CopyProfInterceptorsTest, ModifyingCopyViaLibraryCallClearsCopy) {
alignas(8) char obj[32] = {0};
alignas(8) char other[32] = {0};
__copyprof_copy_ctor_enter_callback(obj, other, sizeof(obj));
__copyprof_copy_ctor_exit_callback(obj, other, sizeof(obj));
ASSERT_TRUE(IsMarkedAsCopy(obj, sizeof(obj)));
memset_fn(obj + 4, /*c=*/1, /*size=*/1);
__copyprof_dtor_enter_callback(obj, sizeof(obj));
__copyprof_dtor_exit_callback(obj, sizeof(obj));
EXPECT_FALSE(__copyprof_state.is_transitive_copy);
}
} // namespace
} // namespace __copyprof