1/****************************************************************************
2 * Copyright (C) 2012-2016 Woboq GmbH
3 * Olivier Goffart <contact at woboq.com>
4 * https://woboq.com/codebrowser.html
5 *
6 * This file is part of the Woboq Code Browser.
7 *
8 * Commercial License Usage:
9 * Licensees holding valid commercial licenses provided by Woboq may use
10 * this file in accordance with the terms contained in a written agreement
11 * between the licensee and Woboq.
12 * For further information see https://woboq.com/codebrowser.html
13 *
14 * Alternatively, this work may be used under a Creative Commons
15 * Attribution-NonCommercial-ShareAlike 3.0 (CC-BY-NC-SA 3.0) License.
16 * http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US
17 * This license does not allow you to use the code browser to assist the
18 * development of your commercial software. If you intent to do so, consider
19 * purchasing a commercial licence.
20 ****************************************************************************/
21
22#include "preprocessorcallback.h"
23#include "annotator.h"
24#include <clang/Lex/Token.h>
25#include <clang/Lex/MacroInfo.h>
26#include <clang/Lex/Preprocessor.h>
27#include <clang/Basic/Version.h>
28#include <llvm/ADT/Twine.h>
29#include "stringbuilder.h"
30#include "projectmanager.h"
31
32
33void PreprocessorCallback::MacroExpands(const clang::Token& MacroNameTok,
34 MyMacroDefinition MD,
35 clang::SourceRange Range, const clang::MacroArgs *)
36{
37 if (disabled)
38 return;
39
40#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 7
41 auto *MI = MD.getMacroInfo();
42#else
43 auto *MI = MD->getMacroInfo();
44#endif
45 clang::SourceLocation loc = MacroNameTok.getLocation();
46 if (!loc.isValid() || !loc.isFileID())
47 return;
48 clang::SourceManager &sm = annotator.getSourceMgr();
49 clang::FileID FID = sm.getFileID(loc);
50 if (!annotator.shouldProcess(FID))
51 return;
52
53 const char *begin = sm.getCharacterData(Range.getBegin());
54 int len = sm.getCharacterData(Range.getEnd()) - begin;
55 len += clang::Lexer::MeasureTokenLength(Range.getEnd(), sm, PP.getLangOpts());
56
57 std::string copy(begin, len);
58 begin = copy.c_str();
59 clang::Lexer lex(loc, PP.getLangOpts(), begin, begin, begin + len);
60 std::vector<clang::Token> tokens;
61 std::string expansion;
62
63 //Lousely based on code from clang::html::HighlightMacros
64
65 // Lex all the tokens in raw mode, to avoid entering #includes or expanding
66 // macros.
67 clang::Token tok;
68 do {
69 lex.LexFromRawLexer(tok);
70
71 // If this is a # at the start of a line, discard it from the token stream.
72 // We don't want the re-preprocess step to see #defines, #includes or other
73 // preprocessor directives.
74 if (tok.is(clang::tok::hash) && tok.isAtStartOfLine())
75 continue;
76
77 // If this is a ## token, change its kind to unknown so that repreprocessing
78 // it will not produce an error.
79 if (tok.is(clang::tok::hashhash))
80 tok.setKind(clang::tok::unknown);
81
82 // If this raw token is an identifier, the raw lexer won't have looked up
83 // the corresponding identifier info for it. Do this now so that it will be
84 // macro expanded when we re-preprocess it.
85 if (tok.is(clang::tok::raw_identifier))
86 PP.LookUpIdentifierInfo(tok);
87
88 tokens.push_back(tok);
89
90 } while(!tok.is(clang::tok::eof));
91
92 // Temporarily change the diagnostics object so that we ignore any generated
93 // diagnostics from this pass.
94 clang::DiagnosticsEngine TmpDiags(PP.getDiagnostics().getDiagnosticIDs(),
95 &PP.getDiagnostics().getDiagnosticOptions(),
96 new clang::IgnoringDiagConsumer);
97
98 disabled = true;
99 clang::DiagnosticsEngine *OldDiags = &PP.getDiagnostics();
100 PP.setDiagnostics(TmpDiags);
101
102 // We don't want pragmas either. Although we filtered out #pragma, removing
103 // _Pragma and __pragma is much harder.
104 bool pragmasPreviouslyEnabled = PP.getPragmasEnabled();
105 PP.setPragmasEnabled(false);
106 seenPragma = false;
107
108#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 9
109 PP.EnterTokenStream(tokens, /*DisableMacroExpansion=*/false);
110#else
111 PP.EnterTokenStream(tokens.data(), tokens.size(), false, false);
112#endif
113
114
115 PP.Lex(tok);
116 while(tok.isNot(clang::tok::eof)) {
117 if (seenPragma) {
118 // skip pragma
119 while(tok.isNot(clang::tok::eof) && tok.isNot(clang::tok::eod))
120 PP.Lex(tok);
121 seenPragma = false;
122 PP.Lex(tok);
123 continue;
124 }
125
126 // If the tokens were already space separated, or if they must be to avoid
127 // them being implicitly pasted, add a space between them.
128 if (tok.hasLeadingSpace())
129 expansion += ' ';
130 // ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok, Tok)) //FIXME
131 // Escape any special characters in the token text.
132 expansion += PP.getSpelling(tok);
133 PP.Lex(tok);
134 }
135
136 PP.setDiagnostics(*OldDiags);
137 PP.setPragmasEnabled(pragmasPreviouslyEnabled);
138 disabled = false;
139
140 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
141
142 clang::SourceLocation defLoc = MI->getDefinitionLoc();
143 clang::FileID defFID = sm.getFileID(defLoc);
144 llvm::SmallString<128> expansionBuffer;
145 std::string link;
146 std::string dataProj;
147 if (defFID != FID) {
148 link = annotator.pathTo(FID, defFID, &dataProj);
149 if (link.empty()) {
150 std::string tag = "class=\"macro\" title=\"" % Generator::escapeAttr(expansion, expansionBuffer)
151 % "\" data-ref=\"" % ref % "\"";
152 annotator.generator(FID).addTag("span", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
153 return;
154 }
155
156 if (!dataProj.empty()) {
157 dataProj = " data-proj=\"" % dataProj % "\"";
158 }
159 }
160
161 if (sm.getMainFileID() != defFID) {
162 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Use_Call);
163 }
164
165 std::string tag = "class=\"macro\" href=\"" % link % "#" % llvm::Twine(sm.getExpansionLineNumber(defLoc)).str()
166 % "\" title=\"" % Generator::escapeAttr(expansion, expansionBuffer)
167 % "\" data-ref=\"" % ref % "\"" % dataProj;
168 annotator.generator(FID).addTag("a", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
169}
170
171void PreprocessorCallback::MacroDefined(const clang::Token& MacroNameTok, const clang::MacroDirective *MD)
172{
173 clang::SourceLocation loc = MacroNameTok.getLocation();
174 if (!loc.isValid() || !loc.isFileID())
175 return;
176
177 clang::SourceManager &sm = annotator.getSourceMgr();
178 clang::FileID FID = sm.getFileID(loc);
179 if (!annotator.shouldProcess(FID))
180 return;
181
182 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
183
184 if (sm.getMainFileID() != FID) {
185 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Declaration);
186 }
187
188 annotator.generator(FID).addTag("dfn", "class=\"macro\" id=\""% ref %"\" data-ref=\"" % ref % "\"", sm.getFileOffset(loc), MacroNameTok.getLength());
189}
190
191void PreprocessorCallback::MacroUndefined(const clang::Token& MacroNameTok, PreprocessorCallback::MyMacroDefinition MD
192#if CLANG_VERSION_MAJOR >= 5
193 , const clang::MacroDirective *
194#endif
195 )
196{
197 clang::SourceLocation loc = MacroNameTok.getLocation();
198 if (!loc.isValid() || !loc.isFileID())
199 return;
200
201 clang::SourceManager &sm = annotator.getSourceMgr();
202 clang::FileID FID = sm.getFileID(loc);
203 if (!annotator.shouldProcess(FID))
204 return;
205
206 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
207 std::string link;
208 std::string dataProj;
209 clang::SourceLocation defLoc;
210 clang::FileID defFID;
211
212 if (MD) {
213#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 7
214 auto *MI = MD.getMacroInfo();
215#else
216 auto *MI = MD->getMacroInfo();
217#endif
218 if (MI) {
219 defLoc = MI->getDefinitionLoc();
220 defFID = sm.getFileID(defLoc);
221 }
222 }
223
224 if (defFID.isInvalid() || defFID != FID) {
225 if (!defFID.isInvalid()) {
226 link = annotator.pathTo(FID, defFID, &dataProj);
227 }
228 if (link.empty()) {
229 std::string tag = "class=\"macro\" data-ref=\"" % ref % "\"";
230 annotator.generator(FID).addTag("span", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
231 return;
232 }
233
234 if (!dataProj.empty()) {
235 dataProj = " data-proj=\"" % dataProj % "\"";
236 }
237 }
238
239 if (sm.getMainFileID() != defFID) {
240 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Use_Write);
241 }
242
243 std::string tag = "class=\"macro\" href=\"" % link % "#" % llvm::Twine(sm.getExpansionLineNumber(defLoc)).str()
244 % "\" data-ref=\"" % ref % "\"" % dataProj;
245 annotator.generator(FID).addTag("a", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
246}
247
248bool PreprocessorCallback::FileNotFound(llvm::StringRef FileName, llvm::SmallVectorImpl<char> &RecoveryPath)
249{
250 if (!recoverIncludePath)
251 return false;
252 clang::SourceLocation currentLoc = static_cast<clang::Lexer *>(PP.getCurrentLexer())->getSourceLocation();
253 auto &SM = annotator.getSourceMgr();
254 const clang::FileEntry* entry = SM.getFileEntryForID(SM.getFileID(currentLoc));
255 if (!entry || llvm::StringRef(entry->getName()).empty())
256 return false;
257 std::string recovery = annotator.projectManager.includeRecovery(FileName, entry->getName());
258 if (recovery.empty() || !llvm::StringRef(recovery).endswith(FileName))
259 return false;
260 RecoveryPath.clear();
261 RecoveryPath.append(recovery.begin(), recovery.begin() + recovery.size() - FileName.size());
262 currentLoc.dump(SM);
263 llvm::errs() << " WARNING: File not found '" << FileName << "'. Recovering using "
264 << llvm::StringRef(RecoveryPath.data(), RecoveryPath.size()) << "\n";
265
266 return true;
267}
268
269
270void PreprocessorCallback::InclusionDirective(clang::SourceLocation HashLoc, const clang::Token& IncludeTok,
271 llvm::StringRef FileName, bool IsAngled,
272 clang::CharSourceRange FilenameRange, const clang::FileEntry* File,
273 llvm::StringRef SearchPath, llvm::StringRef RelativePath,
274 const clang::Module* Imported)
275{
276 if (!HashLoc.isValid() || !HashLoc.isFileID() || !File)
277 return;
278 clang::SourceManager &sm = annotator.getSourceMgr();
279 clang::FileID FID = sm.getFileID(HashLoc);
280 if (!annotator.shouldProcess(FID))
281 return;
282
283 std::string link = annotator.pathTo(FID, File);
284 if (link.empty())
285 return;
286
287 auto B = sm.getFileOffset(FilenameRange.getBegin());
288 auto E = sm.getFileOffset(FilenameRange.getEnd());
289
290 annotator.generator(FID).addTag("a", "href=\"" % link % "\"", B, E-B);
291}
292
293void PreprocessorCallback::Defined(const clang::Token& MacroNameTok, MyMacroDefinition MD,
294 clang::SourceRange Range)
295{
296 clang::SourceLocation loc = MacroNameTok.getLocation();
297 if (!loc.isValid() || !loc.isFileID())
298 return;
299
300 clang::SourceManager &sm = annotator.getSourceMgr();
301
302 clang::FileID FID = sm.getFileID(loc);
303 if (!annotator.shouldProcess(FID))
304 return;
305
306 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
307 std::string link;
308 std::string dataProj;
309 clang::SourceLocation defLoc;
310 clang::FileID defFID;
311
312 if (MD) {
313#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 7
314 auto *MI = MD.getMacroInfo();
315#else
316 auto *MI = MD->getMacroInfo();
317#endif
318 if (MI) {
319 defLoc = MI->getDefinitionLoc();
320 defFID = sm.getFileID(defLoc);
321 }
322 }
323
324 if (defFID.isInvalid() || defFID != FID) {
325 if (!defFID.isInvalid()) {
326 link = annotator.pathTo(FID, defFID, &dataProj);
327 }
328 if (link.empty()) {
329 std::string tag = "class=\"macro\" data-ref=\"" % ref % "\"";
330 annotator.generator(FID).addTag("span", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
331 return;
332 }
333
334 if (!dataProj.empty()) {
335 dataProj = " data-proj=\"" % dataProj % "\"";
336 }
337 }
338
339 if (sm.getMainFileID() != defFID) {
340 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Use_Address);
341 }
342
343 std::string tag = "class=\"macro\" href=\"" % link % "#" % llvm::Twine(sm.getExpansionLineNumber(defLoc)).str()
344 % "\" data-ref=\"" % ref % "\"" % dataProj;
345 annotator.generator(FID).addTag("a", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
346}
347
348void PreprocessorCallback::HandlePPCond(clang::SourceLocation Loc, clang::SourceLocation IfLoc)
349{
350 if (!Loc.isValid() || !Loc.isFileID())
351 return;
352
353 clang::SourceManager &SM = annotator.getSourceMgr();
354 clang::FileID FID = SM.getFileID(Loc);
355 if (!annotator.shouldProcess(FID))
356 return;
357
358 while(ElifMapping.count(IfLoc)) {
359 IfLoc = Loc;
360 }
361
362 if (SM.getFileID(IfLoc) != FID) {
363 return;
364 }
365
366 annotator.generator(FID).addTag("span", ("data-ppcond=\"" + clang::Twine(SM.getExpansionLineNumber(IfLoc)) + "\"").str(),
367 SM.getFileOffset(Loc), clang::Lexer::MeasureTokenLength(Loc, SM, PP.getLangOpts()));
368}
369