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