Files
UnrealEngineUWP/Engine/Source/Programs/AutoRTFMTests/Private/OpenAPI.cpp
ben clayton f64f4f7837 [AutoRTFM] Prevent memory corruption with stack writes in non-scoped transactions
Assert if you attempt to perform a write to the transaction-stack while in non-scoped transaction (writes to a Closed() scope are permitted).

Code changes:

Introduce FStackRange to hold a stack pointer range.

Use FStackRange in FContext for storing the true stack range for the thread.

Add a FStackRange to each FTransaction, and use this to ensure that write records aren't added to a transaction if the address is within the transaction's stack.
By having a serparate stack per transaction we can prevent stack writes making their way to a parent transaction's write log, reducing the amount of memory that may need to be tracked and simplifying the Undo logic.

Ensuring that the write log does not contain stack writes is required for the open-memory validator.

#rb neil.henning

[CL 36005508 by ben clayton in ue5-main branch]
2024-09-04 09:09:31 -04:00

868 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Catch2Includes.h"
#include <AutoRTFM/AutoRTFM.h>
#include <map>
#include <vector>
TEST_CASE("OpenAPI.StartAbortAndStartAgain")
{
int valueB = 0;
int valueC = 0;
AutoRTFM::Transact([&]()
{
// Recorded valueB as starting at 0.
valueB = 20;
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
AutoRTFM::RecordOpenWrite(&valueB);
valueB=10;
AutoRTFM::AbortTransaction();
AutoRTFM::ForTheRuntime::ClearTransactionStatus();
AutoRTFM::ForTheRuntime::StartTransaction();
AutoRTFM::RecordOpenWrite(&valueC);
valueC = 30;
AutoRTFM::AbortTransaction();
});
});
// We rollback the transaction to the value we had when we first recorded the address
REQUIRE(valueB == 20);
REQUIRE(valueC == 0);
}
TEST_CASE("OpenAPI.CommitScopedFromOpen_Illegal", "[.]")
{
AutoRTFM::ETransactionResult transactResult = AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::CommitTransaction(); // illegal. Can't Commit from within a scoped transaction
});
});
REQUIRE(transactResult == AutoRTFM::ETransactionResult::AbortedByRequest);
}
TEST_CASE("OpenAPI.RecordDataClosed_Illegal", "[.]")
{
int value = 0;
AutoRTFM::ETransactionResult transactResult = AutoRTFM::Transact([&]()
{
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
AutoRTFM::RecordOpenWrite(&value); // Illegal. Can't record writes explicitly while closed
value = 1;
}));
});
REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed);
REQUIRE(value == 1);
}
TEST_CASE("OpenAPI.WriteDataInTheOpen")
{
int value = 0;
auto transactResult = AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::RecordOpenWrite(&value);
value = 1;
});
});
REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed);
REQUIRE(value == 1);
}
TEST_CASE("OpenAPI.AbortTransactionScopedFromOpen")
{
auto transactResult = AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::AbortTransaction();
});
FAIL("AutoRTFM::Open failed to throw after an abort");
});
REQUIRE(transactResult == AutoRTFM::ETransactionResult::AbortedByRequest);
}
TEST_CASE("OpenAPI.AbortTransactionScopedFromClosed")
{
auto transactResult = AutoRTFM::Transact([&]()
{
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
AutoRTFM::AbortTransaction();
}));
FAIL("AutoRTFM::Close should have no-op'ed because it's already closed from the Transact");
});
REQUIRE(transactResult == AutoRTFM::ETransactionResult::AbortedByRequest);
}
TEST_CASE("OpenAPI.AbortTransactionDoubleScopedFromOpen")
{
unsigned long value = -42;
auto transactResult = AutoRTFM::Transact([&]()
{
value = 42;
auto transactResult2 = AutoRTFM::Transact([&]()
{
value = 42424242;
AutoRTFM::Open([&]()
{
AutoRTFM::AbortTransaction();
});
FAIL("AutoRTFM::Open failed to throw after an abort");
value = 24242424;
});
if (transactResult2 != AutoRTFM::ETransactionResult::AbortedByRequest) FAIL("transactResult2 != AutoRTFM::ETransactionResult::AbortedByRequest");
if (value != 42) FAIL("value != 42");
value = 123123123;
});
REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed);
REQUIRE(value == 123123123);
}
TEST_CASE("OpenAPI.NestedClosedTransactions")
{
int value = 0x12345678;
auto transactResult = AutoRTFM::Transact([&]()
{
// Read value
int x = value;
value = 0x11111111;
auto transactResult2 = AutoRTFM::Transact([&]()
{
if (value != 0x11111111) FAIL("value != 0x11111111");
// Read value
int y = value;
value = 0x22222222;
auto transactResult3 = AutoRTFM::Transact([&]()
{
if (value != 0x22222222) FAIL("value != 0x22222222");
// Read value
int z = value;
value = 0x33333333;
auto transactResult4 = AutoRTFM::Transact([&]()
{
if (value != 0x33333333) FAIL("value != 0x33333333");
// Read value
int q = value;
value = 0x44444444;
if (value != 0x44444444) FAIL("value != 0x44444444");
if (q != 0x33333333) FAIL("q != 0x33333333");
});
(void)transactResult4;
if (value != 0x44444444) FAIL("value != 0x44444444");
if (z != 0x22222222) FAIL("z != 0x22222222");
});
(void)transactResult3;
value = 0x55555555;
if (value != 0x55555555) FAIL("value != 0x55555555");
if (y != 0x11111111) FAIL("y != 0x11111111");
});
(void)transactResult2;
if (value != 0x55555555) FAIL("value != 0x55555555");
value = 0x66666666;
if (value != 0x66666666) FAIL("value != 0x66666666");
if (x != 0x12345678) FAIL("x != 0x12345678");
});
REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed);
REQUIRE(value == 0x66666666);
}
TEST_CASE("OpenAPI.OpenWithCopy")
{
struct SomeData_t
{
int A;
float B;
char C;
};
SomeData_t SomeData1{ 1,2.0,'3' };
auto transactResult = AutoRTFM::Transact([&]()
{
SomeData_t SomeData2{ 9,8.0,'7' };
SomeData1.A = 11;
SomeData2.A = 29;
AutoRTFM::Open([=]()
{
REQUIRE(SomeData1.A == 11);
REQUIRE(SomeData2.A == 29);
});
});
REQUIRE(transactResult == AutoRTFM::ETransactionResult::Committed);
}
#if defined(_BROKEN_ALLOC_FIXED_)
TEST_CASE("OpenAPI.OpenCloseOpenClose")
{
// START OPEN
REQUIRE(!AutoRTFM::IsTransactional());
int x = 42;
std::vector<int> v;
std::map<int, std::vector<int>> m;
v.push_back(100);
m[1].push_back(2);
m[1].push_back(3);
m[4].push_back(5);
m[6].push_back(7);
m[6].push_back(8);
m[6].push_back(9);
auto transactResult = AutoRTFM::Transact([&]()
{
// A - WE ARE CLOSED
if (!AutoRTFM::IsClosed()) FAIL("A - NOT CLOSED AS EXPECTED!");
// -------------------------------------
AutoRTFM::Open([&]()
{
// B - WE ARE OPEN
REQUIRE(!AutoRTFM::IsClosed());
// -------------------------------------
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
// C - WE ARE CLOSED AGAIN
if (!AutoRTFM::IsClosed()) FAIL("C - NOT CLOSED AS EXPECTED!");
// -------------------------------------
AutoRTFM::Open([&]()
{
// D - WE ARE OPEN AGAIN
REQUIRE(!AutoRTFM::IsClosed());
// An abort here will set state on the transactions, but will not LongJump
// AutoRTFM::Abort();
});
// -------------------------------------
// E - BACK TO CLOSED AFTER AN OPEN
x = 5;
for (size_t n = 10; n--;)
v.push_back(2 * n);
m.clear();
m[10].push_back(11);
m[12].push_back(13);
m[12].push_back(14);
// An abort here is closed and will LongJump past F AND G all the way to H
AutoRTFM::AbortTransaction();
// -------------------------------------
AutoRTFM::Open([&]()
{
// F - WE ARE OPEN AGAIN //
REQUIRE(!AutoRTFM::IsClosed());
});
// -------------------------------------
// G - BACK TO CLOSED AGAIN
if (!AutoRTFM::IsClosed()) FAIL("NOT CLOSED!");
}));
// -------------------------------------
// H - BACK TO OPEN
REQUIRE(!AutoRTFM::IsClosed());
});
// -------------------------------------
// I - Finally closed again to finish out the transaction
if (!AutoRTFM::IsClosed()) FAIL("I - NOT CLOSED AS EXPECTED!");
});
REQUIRE(
AutoRTFM::ETransactionResult::AbortedByRequest ==
transactResult);
REQUIRE(x == 42);
REQUIRE(v.size() == 1);
REQUIRE(v[0] == 100);
REQUIRE(m.size() == 3);
REQUIRE(m[1].size() == 2);
REQUIRE(m[1][0] == 2);
REQUIRE(m[1][1] == 3);
REQUIRE(m[4].size() == 1);
REQUIRE(m[4][0] == 5);
REQUIRE(m[6].size() == 3);
REQUIRE(m[6][0] == 7);
REQUIRE(m[6][1] == 8);
REQUIRE(m[6][2] == 9);
REQUIRE(!AutoRTFM::IsTransactional());
}
#endif
TEST_CASE("OpenAPI.Commit_TransactOpenCloseCommit")
{
REQUIRE(!AutoRTFM::IsTransactional());
// We're open
REQUIRE(!AutoRTFM::IsClosed());
int value = 10;
value++;
// Close and start the top-level transaction
AutoRTFM::Transact([&]()
{
if (!AutoRTFM::IsClosed()) FAIL("Not Closed");
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
value = 42;
}));
REQUIRE(value == 42); // RTFM writes through immediately, so we can see this value in the open
AutoRTFM::ForTheRuntime::CommitTransaction();
});
if (value != 42) FAIL("Value != 42!");
value = 420;
});
REQUIRE(value == 420);
REQUIRE(!AutoRTFM::IsTransactional());
}
TEST_CASE("OpenAPI.Commit_TransactOpenCloseAbort")
{
REQUIRE(!AutoRTFM::IsTransactional());
// We're open
REQUIRE(!AutoRTFM::IsClosed());
int Value = 10;
Value++;
// Close and start the top-level transaction
AutoRTFM::Transact([&]()
{
if (!AutoRTFM::IsClosed()) FAIL("Not Closed");
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
int Local = 0;
Local = 42;
Value = Local;
}));
AutoRTFM::AbortTransaction(); // undoes Value = 42 in the open
});
FAIL("Should not reach here!");
});
REQUIRE(Value == 11);
REQUIRE(!AutoRTFM::IsTransactional());
}
TEST_CASE("OpenAPI.DoubleTransact")
{
double value = 1.0;
AutoRTFM::Transact([&]()
{
AutoRTFM::Transact([&]()
{
value *= 2.5;
AutoRTFM::AbortTransaction();
});
value *= 10.0;
});
REQUIRE(value == 10.0);
}
TEST_CASE("OpenAPI.DoubleTransact2")
{
double value = 1.0;
AutoRTFM::Transact([&]()
{
value = value + 2.0;
AutoRTFM::Transact([&]()
{
if (value == 3.0)
{
value *= 2.5;
}
if (value == 7.5)
AutoRTFM::AbortTransaction();
});
value *= 10.0;
});
REQUIRE(value == 30.0);
}
TEST_CASE("OpenAPI.DoubleTransact3")
{
double result = 0.0;
AutoRTFM::Transact([&]()
{
double value = 1.0;
value = value + 2.0;
AutoRTFM::Transact([&]()
{
if (value == 3.0)
{
value *= 2.5;
}
if (value == 7.5)
AutoRTFM::AbortTransaction();
});
value *= 10.0;
result = value;
});
REQUIRE(result == 30.0);
}
TEST_CASE("OpenAPI.StackWriteCommitInTheOpen1")
{
int value = 0;
AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
AutoRTFM::RecordOpenWrite(&value);
value = 10;
AutoRTFM::ForTheRuntime::CommitTransaction();
REQUIRE(value == 10);
});
});
}
TEST_CASE("OpenAPI.StackWriteCommitInTheOpen2")
{
int Value = 0;
AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::RecordOpenWrite(&Value);
Value = 10;
});
}));
AutoRTFM::ForTheRuntime::CommitTransaction();
REQUIRE(Value == 10);
});
});
}
TEST_CASE("OpenAPI.StackWriteAbortInTheOpen1")
{
int value = 0;
AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
AutoRTFM::RecordOpenWrite(&value);
value = 10;
AutoRTFM::AbortTransaction();
REQUIRE(value == 0);
});
});
}
#if defined(OPENAPI_ILLEGAL_TESTS)
TEST_CASE("OpenAPI.StackWriteCommitInTheOpen3_Illegal")
{
AutoRTFM::Transact([&]()
{
int value = 0;
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
AutoRTFM::ForTheRuntime::WriteMemory(&value, 10);
AutoRTFM::ForTheRuntime::CommitTransaction();
REQUIRE(value == 10);
});
});
}
#endif
int value1 = 0;
TEST_CASE("OpenAPI.WriteMemory1")
{
const int sourceValue = 10;
AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
AutoRTFM::ForTheRuntime::WriteMemory(&value1, &sourceValue);
AutoRTFM::ForTheRuntime::CommitTransaction();
REQUIRE(value1 == 10);
});
});
}
TEST_CASE("OpenAPI.StackWriteAbortInTheOpen2")
{
int value = 0;
int bGotToA = false;
AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
AutoRTFM::ForTheRuntime::WriteMemory(&value, 10); // Illegal to write to value because it's in the inner-most closed-nest
AutoRTFM::AbortTransaction();
});
// Never gets here
bGotToA = true;
REQUIRE(value == 0);
});
REQUIRE(bGotToA == false);
REQUIRE(value == 0);
}
TEST_CASE("OpenAPI.WriteTrivialStructure")
{
struct SomeData
{
int A;
double B;
float C;
char D;
long E[5];
};
SomeData data = { 1,2.0, 3.0f, 'q', {123,234,345,456,567} };
SomeData data2 = { 9,8.0, 7.0f, '^', {999,888,777,666,555} };
AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
AutoRTFM::ForTheRuntime::WriteMemory(&data, &data2);
REQUIRE(data.A == 9);
REQUIRE(data.B == 8.0);
REQUIRE(data.C == 7.0f);
REQUIRE(data.D == '^');
REQUIRE(data.E[0] == 999);
REQUIRE(data.E[1] == 888);
REQUIRE(data.E[2] == 777);
REQUIRE(data.E[3] == 666);
REQUIRE(data.E[4] == 555);
AutoRTFM::AbortTransaction();
REQUIRE(data.A == 1);
REQUIRE(data.B == 2.0);
REQUIRE(data.C == 3.0f);
REQUIRE(data.D == 'q');
REQUIRE(data.E[0] == 123);
REQUIRE(data.E[1] == 234);
REQUIRE(data.E[2] == 345);
REQUIRE(data.E[3] == 456);
REQUIRE(data.E[4] == 567);
});
});
}
TEST_CASE("OpenAPI.WriteTrivialStructure2")
{
struct SomeData
{
int A;
double B;
float C;
char D;
long E[5];
};
SomeData data = { 1,2.0, 3.0f, 'q', {123,234,345,456,567} };
SomeData data2 = { 9,8.0, 7.0f, '^', {999,888,777,666,555} };
SomeData data3 = { 19,28.0, 37.0f, '@', {4999,5888,6777,7666,8555} };
AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
AutoRTFM::ForTheRuntime::WriteMemory(&data, &data2);
REQUIRE(data.A == 9);
REQUIRE(data.B == 8.0);
REQUIRE(data.C == 7.0f);
REQUIRE(data.D == '^');
REQUIRE(data.E[0] == 999);
REQUIRE(data.E[1] == 888);
REQUIRE(data.E[2] == 777);
REQUIRE(data.E[3] == 666);
REQUIRE(data.E[4] == 555);
AutoRTFM::ForTheRuntime::WriteMemory(&data, &data3);
REQUIRE(data.A == 19);
REQUIRE(data.B == 28.0);
REQUIRE(data.C == 37.0f);
REQUIRE(data.D == '@');
REQUIRE(data.E[0] == 4999);
REQUIRE(data.E[1] == 5888);
REQUIRE(data.E[2] == 6777);
REQUIRE(data.E[3] == 7666);
REQUIRE(data.E[4] == 8555);
AutoRTFM::AbortTransaction();
REQUIRE(data.A == 1);
REQUIRE(data.B == 2.0);
REQUIRE(data.C == 3.0f);
REQUIRE(data.D == 'q');
REQUIRE(data.E[0] == 123);
REQUIRE(data.E[1] == 234);
REQUIRE(data.E[2] == 345);
REQUIRE(data.E[3] == 456);
REQUIRE(data.E[4] == 567);
});
});
}
TEST_CASE("OpenAPI.Footgun1")
{
int valueA = 0;
int valueB = 0;
AutoRTFM::Transact([&]()
{
// Does nothing - already closed
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
// Recorded valueB as starting at 0.
valueB = 123;
AutoRTFM::Open([&]()
{
// Unrecorded assignments in the open
valueA = 10;
valueB = 10;
});
// valueA is now recorded as starting at 10
valueA = 20;
AutoRTFM::AbortTransaction();
}));
});
// We rollback the transaction to the value we had when we first recorded the address
REQUIRE(valueA == 10);
REQUIRE(valueB == 0);
}
TEST_CASE("OpenAPI.Footgun2")
{
int valueB = 0;
int valueC = 0;
AutoRTFM::Transact([&]()
{
// Does nothing - already closed
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
// Recorded valueB as starting at 0.
valueB = 20;
AutoRTFM::Open([&]()
{
// Unrecorded assignments in the open
valueB = 10;
valueC = 10;
AutoRTFM::RecordOpenWrite(&valueC);
// valueC was recorded in the open after the change - too late
});
// valueA is now recorded as starting at 10
valueC = 40;
AutoRTFM::AbortTransaction();
}));
});
// We rollback the transaction to the value we had when we first recorded the address
REQUIRE(valueB == 0);
REQUIRE(valueC == 10);
}
#if 0
TEST_CASE("OpenAPI.StartCloseOnCommit")
{
REQUIRE(!AutoRTFM::IsTransactional());
int value = 10;
value++;
(void)value;
AutoRTFM::ForTheRuntime::StartTransaction();
// Can't close outside of a transaction
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
value = 420;
}));
// assignment within the close should be visible to us
REQUIRE(value == 420);
// Setting a value in the open requires us to register the memory address with the transaction
value = 42;
AutoRTFM::RecordOpenWrite(&value);
AutoRTFM::ForTheRuntime::CommitTransaction();
// Finally, 42 is committed to value
REQUIRE(value == 42);
REQUIRE(!AutoRTFM::IsTransactional());
}
#endif
TEST_CASE("OpenAPI.TransOpenStartCloseAbortAbort")
{
bool bGetsToA = false;
bool bGetsToB = false;
bool bGetsToC = false;
bool bGetsToD = false;
REQUIRE(!AutoRTFM::IsTransactional());
int Value = 10;
AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::ForTheRuntime::StartTransaction();
Value++;
Value = 42;
AutoRTFM::EContextStatus CloseStatus = AutoRTFM::Close([&]()
{
Value = 420;
AutoRTFM::AbortTransaction();
bGetsToA = true;
});
REQUIRE(CloseStatus == AutoRTFM::EContextStatus::AbortedByRequest);
AutoRTFM::ForTheRuntime::ClearTransactionStatus();
REQUIRE(bGetsToA == false);
bGetsToB = true;
REQUIRE(Value == 42);
AutoRTFM::AbortTransaction();
bGetsToC = true;
REQUIRE(Value == 42);
});
bGetsToD = true;
});
REQUIRE(bGetsToA == false);
REQUIRE(bGetsToB == true);
REQUIRE(bGetsToC == true);
REQUIRE(bGetsToD == false);
REQUIRE(!AutoRTFM::IsTransactional());
}
TEST_CASE("OpenAPI.TransOpenTransCloseAbortAbort")
{
bool bGetsToA = false;
bool bGetsToB = false;
bool bGetsToC = false;
bool bGetsToD = false;
REQUIRE(!AutoRTFM::IsTransactional());
AutoRTFM::Transact([&]()
{
AutoRTFM::Open([&]()
{
AutoRTFM::Transact([&]()
{
int value = 10;
value++;
value = 42;
// Can't close outside of a Transact
REQUIRE(AutoRTFM::EContextStatus::OnTrack == AutoRTFM::Close([&]()
{
value = 420;
AutoRTFM::AbortTransaction();
bGetsToA = true;
}));
REQUIRE(bGetsToA == false);
bGetsToB = true;
REQUIRE(value == 42);
AutoRTFM::AbortTransaction();
bGetsToC = true;
REQUIRE(value == 42);
});
});
bGetsToD = true;
});
REQUIRE(bGetsToA == false);
REQUIRE(bGetsToB == false);
REQUIRE(bGetsToC == false);
REQUIRE(bGetsToD == true);
REQUIRE(!AutoRTFM::IsTransactional());
}