Step 363: Add C memory/annotation inference and C->Rust ownership mapping
This commit is contained in:
@@ -2151,4 +2151,8 @@ add_executable(step362_test tests/step362_test.cpp)
|
||||
target_include_directories(step362_test PRIVATE src)
|
||||
target_link_libraries(step362_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
add_executable(step363_test tests/step363_test.cpp)
|
||||
target_include_directories(step363_test PRIVATE src)
|
||||
target_link_libraries(step363_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||
|
||||
@@ -5,11 +5,15 @@
|
||||
#include "ast/Variable.h"
|
||||
#include "ast/Statement.h"
|
||||
#include "ast/Expression.h"
|
||||
#include "ast/Type.h"
|
||||
#include "ast/Annotation.h"
|
||||
#include "ast/PreprocessorNodes.h"
|
||||
#include "MemoryStrategyInference.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
class AnnotationInference {
|
||||
public:
|
||||
@@ -28,11 +32,15 @@ public:
|
||||
|
||||
// Delegate memory annotations to existing MemoryStrategyInference
|
||||
if (root->conceptType == "Module") {
|
||||
auto* mod = static_cast<const Module*>(root);
|
||||
MemoryStrategyInference memInf;
|
||||
auto memSuggestions = memInf.inferAnnotations(root);
|
||||
for (const auto& s : memSuggestions) {
|
||||
out.push_back({s.nodeId, s.annotationType, "strategy", s.strategy, s.reason, s.confidence});
|
||||
}
|
||||
if (mod->targetLanguage == "c") {
|
||||
inferCModule(mod, out);
|
||||
}
|
||||
}
|
||||
|
||||
// Infer all other annotation types
|
||||
@@ -377,4 +385,156 @@ private:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void inferCModule(const Module* mod, std::vector<InferredAnnotation>& out) const {
|
||||
if (!mod) return;
|
||||
inferCHeaderGuard(mod, out);
|
||||
for (auto* fnNode : mod->getChildren("functions")) {
|
||||
if (fnNode->conceptType != "Function") continue;
|
||||
inferCFunction(fnNode, out);
|
||||
}
|
||||
}
|
||||
|
||||
void inferCHeaderGuard(const Module* mod, std::vector<InferredAnnotation>& out) const {
|
||||
bool hasIfndef = false;
|
||||
bool hasEndif = false;
|
||||
for (auto* stmt : mod->getChildren("statements")) {
|
||||
if (stmt->conceptType != "PragmaDirective") continue;
|
||||
auto* pragma = static_cast<const PragmaDirective*>(stmt);
|
||||
std::string d = lowerAscii(pragma->directive);
|
||||
if (startsWith(d, "ifndef") || startsWith(d, "ifdef")) hasIfndef = true;
|
||||
if (startsWith(d, "endif")) hasEndif = true;
|
||||
}
|
||||
if (hasIfndef && hasEndif &&
|
||||
!hasInferred(out, mod->id, "SyntheticAnnotation", "guard")) {
|
||||
out.push_back({mod->id, "SyntheticAnnotation", "generator", "guard",
|
||||
"C header guard pattern detected", 0.93});
|
||||
}
|
||||
}
|
||||
|
||||
void inferCFunction(const ASTNode* fn, std::vector<InferredAnnotation>& out) const {
|
||||
std::set<std::string> existing;
|
||||
for (const auto* a : fn->getChildren("annotations")) {
|
||||
existing.insert(a->conceptType);
|
||||
}
|
||||
|
||||
bool hasGoto = false;
|
||||
bool hasVoidPtrPattern = false;
|
||||
bool hasArrayAccess = false;
|
||||
bool hasBoundsCheck = false;
|
||||
for (auto* bodyNode : fn->getChildren("body")) {
|
||||
hasGoto = hasGoto || hasGotoPattern(bodyNode);
|
||||
hasVoidPtrPattern = hasVoidPtrPattern || hasVoidPtrPatternNode(bodyNode);
|
||||
hasArrayAccess = hasArrayAccess || hasArrayAccessNode(bodyNode);
|
||||
hasBoundsCheck = hasBoundsCheck || hasBoundsCheckNode(bodyNode);
|
||||
}
|
||||
|
||||
if (hasGoto && !existing.count("ComplexityAnnotation")) {
|
||||
out.push_back({fn->id, "ComplexityAnnotation", "timeComplexity", "high",
|
||||
"goto usage increases control-flow complexity", 0.90});
|
||||
}
|
||||
if (hasGoto && !existing.count("RiskAnnotation")) {
|
||||
out.push_back({fn->id, "RiskAnnotation", "level", "medium",
|
||||
"goto usage is error-prone in C code paths", 0.82});
|
||||
}
|
||||
if (hasVoidPtrPattern && !existing.count("RiskAnnotation")) {
|
||||
out.push_back({fn->id, "RiskAnnotation", "level", "high",
|
||||
"void* conversion erases type guarantees", 0.90});
|
||||
}
|
||||
if (hasVoidPtrPattern && !existing.count("AmbiguityAnnotation")) {
|
||||
out.push_back({fn->id, "AmbiguityAnnotation", "level", "medium",
|
||||
"void* usage introduces type ambiguity", 0.84});
|
||||
}
|
||||
if (hasArrayAccess && !hasBoundsCheck && !existing.count("BoundsCheckAnnotation")) {
|
||||
out.push_back({fn->id, "BoundsCheckAnnotation", "mode", "unchecked",
|
||||
"Array access without guard detected", 0.80});
|
||||
}
|
||||
}
|
||||
|
||||
bool hasGotoPattern(const ASTNode* node) const {
|
||||
if (!node) return false;
|
||||
if (node->conceptType == "FunctionCall") {
|
||||
auto* fc = static_cast<const FunctionCall*>(node);
|
||||
if (lowerAscii(fc->functionName) == "goto") return true;
|
||||
}
|
||||
for (auto* child : node->allChildren()) {
|
||||
if (hasGotoPattern(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasVoidPtrPatternNode(const ASTNode* node) const {
|
||||
if (!node) return false;
|
||||
if (node->conceptType == "Variable") {
|
||||
auto* typeNode = node->getChild("type");
|
||||
if (containsVoidPtr(typeNode)) return true;
|
||||
}
|
||||
if (node->conceptType == "FunctionCall") {
|
||||
auto* fc = static_cast<const FunctionCall*>(node);
|
||||
if (lowerAscii(fc->functionName).find("void*") != std::string::npos) return true;
|
||||
}
|
||||
for (auto* child : node->allChildren()) {
|
||||
if (hasVoidPtrPatternNode(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasArrayAccessNode(const ASTNode* node) const {
|
||||
if (!node) return false;
|
||||
if (node->conceptType == "IndexAccess") return true;
|
||||
for (auto* child : node->allChildren()) {
|
||||
if (hasArrayAccessNode(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasBoundsCheckNode(const ASTNode* node) const {
|
||||
if (!node) return false;
|
||||
if (node->conceptType == "BoundsCheckAnnotation") return true;
|
||||
if (node->conceptType == "FunctionCall") {
|
||||
auto* fc = static_cast<const FunctionCall*>(node);
|
||||
std::string fn = lowerAscii(fc->functionName);
|
||||
if (fn == "bounds_check" || fn == "assert" || fn == "check_bounds") return true;
|
||||
}
|
||||
for (auto* child : node->allChildren()) {
|
||||
if (hasBoundsCheckNode(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool containsVoidPtr(const ASTNode* typeNode) {
|
||||
if (!typeNode) return false;
|
||||
if (typeNode->conceptType == "PrimitiveType") {
|
||||
auto* p = static_cast<const PrimitiveType*>(typeNode);
|
||||
return lowerAscii(p->kind).find("void*") != std::string::npos;
|
||||
}
|
||||
if (typeNode->conceptType == "CustomType") {
|
||||
auto* c = static_cast<const CustomType*>(typeNode);
|
||||
return lowerAscii(c->typeName).find("void*") != std::string::npos;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hasInferred(const std::vector<InferredAnnotation>& out,
|
||||
const std::string& nodeId,
|
||||
const std::string& annotationType,
|
||||
const std::string& value) {
|
||||
for (const auto& a : out) {
|
||||
if (a.nodeId == nodeId && a.annotationType == annotationType && a.value == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string lowerAscii(const std::string& value) {
|
||||
std::string out = value;
|
||||
std::transform(out.begin(), out.end(), out.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool startsWith(const std::string& value, const std::string& prefix) {
|
||||
return value.rfind(prefix, 0) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "ProjectionAdaptation.h"
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Module.h"
|
||||
#include "ast/Function.h"
|
||||
@@ -157,7 +158,9 @@ private:
|
||||
}
|
||||
if (ct == "OwnerAnnotation") {
|
||||
auto* src = static_cast<const OwnerAnnotation*>(anno);
|
||||
auto* a = new OwnerAnnotation(src->id + "_proj", src->strategy);
|
||||
auto* a = new OwnerAnnotation(
|
||||
src->id + "_proj",
|
||||
ProjectionAdaptation::adaptOwnerStrategy(src->strategy, targetLanguage));
|
||||
a->ownerType = src->ownerType;
|
||||
return a;
|
||||
}
|
||||
@@ -240,6 +243,7 @@ private:
|
||||
auto* typeChild = src->getChild("type");
|
||||
if (typeChild) {
|
||||
ASTNode* t = cloneNode(typeChild, targetLanguage);
|
||||
t = ProjectionAdaptation::adaptTypeForTarget(t, targetLanguage);
|
||||
if (t) p->setChild("type", t);
|
||||
}
|
||||
auto* defaultValue = src->getChild("defaultValue");
|
||||
|
||||
@@ -10,10 +10,12 @@
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Module.h"
|
||||
#include "ast/Function.h"
|
||||
#include "ast/Variable.h"
|
||||
#include "ast/Type.h"
|
||||
#include "ast/Expression.h"
|
||||
#include "ast/Annotation.h"
|
||||
|
||||
@@ -92,8 +94,7 @@ private:
|
||||
inferCppModule(mod, out);
|
||||
}
|
||||
else if (lang == "c") {
|
||||
out.push_back({mod->id, "DeallocateAnnotation", "Explicit",
|
||||
"C requires explicit malloc/free", 0.90});
|
||||
inferCModule(mod, out);
|
||||
}
|
||||
else if (lang == "rust") {
|
||||
out.push_back({mod->id, "OwnerAnnotation", "Single",
|
||||
@@ -119,11 +120,26 @@ private:
|
||||
"C++ idiom: RAII with scope-based cleanup", 0.70});
|
||||
}
|
||||
|
||||
void inferCModule(const Module* mod,
|
||||
std::vector<Suggestion>& out) const {
|
||||
out.push_back({mod->id, "OwnerAnnotation", "Manual",
|
||||
"C defaults to manual ownership", 0.95});
|
||||
out.push_back({mod->id, "LifetimeAnnotation", "Scope",
|
||||
"C locals default to lexical scope lifetime", 0.92});
|
||||
out.push_back({mod->id, "ReclaimAnnotation", "Explicit",
|
||||
"C uses explicit reclamation (free)", 0.95});
|
||||
}
|
||||
|
||||
void inferFunction(const Function* fn, const std::string& lang,
|
||||
std::vector<Suggestion>& out) const {
|
||||
// Skip if function already has a memory annotation
|
||||
if (hasMemoryAnnotation(fn)) return;
|
||||
|
||||
if (lang == "c") {
|
||||
inferCFunction(fn, out);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check body for patterns
|
||||
bool hasAlloc = false;
|
||||
bool hasDealloc = false;
|
||||
@@ -162,6 +178,41 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void inferCFunction(const Function* fn,
|
||||
std::vector<Suggestion>& out) const {
|
||||
bool hasAlloc = false;
|
||||
|
||||
for (auto* paramNode : fn->getChildren("parameters")) {
|
||||
if (paramNode->conceptType != "Parameter") continue;
|
||||
auto* typeNode = paramNode->getChild("type");
|
||||
if (isPointerType(typeNode)) {
|
||||
out.push_back({paramNode->id, "OwnerAnnotation", "Borrowed",
|
||||
"Pointer parameters in C are borrowed by default",
|
||||
0.86});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* stmt : fn->getChildren("body")) {
|
||||
hasAlloc = hasAlloc || containsAllocCall(stmt);
|
||||
inferCLocalLifetime(stmt, out);
|
||||
}
|
||||
|
||||
auto* retType = fn->getChild("returnType");
|
||||
if (isPointerType(retType)) {
|
||||
out.push_back({fn->id, "OwnerAnnotation", inferCReturnOwner(fn->name),
|
||||
"C pointer returns are convention-based ownership transfers",
|
||||
0.72});
|
||||
}
|
||||
|
||||
if (hasAlloc) {
|
||||
out.push_back({fn->id, "OwnerAnnotation", "Manual",
|
||||
"malloc/calloc allocation indicates manual ownership",
|
||||
0.94});
|
||||
out.push_back({fn->id, "ReclaimAnnotation", "Explicit",
|
||||
"malloc/calloc requires explicit free", 0.94});
|
||||
}
|
||||
}
|
||||
|
||||
static bool hasMemoryAnnotation(const ASTNode* node) {
|
||||
for (auto* anno : node->getChildren("annotations")) {
|
||||
const auto& ct = anno->conceptType;
|
||||
@@ -190,6 +241,77 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
static bool containsAllocCall(const ASTNode* node) {
|
||||
if (!node) return false;
|
||||
if (node->conceptType == "FunctionCall") {
|
||||
auto* fc = static_cast<const FunctionCall*>(node);
|
||||
if (fc->functionName == "malloc" || fc->functionName == "calloc") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (auto* child : node->allChildren()) {
|
||||
if (containsAllocCall(child)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void inferCLocalLifetime(const ASTNode* node,
|
||||
std::vector<Suggestion>& out) {
|
||||
if (!node) return;
|
||||
if (node->conceptType == "Variable") {
|
||||
auto* v = static_cast<const Variable*>(node);
|
||||
auto* typeNode = v->getChild("type");
|
||||
std::string typeKind = typeString(typeNode);
|
||||
std::string lowered = lowerAscii(typeKind);
|
||||
if (lowered.find("static") != std::string::npos) {
|
||||
out.push_back({v->id, "LifetimeAnnotation", "Static",
|
||||
"Static storage duration detected in C declaration",
|
||||
0.90});
|
||||
} else {
|
||||
out.push_back({v->id, "LifetimeAnnotation", "Scope",
|
||||
"Local C variable uses scope-bound lifetime", 0.88});
|
||||
}
|
||||
}
|
||||
for (auto* child : node->allChildren()) {
|
||||
inferCLocalLifetime(child, out);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string inferCReturnOwner(const std::string& fnName) {
|
||||
std::string lower = lowerAscii(fnName);
|
||||
if (startsWith(lower, "get") || startsWith(lower, "borrow") ||
|
||||
startsWith(lower, "peek") || startsWith(lower, "view")) {
|
||||
return "Borrowed";
|
||||
}
|
||||
return "Transferred";
|
||||
}
|
||||
|
||||
static bool isPointerType(const ASTNode* typeNode) {
|
||||
return typeString(typeNode).find('*') != std::string::npos;
|
||||
}
|
||||
|
||||
static std::string typeString(const ASTNode* typeNode) {
|
||||
if (!typeNode) return "";
|
||||
if (typeNode->conceptType == "PrimitiveType") {
|
||||
return static_cast<const PrimitiveType*>(typeNode)->kind;
|
||||
}
|
||||
if (typeNode->conceptType == "CustomType") {
|
||||
return static_cast<const CustomType*>(typeNode)->typeName;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::string lowerAscii(const std::string& input) {
|
||||
std::string out = input;
|
||||
std::transform(out.begin(), out.end(), out.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool startsWith(const std::string& value, const std::string& prefix) {
|
||||
return value.rfind(prefix, 0) == 0;
|
||||
}
|
||||
|
||||
static std::string feedbackKey(const Suggestion& suggestion) {
|
||||
return suggestion.annotationType + ":" + suggestion.strategy;
|
||||
}
|
||||
|
||||
72
editor/src/ProjectionAdaptation.h
Normal file
72
editor/src/ProjectionAdaptation.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Type.h"
|
||||
|
||||
namespace ProjectionAdaptation {
|
||||
|
||||
inline std::string adaptOwnerStrategy(const std::string& strategy,
|
||||
const std::string& targetLanguage) {
|
||||
if (targetLanguage == "rust") {
|
||||
if (strategy == "Manual") return "Box";
|
||||
if (strategy == "Borrowed") return "Borrowed";
|
||||
if (strategy == "Transferred") return "Moved";
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
|
||||
inline void trimInPlace(std::string& text) {
|
||||
while (!text.empty() && std::isspace(static_cast<unsigned char>(text.front()))) {
|
||||
text.erase(text.begin());
|
||||
}
|
||||
while (!text.empty() && std::isspace(static_cast<unsigned char>(text.back()))) {
|
||||
text.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
inline bool startsWith(const std::string& value, const std::string& prefix) {
|
||||
return value.rfind(prefix, 0) == 0;
|
||||
}
|
||||
|
||||
inline std::string stripPointers(const std::string& rawType) {
|
||||
std::string out = rawType;
|
||||
out.erase(std::remove(out.begin(), out.end(), '*'), out.end());
|
||||
trimInPlace(out);
|
||||
if (startsWith(out, "const ")) {
|
||||
out = out.substr(6);
|
||||
trimInPlace(out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline ASTNode* adaptPointerTypeForRust(ASTNode* typeNode) {
|
||||
if (!typeNode) return nullptr;
|
||||
if (typeNode->conceptType == "PrimitiveType") {
|
||||
auto* p = static_cast<PrimitiveType*>(typeNode);
|
||||
if (p->kind.find('*') == std::string::npos) return typeNode;
|
||||
std::string base = stripPointers(p->kind);
|
||||
if (!base.empty()) p->kind = "&" + base;
|
||||
return p;
|
||||
}
|
||||
if (typeNode->conceptType == "CustomType") {
|
||||
auto* c = static_cast<CustomType*>(typeNode);
|
||||
if (c->typeName.find('*') == std::string::npos) return typeNode;
|
||||
std::string base = stripPointers(c->typeName);
|
||||
if (!base.empty()) c->typeName = "&" + base;
|
||||
return c;
|
||||
}
|
||||
return typeNode;
|
||||
}
|
||||
|
||||
inline ASTNode* adaptTypeForTarget(ASTNode* typeNode,
|
||||
const std::string& targetLanguage) {
|
||||
if (targetLanguage == "rust") {
|
||||
return adaptPointerTypeForRust(typeNode);
|
||||
}
|
||||
return typeNode;
|
||||
}
|
||||
|
||||
} // namespace ProjectionAdaptation
|
||||
263
editor/tests/step363_test.cpp
Normal file
263
editor/tests/step363_test.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
// Step 363: C Memory Annotation Mapping (12 tests)
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "MemoryStrategyInference.h"
|
||||
#include "AnnotationInference.h"
|
||||
#include "CrossLanguageProjector.h"
|
||||
#include "ast/CParser.h"
|
||||
|
||||
static bool hasMemorySuggestion(
|
||||
const std::vector<MemoryStrategyInference::Suggestion>& suggestions,
|
||||
const std::string& nodeId,
|
||||
const std::string& annotationType,
|
||||
const std::string& strategy) {
|
||||
for (const auto& s : suggestions) {
|
||||
if (s.nodeId == nodeId && s.annotationType == annotationType && s.strategy == strategy) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hasInferred(
|
||||
const std::vector<AnnotationInference::InferredAnnotation>& inferred,
|
||||
const std::string& nodeId,
|
||||
const std::string& annotationType,
|
||||
const std::string& value) {
|
||||
for (const auto& a : inferred) {
|
||||
if (a.nodeId == nodeId && a.annotationType == annotationType && a.value == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
|
||||
// Test 1: C defaults applied on module
|
||||
{
|
||||
Module mod("m1", "CDefaults", "c");
|
||||
MemoryStrategyInference inf;
|
||||
auto suggestions = inf.inferAnnotations(&mod);
|
||||
assert(hasMemorySuggestion(suggestions, "m1", "OwnerAnnotation", "Manual"));
|
||||
assert(hasMemorySuggestion(suggestions, "m1", "LifetimeAnnotation", "Scope"));
|
||||
assert(hasMemorySuggestion(suggestions, "m1", "ReclaimAnnotation", "Explicit"));
|
||||
std::cout << "Test 1 PASSED: c defaults owner/lifetime/reclaim\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 2: malloc pattern -> Owner(Manual) + Reclaim(Explicit)
|
||||
{
|
||||
Module mod("m1", "CMalloc", "c");
|
||||
Function fn("f1", "alloc_buf");
|
||||
FunctionCall* mallocCall = new FunctionCall();
|
||||
mallocCall->id = "c1";
|
||||
mallocCall->functionName = "malloc";
|
||||
fn.addChild("body", mallocCall);
|
||||
mod.addChild("functions", &fn);
|
||||
|
||||
MemoryStrategyInference inf;
|
||||
auto suggestions = inf.inferAnnotations(&mod);
|
||||
assert(hasMemorySuggestion(suggestions, "f1", "OwnerAnnotation", "Manual"));
|
||||
assert(hasMemorySuggestion(suggestions, "f1", "ReclaimAnnotation", "Explicit"));
|
||||
std::cout << "Test 2 PASSED: malloc infers manual+explicit\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 3: local variable -> Lifetime(Scope)
|
||||
{
|
||||
Module mod("m1", "CLocal", "c");
|
||||
Function fn("f1", "work");
|
||||
Variable* v = new Variable("v1", "x");
|
||||
v->setChild("type", new PrimitiveType("t1", "int"));
|
||||
fn.addChild("body", v);
|
||||
mod.addChild("functions", &fn);
|
||||
|
||||
MemoryStrategyInference inf;
|
||||
auto suggestions = inf.inferAnnotations(&mod);
|
||||
assert(hasMemorySuggestion(suggestions, "v1", "LifetimeAnnotation", "Scope"));
|
||||
std::cout << "Test 3 PASSED: local variable infers scope lifetime\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 4: pointer parameter -> Owner(Borrowed)
|
||||
{
|
||||
Module mod("m1", "CParam", "c");
|
||||
Function fn("f1", "consume");
|
||||
Parameter* p = new Parameter("p1", "ptr");
|
||||
p->setChild("type", new PrimitiveType("t1", "int*"));
|
||||
fn.addChild("parameters", p);
|
||||
mod.addChild("functions", &fn);
|
||||
|
||||
MemoryStrategyInference inf;
|
||||
auto suggestions = inf.inferAnnotations(&mod);
|
||||
assert(hasMemorySuggestion(suggestions, "p1", "OwnerAnnotation", "Borrowed"));
|
||||
std::cout << "Test 4 PASSED: pointer parameter infers borrowed owner\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 5: return pointer convention -> Borrowed for get* names
|
||||
{
|
||||
Module mod("m1", "CReturn", "c");
|
||||
Function fn("f1", "get_buffer");
|
||||
fn.setChild("returnType", new PrimitiveType("t1", "char*"));
|
||||
mod.addChild("functions", &fn);
|
||||
|
||||
MemoryStrategyInference inf;
|
||||
auto suggestions = inf.inferAnnotations(&mod);
|
||||
assert(hasMemorySuggestion(suggestions, "f1", "OwnerAnnotation", "Borrowed"));
|
||||
std::cout << "Test 5 PASSED: get* pointer return infers borrowed owner\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 6: static variable -> Lifetime(Static)
|
||||
{
|
||||
Module mod("m1", "CStatic", "c");
|
||||
Function fn("f1", "counter");
|
||||
Variable* v = new Variable("v1", "counter");
|
||||
v->setChild("type", new PrimitiveType("t1", "static int"));
|
||||
fn.addChild("body", v);
|
||||
mod.addChild("functions", &fn);
|
||||
|
||||
MemoryStrategyInference inf;
|
||||
auto suggestions = inf.inferAnnotations(&mod);
|
||||
assert(hasMemorySuggestion(suggestions, "v1", "LifetimeAnnotation", "Static"));
|
||||
std::cout << "Test 6 PASSED: static variable infers static lifetime\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 7: goto usage -> Complexity(high) + Risk(medium)
|
||||
{
|
||||
Module mod("m1", "CGoto", "c");
|
||||
Function fn("f1", "jumping");
|
||||
FunctionCall* go = new FunctionCall();
|
||||
go->id = "g1";
|
||||
go->functionName = "goto";
|
||||
fn.addChild("body", go);
|
||||
mod.addChild("functions", &fn);
|
||||
|
||||
AnnotationInference inf;
|
||||
auto inferred = inf.inferAll(&mod);
|
||||
assert(hasInferred(inferred, "f1", "ComplexityAnnotation", "high"));
|
||||
assert(hasInferred(inferred, "f1", "RiskAnnotation", "medium"));
|
||||
std::cout << "Test 7 PASSED: goto infers complexity/risk signals\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 8: void* cast pattern -> Risk(high) + Ambiguity(medium)
|
||||
{
|
||||
Module mod("m1", "CVoid", "c");
|
||||
Function fn("f1", "casty");
|
||||
Variable* v = new Variable("v1", "raw");
|
||||
v->setChild("type", new PrimitiveType("t1", "void*"));
|
||||
fn.addChild("body", v);
|
||||
mod.addChild("functions", &fn);
|
||||
|
||||
AnnotationInference inf;
|
||||
auto inferred = inf.inferAll(&mod);
|
||||
assert(hasInferred(inferred, "f1", "RiskAnnotation", "high"));
|
||||
assert(hasInferred(inferred, "f1", "AmbiguityAnnotation", "medium"));
|
||||
std::cout << "Test 8 PASSED: void* infers risk+ambiguity\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 9: array access without checks -> BoundsCheck(unchecked)
|
||||
{
|
||||
Module mod("m1", "CArray", "c");
|
||||
Function fn("f1", "idx");
|
||||
IndexAccess* idx = new IndexAccess();
|
||||
idx->id = "i1";
|
||||
idx->setChild("target", new VariableReference("vr1", "arr"));
|
||||
idx->setChild("index", new VariableReference("vr2", "i"));
|
||||
ExpressionStatement* stmt = new ExpressionStatement();
|
||||
stmt->id = "s1";
|
||||
stmt->setChild("expression", idx);
|
||||
fn.addChild("body", stmt);
|
||||
mod.addChild("functions", &fn);
|
||||
|
||||
AnnotationInference inf;
|
||||
auto inferred = inf.inferAll(&mod);
|
||||
assert(hasInferred(inferred, "f1", "BoundsCheckAnnotation", "unchecked"));
|
||||
std::cout << "Test 9 PASSED: unchecked array access inferred\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 10: header guard pattern -> Synthetic(guard)
|
||||
{
|
||||
std::string source = "#ifndef SAMPLE_H\n#define SAMPLE_H\nint x;\n#endif\n";
|
||||
auto mod = CParser::parseC(source);
|
||||
AnnotationInference inf;
|
||||
auto inferred = inf.inferAll(mod.get());
|
||||
assert(hasInferred(inferred, mod->id, "SyntheticAnnotation", "guard"));
|
||||
std::cout << "Test 10 PASSED: header guard infers synthetic marker\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 11: inference confidence scores remain reasonable
|
||||
{
|
||||
Module mod("m1", "CConfidence", "c");
|
||||
Function fn("f1", "alloc");
|
||||
FunctionCall* mallocCall = new FunctionCall();
|
||||
mallocCall->id = "c1";
|
||||
mallocCall->functionName = "malloc";
|
||||
fn.addChild("body", mallocCall);
|
||||
mod.addChild("functions", &fn);
|
||||
|
||||
MemoryStrategyInference inf;
|
||||
auto suggestions = inf.inferAnnotations(&mod);
|
||||
bool foundHigh = false;
|
||||
for (const auto& s : suggestions) {
|
||||
assert(s.confidence >= 0.0 && s.confidence <= 1.0);
|
||||
if (s.confidence >= 0.70) foundHigh = true;
|
||||
}
|
||||
assert(foundHigh);
|
||||
std::cout << "Test 11 PASSED: confidence values are bounded and useful\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 12: cross-language C ownership adaptation to Rust
|
||||
{
|
||||
Module mod("m1", "COwnership", "c");
|
||||
Function* fn = new Function("f1", "transfer");
|
||||
auto* fnOwner = new OwnerAnnotation("a1", "Manual");
|
||||
fn->addChild("annotations", fnOwner);
|
||||
|
||||
Parameter* p = new Parameter("p1", "src");
|
||||
p->setChild("type", new PrimitiveType("t1", "int*"));
|
||||
auto* paramOwner = new OwnerAnnotation("a2", "Borrowed");
|
||||
p->addChild("annotations", paramOwner);
|
||||
fn->addChild("parameters", p);
|
||||
mod.addChild("functions", fn);
|
||||
|
||||
CrossLanguageProjector projector;
|
||||
auto rustMod = projector.project(&mod, "rust");
|
||||
auto* rustFn = static_cast<Function*>(rustMod->getChildren("functions")[0]);
|
||||
|
||||
bool hasBoxOwner = false;
|
||||
for (auto* anno : rustFn->getChildren("annotations")) {
|
||||
if (anno->conceptType == "OwnerAnnotation") {
|
||||
auto* owner = static_cast<OwnerAnnotation*>(anno);
|
||||
if (owner->strategy == "Box") hasBoxOwner = true;
|
||||
}
|
||||
}
|
||||
assert(hasBoxOwner);
|
||||
|
||||
auto* rustParam = static_cast<Parameter*>(rustFn->getChildren("parameters")[0]);
|
||||
auto* rustType = rustParam->getChild("type");
|
||||
assert(rustType != nullptr);
|
||||
assert(rustType->conceptType == "PrimitiveType");
|
||||
auto* rustPrim = static_cast<PrimitiveType*>(rustType);
|
||||
assert(!rustPrim->kind.empty() && rustPrim->kind[0] == '&');
|
||||
|
||||
std::cout << "Test 12 PASSED: C owner/manual->Box and borrowed pointer->&T style\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
std::cout << "\nResults: " << passed << "/12\n";
|
||||
assert(passed == 12);
|
||||
return 0;
|
||||
}
|
||||
45
progress.md
45
progress.md
@@ -2123,6 +2123,51 @@ includes, and method-name lowering for class-style methods.
|
||||
- `step361_test` — PASS (12/12) regression coverage
|
||||
- `step360_test` — PASS (8/8) regression coverage
|
||||
|
||||
### Step 363: C Memory Annotation Mapping
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implemented C-specific memory and semantic annotation inference so C code now
|
||||
gets meaningful defaults and risk signals aligned with explicit-memory patterns.
|
||||
|
||||
**Files created:**
|
||||
- `editor/src/ProjectionAdaptation.h` — extracted projection adaptation helpers:
|
||||
- C owner strategy adaptation for Rust targets (`Manual -> Box`,
|
||||
`Transferred -> Moved`)
|
||||
- pointer-type adaptation for Rust references (`T* -> &T`)
|
||||
|
||||
**Files modified:**
|
||||
- `editor/src/MemoryStrategyInference.h` — C-specific memory inference:
|
||||
- module defaults: `Owner(Manual)`, `Lifetime(Scope)`, `Reclaim(Explicit)`
|
||||
- malloc/calloc detection: function-level owner/reclaim suggestions
|
||||
- pointer parameter inference: `Owner(Borrowed)`
|
||||
- pointer return inference by naming convention:
|
||||
- `get*/borrow*/peek*/view*` -> `Owner(Borrowed)`
|
||||
- otherwise -> `Owner(Transferred)`
|
||||
- local/static variable lifetime inference:
|
||||
- locals -> `Lifetime(Scope)`
|
||||
- static storage -> `Lifetime(Static)`
|
||||
- `editor/src/AnnotationInference.h` — C pattern inference:
|
||||
- `goto` pattern -> `Complexity(high)` + `Risk(medium)`
|
||||
- unchecked array indexing -> `BoundsCheck(unchecked)`
|
||||
- `void*` usage -> `Risk(high)` + `Ambiguity(medium)`
|
||||
- header guard detection -> `Synthetic(guard)`
|
||||
- `editor/src/CrossLanguageProjector.h` — uses `ProjectionAdaptation` helpers
|
||||
for ownership and pointer-type adaptation without exceeding header size limits
|
||||
- `editor/tests/step363_test.cpp` — 12 tests covering defaults, C-specific rules,
|
||||
confidence bounds, header-guard synthesis, and C->Rust ownership adaptation
|
||||
- `editor/CMakeLists.txt` — `step363_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `step363_test` — PASS (12/12) new step coverage
|
||||
- `step362_test` — PASS (12/12) regression coverage
|
||||
- `step361_test` — PASS (12/12) regression coverage
|
||||
- `step360_test` — PASS (8/8) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/CrossLanguageProjector.h` remained under the 600-line cap after
|
||||
refactor by extracting target adaptation logic into
|
||||
`editor/src/ProjectionAdaptation.h`
|
||||
|
||||
# Roadmap Planning — Sprints 12-25+
|
||||
|
||||
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)
|
||||
|
||||
Reference in New Issue
Block a user