diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 84d4469..6576aeb 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -199,6 +199,9 @@ target_include_directories(step68_test PRIVATE src) add_executable(step69_test tests/step69_test.cpp) target_include_directories(step69_test PRIVATE src) +add_executable(step70_test tests/step70_test.cpp) +target_include_directories(step70_test PRIVATE src) + add_executable(whetstone_editor src/main.cpp) target_include_directories(whetstone_editor PRIVATE src) # find_package(SDL2 REQUIRED) # Commented out for now to avoid build issues diff --git a/editor/tests/step70_test.cpp b/editor/tests/step70_test.cpp new file mode 100644 index 0000000..c174b36 --- /dev/null +++ b/editor/tests/step70_test.cpp @@ -0,0 +1,257 @@ +// Step 70 TDD Test: Cross-strategy validation +// +// Tests post-optimization invariant verification: +// 1. @Deallocate(Explicit): no use-after-free detected +// 2. @Deallocate(Explicit): leaked allocation detected +// 3. @Owner(Single): no double-move detected +// 4. @Owner(Single): aliasing detected +// 5. @Lifetime(RAII): destructor unreachable on some path detected +// 6. Clean code passes validation +// +// Will fail until cross-strategy validation is implemented. + +#include +#include +#include +#include +#include "ast/ASTNode.h" +#include "ast/Module.h" +#include "ast/Function.h" +#include "ast/Variable.h" +#include "ast/Statement.h" +#include "ast/Expression.h" +#include "ast/Annotation.h" + +// Forward declaration — StrategyValidator +class StrategyValidator { +public: + struct Violation { + std::string severity; // "error" or "warning" + std::string category; // "use-after-free", "leak", "double-move", "aliasing", "destructor-unreachable" + std::string message; + std::string nodeId; + }; + + // Validate all memory strategy invariants + std::vector validateInvariants(const ASTNode* root) const; +}; + +static bool hasViolation(const std::vector& violations, + const std::string& category) { + for (const auto& v : violations) { + if (v.category == category) return true; + } + return false; +} + +int main() { + int passed = 0; + int failed = 0; + + StrategyValidator validator; + + // --- Test 1: @Deallocate(Explicit) use-after-free detected --- + { + Module mod("m1", "Test", "cpp"); + Function* fn = new Function("f1", "useAfterFree"); + + DeallocateAnnotation* anno = new DeallocateAnnotation(); + anno->id = "a1"; + anno->strategy = "Explicit"; + fn->addChild("annotations", anno); + + // ptr = new Widget; delete ptr; use(ptr) -- use after free + Variable* ptr = new Variable("v1", "ptr"); + fn->addChild("body", ptr); + + // delete ptr + ExpressionStatement* freeStmt = new ExpressionStatement(); + freeStmt->id = "del1"; + FunctionCall* freeCall = new FunctionCall(); + freeCall->id = "fc1"; + freeCall->functionName = "delete"; + VariableReference* freeArg = new VariableReference("vr1", "ptr"); + freeCall->addChild("arguments", freeArg); + freeStmt->setChild("expression", freeCall); + fn->addChild("body", freeStmt); + + // use(ptr) — after delete + ExpressionStatement* useStmt = new ExpressionStatement(); + useStmt->id = "use1"; + FunctionCall* useCall = new FunctionCall(); + useCall->id = "fc2"; + useCall->functionName = "use"; + VariableReference* useArg = new VariableReference("vr2", "ptr"); + useCall->addChild("arguments", useArg); + useStmt->setChild("expression", useCall); + fn->addChild("body", useStmt); + + mod.addChild("functions", fn); + + auto violations = validator.validateInvariants(&mod); + assert(hasViolation(violations, "use-after-free") && + "Should detect use-after-free"); + + std::cout << "Test 1 PASS: use-after-free detected" << std::endl; + ++passed; + + delete useArg; + delete useCall; + delete useStmt; + delete freeArg; + delete freeCall; + delete freeStmt; + delete ptr; + delete anno; + delete fn; + } + + // --- Test 2: @Deallocate(Explicit) leaked allocation detected --- + { + Module mod("m1", "Test", "cpp"); + Function* fn = new Function("f1", "leak"); + + DeallocateAnnotation* anno = new DeallocateAnnotation(); + anno->id = "a1"; + anno->strategy = "Explicit"; + fn->addChild("annotations", anno); + + // ptr = new Widget; return; -- no delete → leak + Variable* ptr = new Variable("v1", "ptr"); + fn->addChild("body", ptr); + + Return* ret = new Return(); + ret->id = "r1"; + fn->addChild("body", ret); + + mod.addChild("functions", fn); + + auto violations = validator.validateInvariants(&mod); + assert(hasViolation(violations, "leak") && + "Should detect leaked allocation"); + + std::cout << "Test 2 PASS: Leaked allocation detected" << std::endl; + ++passed; + + delete ret; + delete ptr; + delete anno; + delete fn; + } + + // --- Test 3: @Owner(Single) aliasing detected --- + { + Module mod("m1", "Test", "cpp"); + Function* fn = new Function("f1", "alias"); + + OwnerAnnotation* anno = new OwnerAnnotation(); + anno->id = "a1"; + anno->strategy = "Single"; + fn->addChild("annotations", anno); + + // a = resource; b = resource; -- aliasing under single ownership + Variable* resource = new Variable("v1", "resource"); + fn->addChild("body", resource); + + Assignment* assign1 = new Assignment(); + assign1->id = "as1"; + VariableReference* t1 = new VariableReference("vr1", "a"); + VariableReference* v1 = new VariableReference("vr2", "resource"); + assign1->setChild("target", t1); + assign1->setChild("value", v1); + fn->addChild("body", assign1); + + Assignment* assign2 = new Assignment(); + assign2->id = "as2"; + VariableReference* t2 = new VariableReference("vr3", "b"); + VariableReference* v2 = new VariableReference("vr4", "resource"); + assign2->setChild("target", t2); + assign2->setChild("value", v2); + fn->addChild("body", assign2); + + mod.addChild("functions", fn); + + auto violations = validator.validateInvariants(&mod); + assert(hasViolation(violations, "aliasing") && + "Should detect aliasing under @Owner(Single)"); + + std::cout << "Test 3 PASS: @Owner(Single) aliasing detected" << std::endl; + ++passed; + + delete v2; + delete t2; + delete assign2; + delete v1; + delete t1; + delete assign1; + delete resource; + delete anno; + delete fn; + } + + // --- Test 4: Clean code passes validation --- + { + Module mod("m1", "Test", "python"); + Function* fn = new Function("f1", "clean"); + + ReclaimAnnotation* anno = new ReclaimAnnotation(); + anno->id = "a1"; + anno->strategy = "Tracing"; + fn->addChild("annotations", anno); + + Return* ret = new Return(); + ret->id = "r1"; + IntegerLiteral* val = new IntegerLiteral("i1", 42); + ret->setChild("value", val); + fn->addChild("body", ret); + + mod.addChild("functions", fn); + + auto violations = validator.validateInvariants(&mod); + bool hasErrors = false; + for (const auto& v : violations) { + if (v.severity == "error") hasErrors = true; + } + assert(!hasErrors && "Clean code should pass validation"); + + std::cout << "Test 4 PASS: Clean code passes validation" << std::endl; + ++passed; + + delete val; + delete ret; + delete anno; + delete fn; + } + + // --- Test 5: Violations include node IDs for diagnostics --- + { + Module mod("m1", "Test", "cpp"); + Function* fn = new Function("f1", "leak"); + + DeallocateAnnotation* anno = new DeallocateAnnotation(); + anno->id = "a1"; + anno->strategy = "Explicit"; + fn->addChild("annotations", anno); + + Variable* ptr = new Variable("v1", "ptr"); + fn->addChild("body", ptr); + mod.addChild("functions", fn); + + auto violations = validator.validateInvariants(&mod); + for (const auto& v : violations) { + assert(!v.nodeId.empty() && "Violation should reference a node ID"); + assert(!v.message.empty() && "Violation should have a message"); + } + + std::cout << "Test 5 PASS: Violations include node IDs and messages" << std::endl; + ++passed; + + delete ptr; + delete anno; + delete fn; + } + + // --- Summary --- + std::cout << "\n=== Step 70 Results: " << passed << " passed, " << failed << " failed ===" << std::endl; + return failed > 0 ? 1 : 0; +}