1//===---------- IssueHash.cpp - Generate identification hashes --*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang/Analysis/IssueHash.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/Decl.h"
12#include "clang/AST/DeclCXX.h"
13#include "clang/Basic/SourceManager.h"
14#include "clang/Lex/Lexer.h"
15#include "llvm/ADT/StringRef.h"
16#include "llvm/ADT/Twine.h"
17#include "llvm/Support/LineIterator.h"
18#include "llvm/Support/MD5.h"
19
20#include <optional>
21#include <sstream>
22#include <string>
23
24using namespace clang;
25
26// Get a string representation of the parts of the signature that can be
27// overloaded on.
28static std::string GetSignature(const FunctionDecl *Target) {
29 if (!Target)
30 return "";
31 std::string Signature;
32
33 // When a flow sensitive bug happens in templated code we should not generate
34 // distinct hash value for every instantiation. Use the signature from the
35 // primary template.
36 if (const FunctionDecl *InstantiatedFrom =
37 Target->getTemplateInstantiationPattern())
38 Target = InstantiatedFrom;
39
40 if (!isa<CXXConstructorDecl>(Val: Target) && !isa<CXXDestructorDecl>(Val: Target) &&
41 !isa<CXXConversionDecl>(Val: Target))
42 Signature.append(str: Target->getReturnType().getAsString()).append(s: " ");
43 Signature.append(str: Target->getQualifiedNameAsString()).append(s: "(");
44
45 for (int i = 0, paramsCount = Target->getNumParams(); i < paramsCount; ++i) {
46 if (i)
47 Signature.append(s: ", ");
48 Signature.append(str: Target->getParamDecl(i)->getType().getAsString());
49 }
50
51 if (Target->isVariadic())
52 Signature.append(s: ", ...");
53 Signature.append(s: ")");
54
55 const auto *TargetT =
56 llvm::dyn_cast_or_null<FunctionType>(Val: Target->getType().getTypePtr());
57
58 if (!TargetT || !isa<CXXMethodDecl>(Val: Target))
59 return Signature;
60
61 if (TargetT->isConst())
62 Signature.append(s: " const");
63 if (TargetT->isVolatile())
64 Signature.append(s: " volatile");
65 if (TargetT->isRestrict())
66 Signature.append(s: " restrict");
67
68 if (const auto *TargetPT =
69 dyn_cast_or_null<FunctionProtoType>(Val: Target->getType().getTypePtr())) {
70 switch (TargetPT->getRefQualifier()) {
71 case RQ_LValue:
72 Signature.append(s: " &");
73 break;
74 case RQ_RValue:
75 Signature.append(s: " &&");
76 break;
77 default:
78 break;
79 }
80 }
81
82 return Signature;
83}
84
85static std::string GetEnclosingDeclContextSignature(const Decl *EnclosingDecl) {
86 if (const auto *ND = dyn_cast_or_null<NamedDecl>(Val: EnclosingDecl)) {
87 if (const auto *FD = dyn_cast<FunctionDecl>(Val: ND))
88 // To distinguish overloads we need to use the signature.
89 return GetSignature(Target: FD);
90 return ND->getQualifiedNameAsString();
91 }
92 return "";
93}
94
95static StringRef GetNthLineOfFile(std::optional<llvm::MemoryBufferRef> Buffer,
96 int Line) {
97 if (!Buffer)
98 return "";
99
100 llvm::line_iterator LI(*Buffer, false);
101 for (; !LI.is_at_eof() && LI.line_number() != Line; ++LI)
102 ;
103
104 return *LI;
105}
106
107static std::string NormalizeLine(const SourceManager &SM, const FullSourceLoc &L,
108 const LangOptions &LangOpts) {
109 static StringRef Whitespaces = " \t\n";
110
111 StringRef Str = GetNthLineOfFile(Buffer: SM.getBufferOrNone(FID: L.getFileID(), Loc: L),
112 Line: L.getExpansionLineNumber());
113 StringRef::size_type col = Str.find_first_not_of(Chars: Whitespaces);
114 if (col == StringRef::npos)
115 col = 1; // The line only contains whitespace.
116 else
117 col++;
118 SourceLocation StartOfLine =
119 SM.translateLineCol(FID: SM.getFileID(SpellingLoc: L), Line: L.getExpansionLineNumber(), Col: col);
120 std::optional<llvm::MemoryBufferRef> Buffer =
121 SM.getBufferOrNone(FID: SM.getFileID(SpellingLoc: StartOfLine), Loc: StartOfLine);
122 if (!Buffer)
123 return {};
124
125 const char *BufferPos = SM.getCharacterData(SL: StartOfLine);
126
127 Token Token;
128 Lexer Lexer(SM.getLocForStartOfFile(FID: SM.getFileID(SpellingLoc: StartOfLine)), LangOpts,
129 Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd());
130
131 size_t NextStart = 0;
132 std::ostringstream LineBuff;
133 while (!Lexer.LexFromRawLexer(Result&: Token) && NextStart < 2) {
134 if (Token.isAtStartOfLine() && NextStart++ > 0)
135 continue;
136 LineBuff << std::string(SM.getCharacterData(SL: Token.getLocation()),
137 Token.getLength());
138 }
139
140 return LineBuff.str();
141}
142
143static llvm::SmallString<32> GetMD5HashOfContent(StringRef Content) {
144 llvm::MD5 Hash;
145 llvm::MD5::MD5Result MD5Res;
146 SmallString<32> Res;
147
148 Hash.update(Str: Content);
149 Hash.final(Result&: MD5Res);
150 llvm::MD5::stringifyResult(Result&: MD5Res, Str&: Res);
151
152 return Res;
153}
154
155std::string clang::getIssueString(const FullSourceLoc &IssueLoc,
156 StringRef CheckerName,
157 StringRef WarningMessage,
158 const Decl *IssueDecl,
159 const LangOptions &LangOpts) {
160 static StringRef Delimiter = "$";
161
162 return (llvm::Twine(CheckerName) + Delimiter +
163 GetEnclosingDeclContextSignature(EnclosingDecl: IssueDecl) + Delimiter +
164 Twine(IssueLoc.getExpansionColumnNumber()) + Delimiter +
165 NormalizeLine(SM: IssueLoc.getManager(), L: IssueLoc, LangOpts) +
166 Delimiter + WarningMessage)
167 .str();
168}
169
170SmallString<32> clang::getIssueHash(const FullSourceLoc &IssueLoc,
171 StringRef CheckerName,
172 StringRef WarningMessage,
173 const Decl *IssueDecl,
174 const LangOptions &LangOpts) {
175
176 return GetMD5HashOfContent(Content: getIssueString(
177 IssueLoc, CheckerName, WarningMessage, IssueDecl, LangOpts));
178}
179