Step 363: Add C memory/annotation inference and C->Rust ownership mapping

This commit is contained in:
Bill
2026-02-16 09:39:51 -07:00
parent 31f12d0323
commit f7fbec7bc4
7 changed files with 673 additions and 3 deletions

View File

@@ -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;
}
};

View File

@@ -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");

View File

@@ -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;
}

View 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