#pragma once // Step 64: Memory annotation validation // // AnnotationValidator: checks canonical memory annotations for consistency. // // Rules: // @Deallocate(Explicit) without a deallocation point → "Missing Intent" error // @Owner(Single) with aliasing (variable assigned to another) → alias error // Conflicting parent/child annotations of same family → conflict error // @Reclaim(Tracing), @Lifetime(RAII) with proper usage → pass #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" class AnnotationValidator { public: struct Diagnostic { std::string severity; // "error" or "warning" std::string message; std::string nodeId; }; std::vector validate(const ASTNode* root) const { std::vector diags; validateNode(root, diags); return diags; } private: void validateNode(const ASTNode* node, std::vector& diags) const { if (!node) return; // Check annotations on this node for (const auto* anno : node->getChildren("annotations")) { if (anno->conceptType == "DeallocateAnnotation") { auto* da = static_cast(anno); if (da->strategy == "Explicit") { checkDeallocateExplicit(node, diags); } } else if (anno->conceptType == "OwnerAnnotation") { auto* oa = static_cast(anno); if (oa->strategy == "Single") { checkOwnerSingleAliasing(node, diags); } checkConflictWithAncestor(node, anno, diags); } else if (anno->conceptType == "ReclaimAnnotation" || anno->conceptType == "LifetimeAnnotation" || anno->conceptType == "AllocateAnnotation") { checkConflictWithAncestor(node, anno, diags); } } // Recurse into children for (auto* child : node->allChildren()) { validateNode(child, diags); } } // @Deallocate(Explicit): the enclosing function body must contain // evidence of deallocation (a FunctionCall to delete/free, or a // DeallocateAnnotation with a deallocateLocation set). // If not found, emit a "Missing Intent" error. void checkDeallocateExplicit(const ASTNode* node, std::vector& diags) const { // Find the function body to scan const ASTNode* fn = node; if (fn->conceptType != "Function") { // Walk up to the enclosing function fn = node->parent; while (fn && fn->conceptType != "Function") fn = fn->parent; } if (!fn) { diags.push_back({"error", "Missing Intent: @Deallocate(Explicit) without " "enclosing function scope", node->id}); return; } // Look for deallocation evidence in the function body bool found = false; for (auto* child : fn->getChildren("body")) { if (hasDeallocEvidence(child)) { found = true; break; } } // Also check the annotation itself for a filled deallocateLocation for (auto* anno : node->getChildren("annotations")) { if (anno->conceptType == "DeallocateAnnotation") { auto* da = static_cast(anno); if (!da->deallocateLocation.empty()) { found = true; break; } } } if (!found) { diags.push_back({"error", "Missing Intent: @Deallocate(Explicit) requires a " "corresponding deallocation point (delete/free)", node->id}); } } // Recursively check for deallocation evidence (FunctionCall to // delete/free/release, or a Return that explicitly frees). bool hasDeallocEvidence(const ASTNode* node) const { if (!node) return false; if (node->conceptType == "FunctionCall") { auto* fc = static_cast(node); if (fc->functionName == "delete" || fc->functionName == "free" || fc->functionName == "release" || fc->functionName == "deallocate") return true; } if (node->conceptType == "ExpressionStatement") { auto* child = node->getChild("expression"); if (child && hasDeallocEvidence(child)) return true; } for (auto* child : node->allChildren()) { if (hasDeallocEvidence(child)) return true; } return false; } // @Owner(Single): check for aliasing assignments in the function body. // An alias occurs when a locally-declared variable appears on the // value side of an Assignment (copied to another variable). void checkOwnerSingleAliasing(const ASTNode* node, std::vector& diags) const { const ASTNode* fn = node; if (fn->conceptType != "Function") { fn = node->parent; while (fn && fn->conceptType != "Function") fn = fn->parent; } if (!fn) return; // Collect locally-declared variable names std::vector localNames; for (auto* child : fn->getChildren("body")) { if (child->conceptType == "Variable") { localNames.push_back( static_cast(child)->name); } } // Scan body for assignments where value references a local variable std::vector assignments; collectByType(fn, "Assignment", assignments); for (auto* assignNode : assignments) { auto* valNode = assignNode->getChild("value"); if (valNode && valNode->conceptType == "VariableReference") { auto* vr = static_cast(valNode); for (const auto& local : localNames) { if (vr->variableName == local) { diags.push_back({"error", "@Owner(Single) violation: variable '" + local + "' is aliased via assignment — single ownership " "does not allow aliasing", node->id}); return; // one diagnostic per annotation } } } } } // Check if a node's annotation conflicts with an ancestor's annotation // of the same family (same conceptType) but different strategy. void checkConflictWithAncestor(const ASTNode* node, const ASTNode* anno, std::vector& diags) const { std::string strategy = getStrategy(anno); if (strategy.empty()) return; // Walk up ancestors const ASTNode* cur = node->parent; while (cur) { for (auto* parentAnno : cur->getChildren("annotations")) { if (parentAnno->conceptType == anno->conceptType) { std::string parentStrategy = getStrategy(parentAnno); if (!parentStrategy.empty() && parentStrategy != strategy) { diags.push_back({"error", "Annotation conflict: " + anno->conceptType + "(" + strategy + ") on node '" + node->id + "' conflicts with " + anno->conceptType + "(" + parentStrategy + ") on ancestor '" + cur->id + "'", node->id}); return; } } } cur = cur->parent; } } // Extract the strategy string from any canonical annotation. static std::string getStrategy(const ASTNode* anno) { const auto& ct = anno->conceptType; if (ct == "DeallocateAnnotation") return static_cast(anno)->strategy; if (ct == "LifetimeAnnotation") return static_cast(anno)->strategy; if (ct == "ReclaimAnnotation") return static_cast(anno)->strategy; if (ct == "OwnerAnnotation") return static_cast(anno)->strategy; if (ct == "AllocateAnnotation") return static_cast(anno)->strategy; return ""; } static void collectByType(const ASTNode* node, const std::string& type, std::vector& out) { if (!node) return; if (node->conceptType == type) out.push_back(node); for (auto* child : node->allChildren()) { collectByType(child, type, out); } } }; #include "AnnotationValidatorExtended.h"