diff --git a/include/StackUsageAnalyzer.hpp b/include/StackUsageAnalyzer.hpp index 9a1bc04..e774b41 100644 --- a/include/StackUsageAnalyzer.hpp +++ b/include/StackUsageAnalyzer.hpp @@ -110,13 +110,14 @@ enum class DescriptiveErrorCode AllocaUserControlled = 7, AllocaTooLarge = 8, AllocaUsageWarning = 9, - InvalidBaseReconstruction = 10 + InvalidBaseReconstruction = 10, + ConstParameterNotModified = 11 }; template<> struct EnumTraits { - static constexpr std::array names = { + static constexpr std::array names = { "None", "StackBufferOverflow", "NegativeStackIndex", @@ -127,7 +128,8 @@ struct EnumTraits "AllocaUserControlled", "AllocaTooLarge", "AllocaUsageWarning", - "InvalidBaseReconstruction" + "InvalidBaseReconstruction", + "ConstParameterNotModified" }; }; @@ -149,6 +151,7 @@ struct Diagnostic DiagnosticSeverity severity = DiagnosticSeverity::Warning; DescriptiveErrorCode errCode = DescriptiveErrorCode::None; + std::string ruleId; std::vector variableAliasingVec; std::string message; }; diff --git a/run_test.py b/run_test.py index 237d1a9..51b9249 100755 --- a/run_test.py +++ b/run_test.py @@ -20,7 +20,11 @@ def normalize(s: str) -> str: line = line.rstrip("\n") # "a b c" -> "a b c" parts = line.strip().split() - lines.append(" ".join(parts)) + normalized = " ".join(parts) + # Normalize spacing around pointer/reference symbols for cross-platform demangler output. + normalized = normalized.replace(" *", "*").replace("* ", "*") + normalized = normalized.replace(" &", "&").replace("& ", "&") + lines.append(normalized) return "\n".join(lines).strip() @@ -108,7 +112,7 @@ def check_help_flags() -> bool: return ok -def check_file(c_path: Path) -> bool: +def check_file(c_path: Path): """ Vérifie qu'avec ce fichier, toutes les attentes sont présentes dans la sortie de l'analyseur. @@ -117,48 +121,56 @@ def check_file(c_path: Path) -> bool: expectations = extract_expectations(c_path) if not expectations: print(" (no expectations found, skipping)\n") - return True + return True, 0, 0 analyzer_output = run_analyzer_on_file(c_path) norm_output = normalize(analyzer_output) all_ok = True + total = len(expectations) + passed = 0 for idx, exp in enumerate(expectations, start=1): norm_exp = normalize(exp) if norm_exp in norm_output: print(f" ✅ expectation #{idx} FOUND") + passed += 1 else: print(f" ❌ expectation #{idx} MISSING") print("----- Expected block -----") print(exp) print("----- Analyzer output (normalized) -----") - # tu peux commenter cette ligne si l'output est trop gros - # print(norm_output) + print(f"<{norm_output}>") print("---------------------------") all_ok = False print() - return all_ok + return all_ok, total, passed def main() -> int: global_ok = check_help_flags() + total_tests = 0 + passed_tests = 0 - c_files = sorted(TEST_DIR.glob("**/*.c")) + c_files = sorted(list(TEST_DIR.glob("**/*.c")) + list(TEST_DIR.glob("**/*.cpp"))) if not c_files: - print(f"No .c files found under {TEST_DIR}") + print(f"No .c/.cpp files found under {TEST_DIR}") return 0 if global_ok else 1 for f in c_files: - ok = check_file(f) + ok, total, passed = check_file(f) + passed_tests += passed + total_tests += total if not ok: global_ok = False if global_ok: print("✅ All tests passed.") + print(f"✅ Passed {passed_tests}/{total_tests} tests.") return 0 else: print("❌ Some tests failed.") + print(f"❌ Passed {passed_tests}/{total_tests} tests.") return 1 diff --git a/src/StackUsageAnalyzer.cpp b/src/StackUsageAnalyzer.cpp index 4125a90..aa17183 100644 --- a/src/StackUsageAnalyzer.cpp +++ b/src/StackUsageAnalyzer.cpp @@ -12,10 +12,13 @@ #include #include +#include +#include #include #include #include +#include #include #include #include @@ -34,6 +37,7 @@ #include #include "compilerlib/compiler.h" +#include "mangle.hpp" namespace ctrace::stack { @@ -162,6 +166,20 @@ struct InvalidBaseReconstructionIssue { bool isOutOfBounds = false; // true si on peut prouver que c'est hors bornes const llvm::Instruction *inst = nullptr; }; + +// Rapport interne pour les paramètres qui peuvent être rendus const (pointee/referent) +struct ConstParamIssue { + std::string funcName; + std::string paramName; + std::string currentType; + std::string suggestedType; + std::string suggestedTypeAlt; + bool pointerConstOnly = false; // ex: T * const param + bool isReference = false; + bool isRvalueRef = false; + unsigned line = 0; + unsigned column = 0; +}; // Analyse intra-fonction pour détecter les "fuites" de pointeurs de stack : // - retour d'une adresse de variable locale (return buf;) // - stockage de l'adresse d'une variable locale dans un global (global = buf;) @@ -1505,6 +1523,524 @@ static std::string deriveAllocaName(const llvm::AllocaInst *AI) return std::string(""); } +static std::string formatFunctionNameForMessage(const std::string &name) +{ + if (ctrace_tools::isMangled(name)) + return ctrace_tools::demangle(name.c_str()); + return name; +} + +struct TypeQualifiers { + bool isConst = false; + bool isVolatile = false; + bool isRestrict = false; +}; + +struct StrippedDIType { + const llvm::DIType *type = nullptr; + TypeQualifiers quals; +}; + +struct ParamDebugInfo { + std::string name; + const llvm::DIType *type = nullptr; + unsigned line = 0; + unsigned column = 0; +}; + +struct ParamTypeInfo { + const llvm::DIType *originalType = nullptr; + const llvm::DIType *pointeeType = nullptr; // unqualified, typedefs stripped + const llvm::DIType *pointeeDisplayType = nullptr; // unqualified, typedefs preserved + bool isPointer = false; + bool isReference = false; + bool isRvalueReference = false; + bool pointerConst = false; + bool pointerVolatile = false; + bool pointerRestrict = false; + bool pointeeConst = false; + bool pointeeVolatile = false; + bool pointeeRestrict = false; + bool isDoublePointer = false; + bool isVoid = false; + bool isFunctionPointer = false; +}; + +static const llvm::DIType *stripTypedefs(const llvm::DIType *type) +{ + using namespace llvm; + const DIType *cur = type; + while (cur) { + auto *DT = dyn_cast(cur); + if (!DT) + break; + auto tag = DT->getTag(); + if (tag == dwarf::DW_TAG_typedef) { + cur = DT->getBaseType(); + continue; + } + break; + } + return cur; +} + +static StrippedDIType stripQualifiers(const llvm::DIType *type) +{ + using namespace llvm; + StrippedDIType out; + out.type = type; + + while (out.type) { + auto *DT = dyn_cast(out.type); + if (!DT) + break; + auto tag = DT->getTag(); + if (tag == dwarf::DW_TAG_const_type) { + out.quals.isConst = true; + out.type = DT->getBaseType(); + continue; + } + if (tag == dwarf::DW_TAG_volatile_type) { + out.quals.isVolatile = true; + out.type = DT->getBaseType(); + continue; + } + if (tag == dwarf::DW_TAG_restrict_type) { + out.quals.isRestrict = true; + out.type = DT->getBaseType(); + continue; + } + break; + } + + return out; +} + +static std::string formatDITypeName(const llvm::DIType *type) +{ + using namespace llvm; + if (!type) + return std::string(""); + + if (auto *BT = dyn_cast(type)) { + if (!BT->getName().empty()) + return BT->getName().str(); + } + + if (auto *CT = dyn_cast(type)) { + if (!CT->getName().empty()) + return CT->getName().str(); + if (!CT->getIdentifier().empty()) + return CT->getIdentifier().str(); + } + + if (auto *DT = dyn_cast(type)) { + auto tag = DT->getTag(); + if (tag == dwarf::DW_TAG_typedef && !DT->getName().empty()) { + return DT->getName().str(); + } + if ((tag == dwarf::DW_TAG_const_type) || + (tag == dwarf::DW_TAG_volatile_type) || + (tag == dwarf::DW_TAG_restrict_type)) { + return formatDITypeName(DT->getBaseType()); + } + if (!DT->getName().empty()) + return DT->getName().str(); + } + + if (auto *ST = dyn_cast(type)) { + (void)ST; + return std::string(""); + } + + return std::string(""); +} + +static bool buildParamTypeInfo(const llvm::DIType *type, + ParamTypeInfo &info) +{ + using namespace llvm; + if (!type) + return false; + + info.originalType = type; + + StrippedDIType top = stripQualifiers(type); + info.pointerConst = top.quals.isConst; + info.pointerVolatile = top.quals.isVolatile; + info.pointerRestrict = top.quals.isRestrict; + + const DIType *topType = stripTypedefs(top.type); + auto *derived = dyn_cast(topType); + if (!derived) + return false; + + auto tag = derived->getTag(); + if (tag == dwarf::DW_TAG_pointer_type) { + info.isPointer = true; + } else if (tag == dwarf::DW_TAG_reference_type) { + info.isReference = true; + } else if (tag == dwarf::DW_TAG_rvalue_reference_type) { + info.isReference = true; + info.isRvalueReference = true; + } else { + return false; + } + + const DIType *baseType = derived->getBaseType(); + StrippedDIType base = stripQualifiers(baseType); + info.pointeeConst = base.quals.isConst; + info.pointeeVolatile = base.quals.isVolatile; + info.pointeeRestrict = base.quals.isRestrict; + info.pointeeDisplayType = base.type ? base.type : baseType; + + const DIType *baseNoTypedef = stripTypedefs(base.type); + info.pointeeType = baseNoTypedef; + + if (!baseNoTypedef) + return true; + + if (auto *baseDerived = dyn_cast(baseNoTypedef)) { + auto baseTag = baseDerived->getTag(); + if (baseTag == dwarf::DW_TAG_pointer_type || + baseTag == dwarf::DW_TAG_reference_type || + baseTag == dwarf::DW_TAG_rvalue_reference_type) { + info.isDoublePointer = true; + } + } + + if (isa(baseNoTypedef)) + info.isFunctionPointer = true; + + if (auto *basic = dyn_cast(baseNoTypedef)) { + if (basic->getName() == "void") + info.isVoid = true; + } + + return true; +} + +static std::string buildTypeString(const ParamTypeInfo &info, + const std::string &baseName, + bool addPointeeConst, + bool includePointerConst, + const std::string ¶mName) +{ + std::string out; + if (info.pointeeConst || addPointeeConst) + out += "const "; + if (info.pointeeVolatile) + out += "volatile "; + out += baseName.empty() ? std::string("") : baseName; + + if (info.isReference) { + out += info.isRvalueReference ? " &&" : " &"; + if (!paramName.empty()) { + out += paramName; + } + return out; + } + + if (info.isPointer) { + out += " *"; + if (includePointerConst && info.pointerConst) + out += " const"; + if (info.pointerVolatile) + out += " volatile"; + if (info.pointerRestrict) + out += " restrict"; + } + + if (!paramName.empty()) { + if (!out.empty() && (out.back() == '*' || out.back() == '&')) + out += paramName; + else + out += " " + paramName; + } + + return out; +} + +static std::string buildPointeeQualPrefix(const ParamTypeInfo &info, + bool addConst) +{ + std::string out; + if (addConst) + out += "const "; + if (info.pointeeVolatile) + out += "volatile "; + if (info.pointeeRestrict) + out += "restrict "; + return out; +} + +static ParamDebugInfo getParamDebugInfo(const llvm::Function &F, + const llvm::Argument &Arg) +{ + using namespace llvm; + ParamDebugInfo info; + info.name = Arg.getName().str(); + + if (auto *SP = F.getSubprogram()) { + for (DINode *node : SP->getRetainedNodes()) { + auto *var = dyn_cast(node); + if (!var || !var->isParameter()) + continue; + if (var->getArg() != Arg.getArgNo() + 1) + continue; + if (!var->getName().empty()) + info.name = var->getName().str(); + info.type = var->getType(); + if (var->getLine() != 0) + info.line = var->getLine(); + break; + } + + if (!info.type) { + if (auto *subTy = SP->getType()) { + auto types = subTy->getTypeArray(); + if (types.size() > Arg.getArgNo() + 1) + info.type = types[Arg.getArgNo() + 1]; + } + } + + if (info.line == 0) + info.line = SP->getLine(); + } + + return info; +} + +static bool calleeParamIsReadOnly(const llvm::Function *callee, + unsigned argIndex) +{ + if (!callee || argIndex >= callee->arg_size()) + return false; + + const llvm::Argument ¶m = *callee->getArg(argIndex); + ParamDebugInfo dbg = getParamDebugInfo(*callee, param); + if (!dbg.type) + return false; + + ParamTypeInfo typeInfo; + if (!buildParamTypeInfo(dbg.type, typeInfo)) + return false; + + if (typeInfo.isDoublePointer || typeInfo.isVoid || typeInfo.isFunctionPointer) + return false; + + if (!typeInfo.isPointer && !typeInfo.isReference) + return false; + + return typeInfo.pointeeConst; +} + +static bool callArgMayWriteThrough(const llvm::CallBase &CB, + unsigned argIndex) +{ + using namespace llvm; + + const Function *callee = CB.getCalledFunction(); + if (!callee) { + const Value *called = CB.getCalledOperand(); + if (called) + called = called->stripPointerCasts(); + callee = dyn_cast(called); + } + + if (!callee) + return true; + + if (auto *MI = dyn_cast(&CB)) { + if (isa(MI)) + return argIndex == 0; + if (isa(MI)) + return argIndex == 0; + } + + if (callee->isIntrinsic()) { + switch (callee->getIntrinsicID()) { + case Intrinsic::dbg_declare: + case Intrinsic::dbg_value: + case Intrinsic::dbg_label: + case Intrinsic::lifetime_start: + case Intrinsic::lifetime_end: + case Intrinsic::invariant_start: + case Intrinsic::invariant_end: + case Intrinsic::assume: + return false; + default: + break; + } + } + + if (callee->doesNotAccessMemory()) + return false; + if (callee->onlyReadsMemory()) + return false; + + if (argIndex >= callee->arg_size()) + return true; // varargs or unknown + + const AttributeList &attrs = callee->getAttributes(); + if (attrs.hasParamAttr(argIndex, Attribute::ReadOnly) || + attrs.hasParamAttr(argIndex, Attribute::ReadNone)) { + return false; + } + if (attrs.hasParamAttr(argIndex, Attribute::WriteOnly)) + return true; + + if (calleeParamIsReadOnly(callee, argIndex)) + return false; + + return true; +} + +static bool valueMayBeWrittenThrough(const llvm::Value *root, + const llvm::Function &F) +{ + using namespace llvm; + (void)F; + + SmallPtrSet visited; + SmallVector worklist; + worklist.push_back(root); + + while (!worklist.empty()) { + const Value *V = worklist.pop_back_val(); + if (!visited.insert(V).second) + continue; + + for (const Use &U : V->uses()) { + const User *Usr = U.getUser(); + + if (auto *SI = dyn_cast(Usr)) { + if (SI->getPointerOperand() == V) + return true; + if (SI->getValueOperand() == V) { + const Value *dst = SI->getPointerOperand()->stripPointerCasts(); + if (auto *AI = dyn_cast(dst)) { + for (const Use &AU : AI->uses()) { + if (auto *LI = dyn_cast(AU.getUser())) { + if (LI->getPointerOperand()->stripPointerCasts() == AI) + worklist.push_back(LI); + } + } + } else { + return true; // pointer escapes to non-local memory + } + } + continue; + } + + if (auto *AI = dyn_cast(Usr)) { + if (AI->getPointerOperand() == V) + return true; + continue; + } + + if (auto *CX = dyn_cast(Usr)) { + if (CX->getPointerOperand() == V) + return true; + continue; + } + + if (auto *CB = dyn_cast(Usr)) { + for (unsigned i = 0; i < CB->arg_size(); ++i) { + if (CB->getArgOperand(i) == V) { + if (callArgMayWriteThrough(*CB, i)) + return true; + } + } + continue; + } + + if (auto *GEP = dyn_cast(Usr)) { + worklist.push_back(GEP); + continue; + } + if (auto *BC = dyn_cast(Usr)) { + worklist.push_back(BC); + continue; + } + if (auto *ASC = dyn_cast(Usr)) { + worklist.push_back(ASC); + continue; + } + if (auto *PN = dyn_cast(Usr)) { + if (PN->getType()->isPointerTy()) + worklist.push_back(PN); + continue; + } + if (auto *Sel = dyn_cast(Usr)) { + if (Sel->getType()->isPointerTy()) + worklist.push_back(Sel); + continue; + } + if (auto *CI = dyn_cast(Usr)) { + if (CI->getType()->isPointerTy()) + worklist.push_back(CI); + continue; + } + if (isa(Usr)) + return true; // unknown aliasing, be conservative + } + } + + return false; +} + +static void analyzeConstParamsInFunction(llvm::Function &F, + std::vector &out) +{ + using namespace llvm; + + if (F.isDeclaration()) + return; + + for (Argument &Arg : F.args()) { + ParamDebugInfo dbg = getParamDebugInfo(F, Arg); + if (!dbg.type) + continue; + + ParamTypeInfo typeInfo; + if (!buildParamTypeInfo(dbg.type, typeInfo)) + continue; + + if (!typeInfo.isPointer && !typeInfo.isReference) + continue; + if (typeInfo.isDoublePointer || typeInfo.isVoid || typeInfo.isFunctionPointer) + continue; + if (typeInfo.pointeeConst) + continue; + + if (valueMayBeWrittenThrough(&Arg, F)) + continue; + + ConstParamIssue issue; + issue.funcName = F.getName().str(); + issue.paramName = dbg.name.empty() ? Arg.getName().str() : dbg.name; + issue.line = dbg.line; + issue.column = dbg.column; + issue.pointerConstOnly = typeInfo.isPointer && typeInfo.pointerConst && !typeInfo.pointeeConst; + issue.isReference = typeInfo.isReference; + issue.isRvalueRef = typeInfo.isRvalueReference; + + std::string baseName = formatDITypeName(typeInfo.pointeeDisplayType); + issue.currentType = buildTypeString(typeInfo, baseName, false, true, issue.paramName); + if (typeInfo.isRvalueReference) { + std::string valuePrefix = buildPointeeQualPrefix(typeInfo, false); + std::string constRefPrefix = buildPointeeQualPrefix(typeInfo, true); + issue.suggestedType = valuePrefix + baseName + " " + issue.paramName; + issue.suggestedTypeAlt = constRefPrefix + baseName + " &" + issue.paramName; + } else { + issue.suggestedType = buildTypeString(typeInfo, baseName, true, false, issue.paramName); + } + + out.push_back(std::move(issue)); + } +} + // Forward declaration : essaie de retrouver une constante derrière une Value static const llvm::ConstantInt* tryGetConstFromValue(const llvm::Value *V, const llvm::Function &F); @@ -3271,6 +3807,75 @@ AnalysisResult analyzeModule(llvm::Module &mod, result.diagnostics.push_back(std::move(diag)); } + // 15) Const-correctness: parameters that can be made const + std::vector constParamIssues; + for (llvm::Function &F : mod) { + if (F.isDeclaration()) + continue; + analyzeConstParamsInFunction(F, constParamIssues); + } + + for (const auto &cp : constParamIssues) + { + std::ostringstream body; + Diagnostic diag; + std::string displayFuncName = formatFunctionNameForMessage(cp.funcName); + + diag.severity = DiagnosticSeverity::Info; + diag.errCode = DescriptiveErrorCode::ConstParameterNotModified; + + const char *prefix = "[!]"; + if (diag.severity == DiagnosticSeverity::Warning) + prefix = "[!!]"; + else if (diag.severity == DiagnosticSeverity::Error) + prefix = "[!!!]"; + + const char *subLabel = "Pointer"; + if (cp.pointerConstOnly) { + subLabel = "PointerConstOnly"; + } else if (cp.isReference) { + subLabel = cp.isRvalueRef ? "ReferenceRvaluePreferValue" : "Reference"; + } + + if (cp.isRvalueRef) { + body << " " << prefix << "ConstParameterNotModified." << subLabel + << ": parameter '" << cp.paramName << "' in function '" << displayFuncName + << "' is an rvalue reference and is never used to modify the referred object\n"; + body << " consider passing by value (" << cp.suggestedType + << ") or const reference (" << cp.suggestedTypeAlt << ")\n"; + body << " current type: " << cp.currentType << "\n"; + } else if (cp.pointerConstOnly) { + body << " " << prefix << "ConstParameterNotModified." << subLabel + << ": parameter '" << cp.paramName << "' in function '" << displayFuncName + << "' is declared '" << cp.currentType + << "' but the pointed object is never modified\n"; + body << " consider '" << cp.suggestedType + << "' for API const-correctness\n"; + } else { + body << " " << prefix << "ConstParameterNotModified." << subLabel + << ": parameter '" << cp.paramName << "' in function '" << displayFuncName + << "' is never used to modify the " + << (cp.isReference ? "referred" : "pointed") + << " object\n"; + } + + if (!cp.isRvalueRef) { + body << " current type: " << cp.currentType << "\n"; + body << " suggested type: " << cp.suggestedType << "\n"; + } + + diag.funcName = cp.funcName; + diag.line = cp.line; + diag.column = cp.column; + diag.startLine = cp.line; + diag.startColumn = cp.column; + diag.endLine = cp.line; + diag.endColumn = cp.column; + diag.message = body.str(); + diag.ruleId = std::string("ConstParameterNotModified.") + subLabel; + result.diagnostics.push_back(std::move(diag)); + } + return result; } @@ -3331,8 +3936,11 @@ AnalysisResult analyzeFile(const std::string &filename, args.push_back("-emit-llvm"); args.push_back("-S"); args.push_back("-g"); - // args.push_back("-O0"); - // std::cout << "-O0 enabled for stack analysis compilation\n"; + if (lang == LanguageType::CXX) { + args.push_back("-x"); + args.push_back("c++"); + args.push_back("-std=gnu++17"); + } args.push_back("-fno-discard-value-names"); args.push_back(filename); compilerlib::OutputMode mode = compilerlib::OutputMode::ToMemory; @@ -3503,7 +4111,10 @@ std::string toJson(const AnalysisResult &result, os << " {\n"; os << " \"id\": \"diag-" << (i + 1) << "\",\n"; os << " \"severity\": \"" << ctrace::stack::enumToString(d.severity) << "\",\n"; - os << " \"ruleId\": \"" << ctrace::stack::enumToString(d.errCode) << "\",\n"; + const std::string ruleId = d.ruleId.empty() + ? std::string(ctrace::stack::enumToString(d.errCode)) + : d.ruleId; + os << " \"ruleId\": \"" << jsonEscape(ruleId) << "\",\n"; os << " \"location\": {\n"; os << " \"function\": \"" << jsonEscape(d.funcName) << "\",\n"; @@ -3556,7 +4167,10 @@ std::string toSarif(const AnalysisResult &result, const auto &d = result.diagnostics[i]; os << " {\n"; // Pour le moment, un seul ruleId générique; tu pourras le spécialiser plus tard. - os << " \"ruleId\": \"CORETRACE_STACK_DIAGNOSTIC\",\n"; + const std::string ruleId = d.ruleId.empty() + ? std::string(ctrace::stack::enumToString(d.errCode)) + : d.ruleId; + os << " \"ruleId\": \"" << jsonEscape(ruleId) << "\",\n"; os << " \"level\": \"" << severityToSarifLevel(d.severity) << "\",\n"; os << " \"message\": { \"text\": \"" << jsonEscape(d.message) << "\" },\n"; os << " \"locations\": [\n"; diff --git a/test/pointer_reference-const_correctness/advanced-cases.c b/test/pointer_reference-const_correctness/advanced-cases.c new file mode 100644 index 0000000..5c0af6f --- /dev/null +++ b/test/pointer_reference-const_correctness/advanced-cases.c @@ -0,0 +1,59 @@ +#include +#include +#include + +void consume_const(const int *p) { + if (p) { + (void)*p; + } +} + +void consume_mut(int *p) { + if (p) { + *p = 1; + } +} + +// at line 21, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'p' in function 'caller_const' is never used to modify the pointed object +// current type: int *p +// suggested type: const int *p +void caller_const(int *p) { + consume_const(p); +} + +void caller_mut(int *p) { + consume_mut(p); +} + +// at line 33, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'p' in function 'read_only' is never used to modify the pointed object +// current type: int *p +// suggested type: const int *p +void read_only(int *p) { + int v = *p; + printf("%d\n", v); +} + +void variadic_use(int *p) { + printf("%p\n", (void *)p); +} + +// at line 46, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'reg' in function 'read_mmio' is never used to modify the pointed object +// current type: volatile int *reg +// suggested type: const volatile int *reg +void read_mmio(volatile int *reg) { + int v = *reg; + (void)v; +} + +void takes_double(int **pp) { + if (pp) { + (void)*pp; + } +} + +void takes_void(void *p) { + (void)p; +} diff --git a/test/pointer_reference-const_correctness/advanced-cases.cpp b/test/pointer_reference-const_correctness/advanced-cases.cpp new file mode 100644 index 0000000..4c7f6f4 --- /dev/null +++ b/test/pointer_reference-const_correctness/advanced-cases.cpp @@ -0,0 +1,38 @@ +#include + +void consume_ref(const int &v) { + (void)v; +} + +void consume_ref_mut(int &v) { + v += 1; +} + +// at line 15, column 0 +// [!]ConstParameterNotModified.Reference: parameter 'v' in function 'caller_ref(int&)' is never used to modify the referred object +// current type: int &v +// suggested type: const int &v +void caller_ref(int &v) { + consume_ref(v); +} + +void caller_ref_mut(int &v) { + consume_ref_mut(v); +} + +// at line 27, column 0 +// [!]ConstParameterNotModified.ReferenceRvaluePreferValue: parameter 'v' in function 'rvalue_use(int&&)' is an rvalue reference and is never used to modify the referred object +// consider passing by value (int v) or const reference (const int &v) +// current type: int &&v +int rvalue_use(int &&v) { + return v; +} + +// at line 35, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'p' in function 'read_only_ptr(int*)' is never used to modify the pointed object +// current type: int *p +// suggested type: const int *p +void read_only_ptr(int *p) { + int x = *p; + (void)x; +} diff --git a/test/pointer_reference-const_correctness/const-mixed.c b/test/pointer_reference-const_correctness/const-mixed.c new file mode 100644 index 0000000..b037829 --- /dev/null +++ b/test/pointer_reference-const_correctness/const-mixed.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +// at line 10, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'values' in function 'print_sum' is never used to modify the pointed object +// current type: int *values +// suggested type: const int *values +void print_sum(int *values, size_t count) { + int sum = 0; + for (size_t i = 0; i < count; ++i) { + sum += values[i]; + } + printf("%d\n", sum); +} + +void copy_data(const char *source, char * const dest, size_t len) { + memcpy(dest, source, len); +} + +void read_data(char * const buffer) { + printf("%s\n", buffer); +} + +// at line 35, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'a' in function 'get_max' is never used to modify the pointed object +// current type: int *a +// suggested type: const int *a + +// at line 35, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'b' in function 'get_max' is never used to modify the pointed object +// current type: int *b +// suggested type: const int *b +int get_max(int *a, int *b) { + return (*a > *b) ? *a : *b; +} + +struct Point { int x, y; }; + +// at line 50, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'p1' in function 'distance' is never used to modify the pointed object +// current type: Point *p1 +// suggested type: const Point *p1 + +// at line 50, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'p2' in function 'distance' is never used to modify the pointed object +// current type: Point *p2 +// suggested type: const Point *p2 +int distance(struct Point *p1, struct Point *p2) { + return abs(p1->x - p2->x) + abs(p1->y - p2->y); +} diff --git a/test/pointer_reference-const_correctness/const-readonly.c b/test/pointer_reference-const_correctness/const-readonly.c new file mode 100644 index 0000000..7c95750 --- /dev/null +++ b/test/pointer_reference-const_correctness/const-readonly.c @@ -0,0 +1,38 @@ +#include +#include + +void increment(int *value) { + (*value)++; +} + +void modify(int *ptr) { + *ptr = 42; +} + +void process(int *data) { + modify(data); // assume modify(int *) changes pointee +} + +void fill_array(int *arr, size_t n, bool condition) { + for (size_t i = 0; i < n; ++i) { + if (condition) arr[i] = 0; + } +} + +// at line 26, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'reg' in function 'read_volatile' is never used to modify the pointed object +// current type: volatile int *reg +// suggested type: const volatile int *reg +void read_volatile(volatile int *reg) { + int val = *reg; + // use val +} + +// at line 35, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'value' in function 'log' is never used to modify the pointed object +// current type: int *value +// suggested type: const int *value +void log(int *value) { + printf("%d\n", *value); // read + // but if printf were variadic mutating, conservative +} diff --git a/test/pointer_reference-const_correctness/readonly-pointer.c b/test/pointer_reference-const_correctness/readonly-pointer.c new file mode 100644 index 0000000..d9b0288 --- /dev/null +++ b/test/pointer_reference-const_correctness/readonly-pointer.c @@ -0,0 +1,21 @@ +#include + +// at line 13, column 0 +// [!]ConstParameterNotModified.Pointer: parameter 'param3' in function 'myfunc' is never used to modify the pointed object +// current type: int32_t *param3 +// suggested type: const int32_t *param3 + +// at line 13, column 0 +// [!]ConstParameterNotModified.PointerConstOnly: parameter 'param4' in function 'myfunc' is declared 'int32_t * const param4' but the pointed object is never modified +// consider 'const int32_t *param4' for API const-correctness +// current type: int32_t * const param4 +// suggested type: const int32_t *param4 +void myfunc(int32_t *out, const int32_t *in, int32_t *param3, int32_t * const param4) +{ + *out = *in + *param3 + *param4; +} + +void set_zero(int *p) +{ + *p = 0; +} diff --git a/test/pointer_reference-const_correctness/readonly-reference.cpp b/test/pointer_reference-const_correctness/readonly-reference.cpp new file mode 100644 index 0000000..8b950b9 --- /dev/null +++ b/test/pointer_reference-const_correctness/readonly-reference.cpp @@ -0,0 +1,17 @@ +// at line 5, column 0 +// [!]ConstParameterNotModified.Reference: parameter 'inc' in function 'increment(int&, int&)' is never used to modify the referred object +// current type: int &inc +// suggested type: const int &inc +void increment(int &value, int &inc) +{ + value += inc; +} + +// at line 14, column 0 +// [!]ConstParameterNotModified.ReferenceRvaluePreferValue: parameter 'value' in function 'read_once(int&&)' is an rvalue reference and is never used to modify the referred object +// consider passing by value (int value) or const reference (const int &value) +// current type: int &&value +int read_once(int &&value) +{ + return value; +}