summaryrefslogtreecommitdiffstats
path: root/iwyu_location_util.h
blob: 6f8cf81cf0b3142482214bbb98d78e9dc9ef365e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
//===--- iwyu_location_util.h - SourceLoc-related utilities for iwyu ------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// clang-File utilities for the IWYU checker.
//
// This file deals with clang's FileEntry and SourceLocation classes,
// and useful utilities on them.  It does not deal with file paths
// per se; for that, see iwyu_path_util.h
//
// In addition to some ad hoc helper routines, in general, for every
// type we consider, we try to provide 3 routines (as appropriate):
//    GetLocation():  convert the object to an appropriate SourceLocation
//    GetFileEntry(): convert the object to the file it's in
//    GetFilePath():  convert the object to the filename it's in
//
//
// Background on Clang data structures:
//
// Clang uses the type FileEntry to identify a physical file in the
// file system.  A FileEntry is created for each source file Clang
// processes.  Clang never creates two FileEntry objects for the same
// file.  Therefore we use const FileEntry* in IWYU as unique IDs for
// files.
//
// Clang's FileID type is a misnomer.  It's actually an ID of a
// particular #include statement.  If a file is #included in two
// places, it will have two different FildIDs.
//
// A SourceLocation is a compact encoding of a location in a
// particular #include of a source file.  From it you can find the
// file path, line number, column number, and which file (if any)
// #includes the file.

#ifndef INCLUDE_WHAT_YOU_USE_IWYU_LOCATION_UTIL_H_
#define INCLUDE_WHAT_YOU_USE_IWYU_LOCATION_UTIL_H_

#include <string>                       // for string

#include "iwyu_globals.h"
#include "iwyu_path_util.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Token.h"

namespace clang {
class Decl;
class NestedNameSpecifierLoc;
class Stmt;
class TemplateArgumentLoc;
class TypeLoc;
}  // namespace clang

namespace include_what_you_use {

using std::string;

//------------------------------------------------------------
// Helper functions for FileEntry.

// Some symbols are directly defined by the compiler.  For them, the
// definition location points to the "<built-in>" file.
inline bool IsBuiltinFile(const clang::FileEntry* file) {
  return file == nullptr;
}

// There are two kinds of symbols that are not defined in the source
// files: the compiler can define some standard symbols
// (e.g. __FILE__), and the user can define macros on the command line
// of the compiler using -D.  A symbol appears to be defined in file
// "<built-in>" in the first case, and "<command line>" in the second.
// IsBuiltinOrCommandLineFile(file) returns true if it's either of the
// two cases.
inline bool IsBuiltinOrCommandLineFile(const clang::FileEntry* file) {
  return IsBuiltinFile(file) || file->getName().equals("<command line>");
}

// When macro args are concatenated e.g. '#define CAT(A, B) A##B', their
// location ends up outside the source text, in what the compiler calls
// "<scratch space>".
// This function returns true if the provided loc is in scratch space.
bool IsInScratchSpace(clang::SourceLocation loc);

inline string GetFilePath(const clang::FileEntry* file) {
  return (IsBuiltinFile(file) ? "<built-in>" :
          NormalizeFilePath(file->getName().str()));
}

//------------------------------------------------------------
// Helper functions for SourceLocation

inline clang::SourceLocation GetSpellingLoc(clang::SourceLocation loc) {
  return loc.isValid() ? GlobalSourceManager()->getSpellingLoc(loc) : loc;
}

inline clang::SourceLocation GetInstantiationLoc(clang::SourceLocation loc) {
  return loc.isValid() ? GlobalSourceManager()->getExpansionLoc(loc) : loc;
}

inline bool IsInMacro(clang::SourceLocation loc) {
  return GetSpellingLoc(loc) != GetInstantiationLoc(loc);
}

// Returns the line number corresponding to a source location.
// Prefers the spelling line number, but if it's not useful (because
// the location is in <scratch space> for instance, due to macro token
// concatenation), uses the instantiation line number.  Returns -1 if
// we can't figure out the line #.
inline int GetLineNumber(clang::SourceLocation loc) {
  if (!loc.isValid())
    return -1;
  const clang::FullSourceLoc fullloc(loc, *GlobalSourceManager());
  bool invalid = false;
  int retval = fullloc.getSpellingLineNumber(&invalid);
  if (invalid)
    retval = fullloc.getExpansionLineNumber(&invalid);
  if (invalid)
    retval = -1;
  return retval;
}

// The rest of this section of the file is for returning the
// FileEntry* corresponding to a source location: the file that the
// location is in.  This is a surprising amount of work.

// Tells which #include loc comes from.
// This is the most basic FileEntry getter, it only does a simple lookup in
// SourceManager to determine which file the location is associated with.
inline const clang::FileEntry* GetLocFileEntry(clang::SourceLocation loc) {
  // clang uses the name FileID to mean 'a filename that was reached via
  // a particular series of #includes.'  (What one might think a FileID
  // might be -- a unique reference to a filesystem object -- is
  // actually a FileEntry*.)
  const clang::SourceManager& source_manager = *GlobalSourceManager();
  return source_manager.getFileEntryForID(source_manager.getFileID(loc));
}

inline const clang::FileEntry* GetFileEntry(clang::SourceLocation loc) {
  if (!loc.isValid())
    return nullptr;

  // We want where the user actually writes the token, instead of
  // where it appears as part of a macro expansion.  For example, in:
  //
  //  file foo.h,  line 5:  #define FOO(x) x + y
  //  file bar.cc, line 10: FOO(z)
  //
  // FOO(z) will expand to 'z + y', where symbol z's location is
  // foo.h, line 5, and its spelling location is bar.cc, line 10.
  const clang::FileEntry* retval = GetLocFileEntry(GetSpellingLoc(loc));

  // Sometimes the spelling location is NULL, because the symbol is
  // 'spelled' via macro concatenation.  For instance, all the
  // __gthrw3 symbols in
  // /usr/include/c++/4.2/x86_64-linux-gnu/bits/gthr-default.h.
  // In that case, fall back on the instantiation location.
  if (!retval) {
    retval = GetLocFileEntry(GetInstantiationLoc(loc));
  }
  return retval;
}

//------------------------------------------------------------
// GetFileEntry(), GetFilePath(), and GetLocation().

inline clang::SourceLocation GetLocation(const clang::Token& token) {
  return token.getLocation();
}

inline clang::SourceLocation GetLocation(clang::SourceLocation loc) {
  return loc;   // the identity location-getter, useful with templates
}
clang::SourceLocation GetLocation(const clang::Decl* decl);
clang::SourceLocation GetLocation(const clang::Stmt* stmt);
clang::SourceLocation GetLocation(const clang::TypeLoc* typeloc);
clang::SourceLocation GetLocation(const clang::NestedNameSpecifierLoc* nnsloc);
clang::SourceLocation GetLocation(const clang::TemplateArgumentLoc* argloc);

// These define default implementations of GetFileEntry() and
// GetPath() in terms of GetLocation().  As long as an object defines
// its own GetLocation(), it will get these other two for free.
template<typename T> const clang::FileEntry* GetFileEntry(const T& obj) {
  return GetFileEntry(GetLocation(obj));
}
template<typename T> const string GetFilePath(const T& obj) {
  return GetFilePath(GetFileEntry(obj));
}

//------------------------------------------------------------
// Some utility, location-based routines.

// Given any two objects that have instantiation-locations, says
// whether one occurs before the other in the translation unit (using
// instantiated locations).  This means that one would occur before
// the other looking at the output of cc -E or equivalent.
template<typename T, typename U>
inline bool IsBeforeInTranslationUnit(const T& a, const U& b) {
  const clang::FullSourceLoc a_loc(GetLocation(a), *GlobalSourceManager());
  const clang::FullSourceLoc b_loc(GetLocation(b), *GlobalSourceManager());
  // Inside a macro, everything has the same instantiation location.
  // We'd like to use spelling-location to break that tie, but it's
  // unreliable since a or b might be spelled in "<scratch space>".
  // So we're just conservative and return true always if the two have
  // an equal location and are in a macro.  (Because we check the
  // instantiation-location is equal, it's enough that one of the two
  // be in a macro; we prefer that since IsInMacro fails if T or U is
  // the wrong type.)  TODO(csilvers): see if's possible to get
  // isBeforeInTranslationUnitThan working properly.  This may require
  // storing source-locations better in OneUse.
  if ((IsInMacro(a_loc) || IsInMacro(b_loc)) &&
      GetInstantiationLoc(a_loc) == GetInstantiationLoc(b_loc))
    return true;
  return a_loc.isBeforeInTranslationUnitThan(b_loc);
}

// Like IsBeforeInTranslationUnit, but both a and b must be
// instantiated in the same file as well.
template<typename T, typename U>
inline bool IsBeforeInSameFile(const T& a, const U& b) {
  if (GetFileEntry(a) != GetFileEntry(b))
    return false;
  return IsBeforeInTranslationUnit(a, b);
}

}  // namespace include_what_you_use

#endif  // INCLUDE_WHAT_YOU_USE_IWYU_LOCATION_UTIL_H_