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/Basic/Specifiers.h"
15#include "clang/Lex/Lexer.h"
16#include "llvm/ADT/StringExtras.h"
17#include "llvm/ADT/StringRef.h"
18#include "llvm/ADT/Twine.h"
19#include "llvm/Support/LineIterator.h"
20#include "llvm/Support/MD5.h"
21#include "llvm/Support/Path.h"
22
23#include <functional>
24#include <sstream>
25#include <string>
26
27using namespace clang;
28
29// Get a string representation of the parts of the signature that can be
30// overloaded on.
31static std::string GetSignature(const FunctionDecl *Target) {
32  if (!Target)
33    return "";
34  std::string Signature;
35
36  // When a flow sensitive bug happens in templated code we should not generate
37  // distinct hash value for every instantiation. Use the signature from the
38  // primary template.
39  if (const FunctionDecl *InstantiatedFrom =
40          Target->getTemplateInstantiationPattern())
41    Target = InstantiatedFrom;
42
43  if (!isa<CXXConstructorDecl>(Target) && !isa<CXXDestructorDecl>(Target) &&
44      !isa<CXXConversionDecl>(Target))
45    Signature.append(Target->getReturnType().getAsString()).append(" ");
46  Signature.append(Target->getQualifiedNameAsString()).append("(");
47
48  for (int i = 0, paramsCount = Target->getNumParams(); i < paramsCount; ++i) {
49    if (i)
50      Signature.append(", ");
51    Signature.append(Target->getParamDecl(i)->getType().getAsString());
52  }
53
54  if (Target->isVariadic())
55    Signature.append(", ...");
56  Signature.append(")");
57
58  const auto *TargetT =
59      llvm::dyn_cast_or_null<FunctionType>(Target->getType().getTypePtr());
60
61  if (!TargetT || !isa<CXXMethodDecl>(Target))
62    return Signature;
63
64  if (TargetT->isConst())
65    Signature.append(" const");
66  if (TargetT->isVolatile())
67    Signature.append(" volatile");
68  if (TargetT->isRestrict())
69    Signature.append(" restrict");
70
71  if (const auto *TargetPT =
72          dyn_cast_or_null<FunctionProtoType>(Target->getType().getTypePtr())) {
73    switch (TargetPT->getRefQualifier()) {
74    case RQ_LValue:
75      Signature.append(" &");
76      break;
77    case RQ_RValue:
78      Signature.append(" &&");
79      break;
80    default:
81      break;
82    }
83  }
84
85  return Signature;
86}
87
88static std::string GetEnclosingDeclContextSignature(const Decl *D) {
89  if (!D)
90    return "";
91
92  if (const auto *ND = dyn_cast<NamedDecl>(D)) {
93    std::string DeclName;
94
95    switch (ND->getKind()) {
96    case Decl::Namespace:
97    case Decl::Record:
98    case Decl::CXXRecord:
99    case Decl::Enum:
100      DeclName = ND->getQualifiedNameAsString();
101      break;
102    case Decl::CXXConstructor:
103    case Decl::CXXDestructor:
104    case Decl::CXXConversion:
105    case Decl::CXXMethod:
106    case Decl::Function:
107      DeclName = GetSignature(dyn_cast_or_null<FunctionDecl>(ND));
108      break;
109    case Decl::ObjCMethod:
110      // ObjC Methods can not be overloaded, qualified name uniquely identifies
111      // the method.
112      DeclName = ND->getQualifiedNameAsString();
113      break;
114    default:
115      break;
116    }
117
118    return DeclName;
119  }
120
121  return "";
122}
123
124static StringRef GetNthLineOfFile(llvm::Optional<llvm::MemoryBufferRef> Buffer,
125                                  int Line) {
126  if (!Buffer)
127    return "";
128
129  llvm::line_iterator LI(*Buffer, false);
130  for (; !LI.is_at_eof() && LI.line_number() != Line; ++LI)
131    ;
132
133  return *LI;
134}
135
136static std::string NormalizeLine(const SourceManager &SM, const FullSourceLoc &L,
137                                 const LangOptions &LangOpts) {
138  static StringRef Whitespaces = " \t\n";
139
140  StringRef Str = GetNthLineOfFile(SM.getBufferOrNone(L.getFileID(), L),
141                                   L.getExpansionLineNumber());
142  StringRef::size_type col = Str.find_first_not_of(Whitespaces);
143  if (col == StringRef::npos)
144    col = 1; // The line only contains whitespace.
145  else
146    col++;
147  SourceLocation StartOfLine =
148      SM.translateLineCol(SM.getFileID(L), L.getExpansionLineNumber(), col);
149  Optional<llvm::MemoryBufferRef> Buffer =
150      SM.getBufferOrNone(SM.getFileID(StartOfLine), StartOfLine);
151  if (!Buffer)
152    return {};
153
154  const char *BufferPos = SM.getCharacterData(StartOfLine);
155
156  Token Token;
157  Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(StartOfLine)), LangOpts,
158              Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd());
159
160  size_t NextStart = 0;
161  std::ostringstream LineBuff;
162  while (!Lexer.LexFromRawLexer(Token) && NextStart < 2) {
163    if (Token.isAtStartOfLine() && NextStart++ > 0)
164      continue;
165    LineBuff << std::string(SM.getCharacterData(Token.getLocation()),
166                            Token.getLength());
167  }
168
169  return LineBuff.str();
170}
171
172static llvm::SmallString<32> GetMD5HashOfContent(StringRef Content) {
173  llvm::MD5 Hash;
174  llvm::MD5::MD5Result MD5Res;
175  SmallString<32> Res;
176
177  Hash.update(Content);
178  Hash.final(MD5Res);
179  llvm::MD5::stringifyResult(MD5Res, Res);
180
181  return Res;
182}
183
184std::string clang::getIssueString(const FullSourceLoc &IssueLoc,
185                                  StringRef CheckerName,
186                                  StringRef WarningMessage,
187                                  const Decl *IssueDecl,
188                                  const LangOptions &LangOpts) {
189  static StringRef Delimiter = "$";
190
191  return (llvm::Twine(CheckerName) + Delimiter +
192          GetEnclosingDeclContextSignature(IssueDecl) + Delimiter +
193          Twine(IssueLoc.getExpansionColumnNumber()) + Delimiter +
194          NormalizeLine(IssueLoc.getManager(), IssueLoc, LangOpts) +
195          Delimiter + WarningMessage)
196      .str();
197}
198
199SmallString<32> clang::getIssueHash(const FullSourceLoc &IssueLoc,
200                                    StringRef CheckerName,
201                                    StringRef WarningMessage,
202                                    const Decl *IssueDecl,
203                                    const LangOptions &LangOpts) {
204
205  return GetMD5HashOfContent(getIssueString(
206      IssueLoc, CheckerName, WarningMessage, IssueDecl, LangOpts));
207}
208