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
134 if (expansion.size() >= 30 * 1000) {
135 // Don't let the macro expansion grow too large.
136 expansion += "...";
137 while(tok.isNot(clang::tok::eof))
138 PP.LexUnexpandedToken(tok);
139 break;
140 }
141
142 PP.Lex(tok);
143 }
144
145 PP.setDiagnostics(*OldDiags);
146 PP.setPragmasEnabled(pragmasPreviouslyEnabled);
147 disabled = false;
148
149 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
150
151 clang::SourceLocation defLoc = MI->getDefinitionLoc();
152 clang::FileID defFID = sm.getFileID(defLoc);
153 llvm::SmallString<128> expansionBuffer;
154 std::string link;
155 std::string dataProj;
156 if (defFID != FID) {
157 link = annotator.pathTo(FID, defFID, &dataProj);
158 if (link.empty()) {
159 std::string tag = "class=\"macro\" title=\"" % Generator::escapeAttr(expansion, expansionBuffer)
160 % "\" data-ref=\"" % ref % "\"";
161 annotator.generator(FID).addTag("span", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
162 return;
163 }
164
165 if (!dataProj.empty()) {
166 dataProj = " data-proj=\"" % dataProj % "\"";
167 }
168 }
169
170 if (sm.getMainFileID() != defFID) {
171 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Use_Call);
172 }
173
174 std::string tag = "class=\"macro\" href=\"" % link % "#" % llvm::Twine(sm.getExpansionLineNumber(defLoc)).str()
175 % "\" title=\"" % Generator::escapeAttr(expansion, expansionBuffer)
176 % "\" data-ref=\"" % ref % "\"" % dataProj;
177 annotator.generator(FID).addTag("a", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
178}
179
180void PreprocessorCallback::MacroDefined(const clang::Token& MacroNameTok, const clang::MacroDirective *MD)
181{
182 clang::SourceLocation loc = MacroNameTok.getLocation();
183 if (!loc.isValid() || !loc.isFileID())
184 return;
185
186 clang::SourceManager &sm = annotator.getSourceMgr();
187 clang::FileID FID = sm.getFileID(loc);
188 if (!annotator.shouldProcess(FID))
189 return;
190
191 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
192
193 if (sm.getMainFileID() != FID) {
194 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Declaration);
195 }
196
197 annotator.generator(FID).addTag("dfn", "class=\"macro\" id=\""% ref %"\" data-ref=\"" % ref % "\"", sm.getFileOffset(loc), MacroNameTok.getLength());
198}
199
200void PreprocessorCallback::MacroUndefined(const clang::Token& MacroNameTok, PreprocessorCallback::MyMacroDefinition MD
201#if CLANG_VERSION_MAJOR >= 5
202 , const clang::MacroDirective *
203#endif
204 )
205{
206 clang::SourceLocation loc = MacroNameTok.getLocation();
207 if (!loc.isValid() || !loc.isFileID())
208 return;
209
210 clang::SourceManager &sm = annotator.getSourceMgr();
211 clang::FileID FID = sm.getFileID(loc);
212 if (!annotator.shouldProcess(FID))
213 return;
214
215 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
216 std::string link;
217 std::string dataProj;
218 clang::SourceLocation defLoc;
219 clang::FileID defFID;
220
221 if (MD) {
222#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 7
223 auto *MI = MD.getMacroInfo();
224#else
225 auto *MI = MD->getMacroInfo();
226#endif
227 if (MI) {
228 defLoc = MI->getDefinitionLoc();
229 defFID = sm.getFileID(defLoc);
230 }
231 }
232
233 if (defFID.isInvalid() || defFID != FID) {
234 if (!defFID.isInvalid()) {
235 link = annotator.pathTo(FID, defFID, &dataProj);
236 }
237 if (link.empty()) {
238 std::string tag = "class=\"macro\" data-ref=\"" % ref % "\"";
239 annotator.generator(FID).addTag("span", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
240 return;
241 }
242
243 if (!dataProj.empty()) {
244 dataProj = " data-proj=\"" % dataProj % "\"";
245 }
246 }
247
248 if (sm.getMainFileID() != defFID) {
249 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Use_Write);
250 }
251
252 std::string tag = "class=\"macro\" href=\"" % link % "#" % llvm::Twine(sm.getExpansionLineNumber(defLoc)).str()
253 % "\" data-ref=\"" % ref % "\"" % dataProj;
254 annotator.generator(FID).addTag("a", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
255}
256
257bool PreprocessorCallback::FileNotFound(llvm::StringRef FileName, llvm::SmallVectorImpl<char> &RecoveryPath)
258{
259 if (!recoverIncludePath)
260 return false;
261 clang::SourceLocation currentLoc = static_cast<clang::Lexer *>(PP.getCurrentLexer())->getSourceLocation();
262 auto &SM = annotator.getSourceMgr();
263 const clang::FileEntry* entry = SM.getFileEntryForID(SM.getFileID(currentLoc));
264 if (!entry || llvm::StringRef(entry->getName()).empty())
265 return false;
266 std::string recovery = annotator.projectManager.includeRecovery(FileName, entry->getName());
267 if (recovery.empty() || !llvm::StringRef(recovery).endswith(FileName))
268 return false;
269 RecoveryPath.clear();
270 RecoveryPath.append(recovery.begin(), recovery.begin() + recovery.size() - FileName.size());
271 currentLoc.dump(SM);
272 llvm::errs() << " WARNING: File not found '" << FileName << "'. Recovering using "
273 << llvm::StringRef(RecoveryPath.data(), RecoveryPath.size()) << "\n";
274
275 return true;
276}
277
278
279void PreprocessorCallback::InclusionDirective(clang::SourceLocation HashLoc, const clang::Token& IncludeTok,
280 llvm::StringRef FileName, bool IsAngled,
281 clang::CharSourceRange FilenameRange, const clang::FileEntry* File,
282 llvm::StringRef SearchPath, llvm::StringRef RelativePath,
283 const clang::Module* Imported
284#if CLANG_VERSION_MAJOR >= 7
285 , clang::SrcMgr::CharacteristicKind
286#endif
287 )
288{
289 if (!HashLoc.isValid() || !HashLoc.isFileID() || !File)
290 return;
291 clang::SourceManager &sm = annotator.getSourceMgr();
292 clang::FileID FID = sm.getFileID(HashLoc);
293 if (!annotator.shouldProcess(FID))
294 return;
295
296 std::string link = annotator.pathTo(FID, File);
297 if (link.empty())
298 return;
299
300 auto B = sm.getFileOffset(FilenameRange.getBegin());
301 auto E = sm.getFileOffset(FilenameRange.getEnd());
302
303 annotator.generator(FID).addTag("a", "href=\"" % link % "\"", B, E-B);
304}
305
306void PreprocessorCallback::Defined(const clang::Token& MacroNameTok, MyMacroDefinition MD,
307 clang::SourceRange Range)
308{
309 clang::SourceLocation loc = MacroNameTok.getLocation();
310 if (!loc.isValid() || !loc.isFileID())
311 return;
312
313 clang::SourceManager &sm = annotator.getSourceMgr();
314
315 clang::FileID FID = sm.getFileID(loc);
316 if (!annotator.shouldProcess(FID))
317 return;
318
319 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
320 std::string link;
321 std::string dataProj;
322 clang::SourceLocation defLoc;
323 clang::FileID defFID;
324
325 if (MD) {
326#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 7
327 auto *MI = MD.getMacroInfo();
328#else
329 auto *MI = MD->getMacroInfo();
330#endif
331 if (MI) {
332 defLoc = MI->getDefinitionLoc();
333 defFID = sm.getFileID(defLoc);
334 }
335 }
336
337 if (defFID.isInvalid() || defFID != FID) {
338 if (!defFID.isInvalid()) {
339 link = annotator.pathTo(FID, defFID, &dataProj);
340 }
341 if (link.empty()) {
342 std::string tag = "class=\"macro\" data-ref=\"" % ref % "\"";
343 annotator.generator(FID).addTag("span", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
344 return;
345 }
346
347 if (!dataProj.empty()) {
348 dataProj = " data-proj=\"" % dataProj % "\"";
349 }
350 }
351
352 if (sm.getMainFileID() != defFID) {
353 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Use_Address);
354 }
355
356 std::string tag = "class=\"macro\" href=\"" % link % "#" % llvm::Twine(sm.getExpansionLineNumber(defLoc)).str()
357 % "\" data-ref=\"" % ref % "\"" % dataProj;
358 annotator.generator(FID).addTag("a", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
359}
360
361void PreprocessorCallback::HandlePPCond(clang::SourceLocation Loc, clang::SourceLocation IfLoc)
362{
363 if (!Loc.isValid() || !Loc.isFileID())
364 return;
365
366 clang::SourceManager &SM = annotator.getSourceMgr();
367 clang::FileID FID = SM.getFileID(Loc);
368 if (!annotator.shouldProcess(FID))
369 return;
370
371 while(ElifMapping.count(IfLoc)) {
372 IfLoc = Loc;
373 }
374
375 if (SM.getFileID(IfLoc) != FID) {
376 return;
377 }
378
379 annotator.generator(FID).addTag("span", ("data-ppcond=\"" + clang::Twine(SM.getExpansionLineNumber(IfLoc)) + "\"").str(),
380 SM.getFileOffset(Loc), clang::Lexer::MeasureTokenLength(Loc, SM, PP.getLangOpts()));
381}
382