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