blob: 5cc8030a0e600408758e423c9b9de86b51253479 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "peridot/bin/ledger/coroutine/context/context.h"
#include <string.h>
#include <lib/fit/function.h>
#include <lib/fxl/compiler_specific.h>
#include <lib/fxl/logging.h>
#include "gtest/gtest.h"
namespace context {
#if __has_feature(safe_stack)
char* GetUnsafeStackForTest(const Stack& stack) {
return reinterpret_cast<char*>(stack.unsafe_stack());
}
#endif
namespace {
// Function using variable args to generate mmx code on x86_64. Running this
// without crashing ensures that the stack is correctly aligned.
int UseMMX(const char* format, ...) {
va_list va;
va_start(va, format);
int result = vsnprintf(nullptr, 0, format, va);
va_end(va);
return result;
}
size_t Fact(size_t n) {
if (n == 0) {
return 1;
}
return n * Fact(n - 1);
}
void RunInContext(void* data) {
auto runnable = reinterpret_cast<fit::function<void()>*>(data);
(*runnable)();
}
TEST(Context, GetContext) {
Context context;
EXPECT_TRUE(GetContext(&context));
}
TEST(Context, SetContext) {
Context context;
volatile size_t nb_calls = 0;
volatile bool result = GetContext(&context);
++nb_calls;
if (result) {
SetContext(&context);
}
EXPECT_EQ(2u, nb_calls);
}
TEST(Context, MakeContext) {
Stack stack;
Context new_context;
Context old_context;
size_t f = 0u;
int va_args_result = 0;
fit::function<void()> runnable = [&]() {
f = Fact(5);
va_args_result = UseMMX("Hello %d %d\n", 1, 2);
SetContext(&old_context);
};
MakeContext(&new_context, &stack, &RunInContext, &runnable);
SwapContext(&old_context, &new_context);
EXPECT_EQ(120u, f);
EXPECT_EQ(10, va_args_result);
}
struct ThreadLocalContext {
static thread_local char* thread_local_ptr;
char* ptr = nullptr;
Context old_context;
};
thread_local char* ThreadLocalContext::thread_local_ptr = nullptr;
void GetThreadLocalPointer(void* context) {
auto thread_local_context = reinterpret_cast<ThreadLocalContext*>(context);
thread_local_context->ptr = ThreadLocalContext::thread_local_ptr;
SetContext(&thread_local_context->old_context);
}
TEST(Context, ThreadLocal) {
Stack stack;
ThreadLocalContext context;
char c = 'a';
ThreadLocalContext::thread_local_ptr = &c;
Context new_context;
EXPECT_TRUE(GetContext(&new_context));
MakeContext(&new_context, &stack, &GetThreadLocalPointer, &context);
SwapContext(&context.old_context, &new_context);
EXPECT_EQ(&c, context.ptr);
}
#if __has_feature(safe_stack)
// Force to set the pointed address to 1. This must be no-inline to prevent the
// compiler to optimize away the set.
FXL_NOINLINE void ForceSet(volatile char* addr) { *addr = 1; }
// Write some data to the unsafe stack.
void TrashStack(void* context) {
volatile char buffer[1024];
for (size_t i = 0; i < 6; ++i) {
ForceSet(buffer + Fact(i));
}
SetContext(reinterpret_cast<Context*>(context));
}
TEST(Context, MakeContextUnsafeStack) {
Stack stack;
memset(GetUnsafeStackForTest(stack), 0, stack.stack_size());
Context new_context;
Context old_context;
EXPECT_TRUE(GetContext(&new_context));
MakeContext(&new_context, &stack, &TrashStack, &old_context);
SwapContext(&old_context, &new_context);
bool found = false;
char* ptr = GetUnsafeStackForTest(stack);
for (size_t i = 0; i < stack.stack_size(); ++i) {
found = found || *(ptr + i);
}
EXPECT_TRUE(found);
}
__NO_SAFESTACK intptr_t GetSafeStackPointer() {
char a = 0;
// Suppress check about returning a stack memory address.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-stack-address"
return reinterpret_cast<intptr_t>(&a); // NOLINT
#pragma clang diagnostic pop
}
void CheckDistinctStack(void* context) {
char buff[1];
memset(buff, 0, sizeof(buff));
// buff is on the unsafe stack, GetSafeStackPointer() returns a value on the
// safe stack. This checks that the address of the 2 stacks are separated at
// least by 2 PAGE_SIZE, given that each stack has a guard.
EXPECT_GE(std::abs(reinterpret_cast<intptr_t>(buff) - GetSafeStackPointer()),
2 * PAGE_SIZE);
SetContext(reinterpret_cast<Context*>(context));
}
TEST(Context, CheckStacksAreDifferent) {
Stack stack;
Context new_context;
Context old_context;
EXPECT_TRUE(GetContext(&new_context));
MakeContext(&new_context, &stack, &CheckDistinctStack, &old_context);
SwapContext(&old_context, &new_context);
}
#endif
} // namespace
} // namespace context