Index: clang/lib/Analysis/UnsafeBufferUsage.cpp =================================================================== --- clang/lib/Analysis/UnsafeBufferUsage.cpp +++ clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -8,7 +8,9 @@ #include "clang/Analysis/Analyses/UnsafeBufferUsage.h" #include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/Preprocessor.h" @@ -22,6 +24,52 @@ using namespace clang; using namespace ast_matchers; +#ifndef NDEBUG +namespace { +class StmtDebugPrinter + : public ConstStmtVisitor { +public: + std::string VisitStmt(const Stmt *S) { return S->getStmtClassName(); } + + std::string VisitBinaryOperator(const BinaryOperator *BO) { + return "BinaryOperator(" + BO->getOpcodeStr().str() + ")"; + } + + std::string VisitUnaryOperator(const UnaryOperator *UO) { + return "UnaryOperator(" + UO->getOpcodeStr(UO->getOpcode()).str() + ")"; + } + + std::string VisitImplicitCastExpr(const ImplicitCastExpr *ICE) { + return "ImplicitCastExpr(" + std::string(ICE->getCastKindName()) + ")"; + } +}; + +// Returns a string of ancestor `Stmt`s of the given `DRE` in such a form: +// "DRE ==> parent-of-DRE ==> grandparent-of-DRE ==> ...". +static std::string getDREAncestorString(const DeclRefExpr *DRE, + ASTContext &Ctx) { + std::stringstream SS; + const Stmt *St = DRE; + StmtDebugPrinter StmtPriner; + + do { + SS << StmtPriner.Visit(St); + + DynTypedNodeList StParents = Ctx.getParents(*St); + + if (StParents.size() > 1) + return "unavailable due to multiple parents"; + if (StParents.size() == 0) + break; + St = StParents.begin()->get(); + if (St) + SS << " ==> "; + } while (St); + return SS.str(); +} +} // namespace +#endif /* NDEBUG */ + namespace clang::ast_matchers { // A `RecursiveASTVisitor` that traverses all descendants of a given node "n" // except for those belonging to a different callable of "n". @@ -2589,11 +2637,15 @@ #ifndef NDEBUG auto AllUnclaimed = Tracker.getUnclaimedUses(it->first); for (auto UnclaimedDRE : AllUnclaimed) { - Handler.addDebugNoteForVar( - it->first, UnclaimedDRE->getBeginLoc(), - ("failed to produce fixit for '" + it->first->getNameAsString() + - "' : has an unclaimed use")); - } + std::string UnclaimedUseTrace = + getDREAncestorString(UnclaimedDRE, D->getASTContext()); + + Handler.addDebugNoteForVar( + it->first, UnclaimedDRE->getBeginLoc(), + ("failed to produce fixit for '" + it->first->getNameAsString() + + "' : has an unclaimed use\nThe unclaimed DRE trace: " + + UnclaimedUseTrace)); + } #endif it = FixablesForAllVars.byVar.erase(it); } else if (it->first->isInitCapture()) { Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-debug-unclaimed/lit.local.cfg =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-debug-unclaimed/lit.local.cfg @@ -0,0 +1,11 @@ +# -*- Python -*- + +config.substitutions.append( + ( + "%analyze_safe_buffer_debug_notes", + "'%s' %s" % ( + config.python_executable, + os.path.join(config.clang_src_dir, "utils", "analyze_safe_buffer_debug_notes.py") + ) + ) +) Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-debug-unclaimed/warn-unsafe-buffer-usage-debug-unclaimed.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-debug-unclaimed/warn-unsafe-buffer-usage-debug-unclaimed.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -Wno-unused-value -Wunsafe-buffer-usage -fsafe-buffer-usage-suggestions \ +// RUN: -mllvm -debug-only=SafeBuffers \ +// RUN: -std=c++20 -verify=expected %s + +// RUN: %clang_cc1 -Wno-unused-value -Wunsafe-buffer-usage -fsafe-buffer-usage-suggestions \ +// RUN: -mllvm -debug-only=SafeBuffers \ +// RUN: -std=c++20 %s \ +// RUN: 2>&1 | grep 'The unclaimed DRE trace:' \ +// RUN: | sed 's/^The unclaimed DRE trace://' \ +// RUN: | %analyze_safe_buffer_debug_notes \ +// RUN: | FileCheck %s + +// This debugging facility is only available in debug builds. +// +// REQUIRES: asserts +// REQUIRES: shell + +void test_unclaimed_use(int *p) { // expected-warning{{'p' is an unsafe pointer used for buffer access}} + p++; // expected-note{{used in pointer arithmetic here}} \ + expected-note{{safe buffers debug: failed to produce fixit for 'p' : has an unclaimed use\n \ + The unclaimed DRE trace: DeclRefExpr, UnaryOperator(++), CompoundStmt}} + *((p + 1) + 1); // expected-warning{{unsafe pointer arithmetic}} \ + expected-note{{used in pointer arithmetic here}} \ + expected-note{{safe buffers debug: failed to produce fixit for 'p' : has an unclaimed use\n \ + The unclaimed DRE trace: DeclRefExpr, ImplicitCastExpr(LValueToRValue), BinaryOperator(+), ParenExpr, BinaryOperator(+), ParenExpr, UnaryOperator(*), CompoundStmt}} + p -= 1; // expected-note{{used in pointer arithmetic here}} \ + expected-note{{safe buffers debug: failed to produce fixit for 'p' : has an unclaimed use\n \ + The unclaimed DRE trace: DeclRefExpr, BinaryOperator(-=), CompoundStmt}} + p--; // expected-note{{used in pointer arithmetic here}} \ + expected-note{{safe buffers debug: failed to produce fixit for 'p' : has an unclaimed use\n \ + The unclaimed DRE trace: DeclRefExpr, UnaryOperator(--), CompoundStmt}} + p[5] = 5; // expected-note{{used in buffer access here}} +} + +// CHECK: Root # 1 +// CHECK: |- DeclRefExpr # 4 +// CHECK: |-- UnaryOperator(++) # 1 +// CHECK: |--- CompoundStmt # 1 +// CHECK: |-- ImplicitCastExpr(LValueToRValue) # 1 +// CHECK: |--- BinaryOperator(+) # 1 +// CHECK: |---- ParenExpr # 1 +// CHECK: |----- BinaryOperator(+) # 1 +// CHECK: |------ ParenExpr # 1 +// CHECK: |------- UnaryOperator(*) # 1 +// CHECK: |-------- CompoundStmt # 1 +// CHECK: |-- BinaryOperator(-=) # 1 +// CHECK: |--- CompoundStmt # 1 +// CHECK: |-- UnaryOperator(--) # 1 +// CHECK: |--- CompoundStmt # 1 Index: clang/utils/analyze_safe_buffer_debug_notes.py =================================================================== --- /dev/null +++ clang/utils/analyze_safe_buffer_debug_notes.py @@ -0,0 +1,39 @@ +import sys +from collections import OrderedDict + +class Trie: + def __init__(self, name): + self.name = name + self.children = OrderedDict() + self.count = 1 + + def add(self, name): + if name in self.children: + self.children[name].count += 1 + else: + self.children[name] = Trie(name) + return self.children[name] + + def print(self, depth): + if depth > 0: + print('|', end="") + for i in range(depth): + print('-', end="") + if depth > 0: + print(end=" ") + print(self.name, '#', self.count) + for key, child in self.children.items(): + child.print(depth + 1) + + +Root = Trie("Root") + +if __name__ == "__main__": + for line in sys.stdin: + words = line.split('==>') + words = [word.strip() for word in words] + MyTrie = Root; + for word in words: + MyTrie = MyTrie.add(word) + + Root.print(0)