254 lines
9.5 KiB
C++
254 lines
9.5 KiB
C++
#pragma once
|
|
|
|
// Step 445: API Boundary Preservation — ensures migrated modules maintain
|
|
// their public API surface with FFI annotations and @Contract verification.
|
|
|
|
#include "MigrationPlanGenerator.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
struct FunctionSignature {
|
|
std::string name;
|
|
std::string returnType;
|
|
std::vector<std::string> paramTypes;
|
|
std::vector<std::string> paramNames;
|
|
bool isPublic = true;
|
|
};
|
|
|
|
struct TypeMapping {
|
|
std::string sourceName;
|
|
std::string sourceLanguage;
|
|
std::string targetName;
|
|
std::string targetLanguage;
|
|
std::string note; // compatibility note
|
|
};
|
|
|
|
struct APIMigrationContract {
|
|
std::string functionName;
|
|
std::string precondition;
|
|
std::string postcondition;
|
|
};
|
|
|
|
struct FFIBoundary {
|
|
std::string functionName;
|
|
std::string sourceLanguage;
|
|
std::string targetLanguage;
|
|
std::string annotation; // e.g. @FFI(extern "C"), #[no_mangle], etc.
|
|
};
|
|
|
|
struct APIBoundaryReport {
|
|
std::string moduleName;
|
|
std::string sourceLanguage;
|
|
std::string targetLanguage;
|
|
std::vector<FunctionSignature> publicAPI;
|
|
std::vector<TypeMapping> typeMappings;
|
|
std::vector<APIMigrationContract> contracts;
|
|
std::vector<FFIBoundary> ffiBoundaries;
|
|
bool apiPreserved = true;
|
|
|
|
bool hasFunction(const std::string& name) const {
|
|
for (const auto& f : publicAPI)
|
|
if (f.name == name) return true;
|
|
return false;
|
|
}
|
|
|
|
bool hasTypeMapping(const std::string& sourceName) const {
|
|
for (const auto& t : typeMappings)
|
|
if (t.sourceName == sourceName) return true;
|
|
return false;
|
|
}
|
|
|
|
bool hasContract(const std::string& funcName) const {
|
|
for (const auto& c : contracts)
|
|
if (c.functionName == funcName) return true;
|
|
return false;
|
|
}
|
|
|
|
bool hasFFI(const std::string& funcName) const {
|
|
for (const auto& f : ffiBoundaries)
|
|
if (f.functionName == funcName) return true;
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class APIBoundaryPreserver {
|
|
public:
|
|
static APIBoundaryReport analyze(const std::string& source,
|
|
const MigrationUnit& unit) {
|
|
APIBoundaryReport report;
|
|
report.moduleName = unit.moduleName;
|
|
report.sourceLanguage = unit.sourceLanguage;
|
|
report.targetLanguage = unit.targetLanguage;
|
|
|
|
// Extract public API from source
|
|
report.publicAPI = extractPublicFunctions(source, unit.exports);
|
|
|
|
// Generate type mappings
|
|
report.typeMappings = generateTypeMappings(source, unit);
|
|
|
|
// Generate contracts for public functions
|
|
report.contracts = generateContracts(report.publicAPI, source);
|
|
|
|
// Generate FFI boundaries for mixed-language operation
|
|
report.ffiBoundaries = generateFFIBoundaries(report.publicAPI, unit);
|
|
|
|
report.apiPreserved = true;
|
|
return report;
|
|
}
|
|
|
|
static bool verifyPreservation(const APIBoundaryReport& original,
|
|
const APIBoundaryReport& migrated) {
|
|
// Every original public function must exist in migrated
|
|
for (const auto& f : original.publicAPI) {
|
|
if (!migrated.hasFunction(f.name)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
static std::vector<FunctionSignature> extractPublicFunctions(
|
|
const std::string& source, const std::vector<std::string>& exports) {
|
|
std::vector<FunctionSignature> sigs;
|
|
for (const auto& name : exports) {
|
|
FunctionSignature sig;
|
|
sig.name = name;
|
|
sig.isPublic = true;
|
|
// Extract return type and params from source (heuristic)
|
|
extractSignatureDetails(source, name, sig);
|
|
sigs.push_back(std::move(sig));
|
|
}
|
|
return sigs;
|
|
}
|
|
|
|
static void extractSignatureDetails(const std::string& source,
|
|
const std::string& name,
|
|
FunctionSignature& sig) {
|
|
// Find "type name(" pattern
|
|
size_t namePos = source.find(name + "(");
|
|
if (namePos == std::string::npos) {
|
|
sig.returnType = "int"; // default for C
|
|
return;
|
|
}
|
|
// Extract return type (word before function name)
|
|
size_t start = namePos;
|
|
while (start > 0 && source[start - 1] == ' ') --start;
|
|
size_t typeEnd = start;
|
|
while (start > 0 && (std::isalnum((unsigned char)source[start - 1]) ||
|
|
source[start - 1] == '_' || source[start - 1] == '*')) --start;
|
|
sig.returnType = source.substr(start, typeEnd - start);
|
|
if (sig.returnType.empty()) sig.returnType = "int";
|
|
|
|
// Extract parameter list
|
|
size_t parenOpen = source.find('(', namePos);
|
|
size_t parenClose = source.find(')', parenOpen);
|
|
if (parenOpen != std::string::npos && parenClose != std::string::npos) {
|
|
std::string params = source.substr(parenOpen + 1, parenClose - parenOpen - 1);
|
|
parseParams(params, sig);
|
|
}
|
|
}
|
|
|
|
static void parseParams(const std::string& params, FunctionSignature& sig) {
|
|
if (params.empty() || params == "void") return;
|
|
size_t pos = 0;
|
|
while (pos < params.size()) {
|
|
size_t comma = params.find(',', pos);
|
|
if (comma == std::string::npos) comma = params.size();
|
|
std::string param = trim(params.substr(pos, comma - pos));
|
|
if (!param.empty()) {
|
|
// Split "type name" — last word is name, rest is type
|
|
size_t lastSpace = param.rfind(' ');
|
|
if (lastSpace != std::string::npos) {
|
|
sig.paramTypes.push_back(trim(param.substr(0, lastSpace)));
|
|
sig.paramNames.push_back(trim(param.substr(lastSpace + 1)));
|
|
} else {
|
|
sig.paramTypes.push_back(param);
|
|
sig.paramNames.push_back("arg" + std::to_string(sig.paramNames.size()));
|
|
}
|
|
}
|
|
pos = comma + 1;
|
|
}
|
|
}
|
|
|
|
static std::vector<TypeMapping> generateTypeMappings(const std::string& source,
|
|
const MigrationUnit& unit) {
|
|
std::vector<TypeMapping> mappings;
|
|
std::string tgt = unit.targetLanguage;
|
|
|
|
// Common C → target type mappings
|
|
auto addIf = [&](const std::string& pattern, const std::string& src,
|
|
const std::string& dst, const std::string& note) {
|
|
if (source.find(pattern) != std::string::npos)
|
|
mappings.push_back({src, unit.sourceLanguage, dst, tgt, note});
|
|
};
|
|
|
|
if (tgt == "rust") {
|
|
addIf("int ", "int", "i32", "C int maps to Rust i32");
|
|
addIf("char*", "char*", "&str / String", "C string to Rust string type");
|
|
addIf("void*", "void*", "*mut c_void", "Opaque pointer in FFI");
|
|
addIf("struct ", "struct", "struct", "C struct maps to Rust struct");
|
|
} else if (tgt == "java") {
|
|
addIf("int ", "int", "int", "Direct mapping");
|
|
addIf("char*", "char*", "String", "C string to Java String");
|
|
addIf("struct ", "struct", "class", "C struct maps to Java class");
|
|
} else if (tgt == "python") {
|
|
addIf("int ", "int", "int", "Direct mapping");
|
|
addIf("char*", "char*", "str", "C string to Python str");
|
|
}
|
|
return mappings;
|
|
}
|
|
|
|
static std::vector<APIMigrationContract> generateContracts(
|
|
const std::vector<FunctionSignature>& api, const std::string& source) {
|
|
std::vector<APIMigrationContract> contracts;
|
|
for (const auto& f : api) {
|
|
APIMigrationContract c;
|
|
c.functionName = f.name;
|
|
// Generate pre/post conditions from signature
|
|
if (!f.paramTypes.empty()) {
|
|
// Pointer params get non-null precondition
|
|
for (size_t i = 0; i < f.paramTypes.size(); ++i) {
|
|
if (f.paramTypes[i].find('*') != std::string::npos) {
|
|
c.precondition += f.paramNames[i] + " != NULL; ";
|
|
}
|
|
}
|
|
}
|
|
if (f.returnType.find('*') != std::string::npos) {
|
|
c.postcondition = "result may be NULL (caller must check)";
|
|
} else if (f.returnType == "int") {
|
|
c.postcondition = "returns int result";
|
|
}
|
|
if (!c.precondition.empty() || !c.postcondition.empty())
|
|
contracts.push_back(std::move(c));
|
|
}
|
|
return contracts;
|
|
}
|
|
|
|
static std::vector<FFIBoundary> generateFFIBoundaries(
|
|
const std::vector<FunctionSignature>& api, const MigrationUnit& unit) {
|
|
std::vector<FFIBoundary> boundaries;
|
|
for (const auto& f : api) {
|
|
FFIBoundary ffi;
|
|
ffi.functionName = f.name;
|
|
ffi.sourceLanguage = unit.sourceLanguage;
|
|
ffi.targetLanguage = unit.targetLanguage;
|
|
if (unit.targetLanguage == "rust") {
|
|
ffi.annotation = "@FFI(#[no_mangle] pub extern \"C\")";
|
|
} else if (unit.targetLanguage == "java") {
|
|
ffi.annotation = "@FFI(JNI native)";
|
|
} else {
|
|
ffi.annotation = "@FFI(extern)";
|
|
}
|
|
boundaries.push_back(std::move(ffi));
|
|
}
|
|
return boundaries;
|
|
}
|
|
|
|
static std::string trim(const std::string& s) {
|
|
size_t start = 0, end = s.size();
|
|
while (start < end && std::isspace((unsigned char)s[start])) ++start;
|
|
while (end > start && std::isspace((unsigned char)s[end - 1])) --end;
|
|
return s.substr(start, end - start);
|
|
}
|
|
};
|