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 "projectmanager.h"
25#include "stringbuilder.h"
26#include <clang/Basic/FileManager.h>
27#include <clang/Basic/Version.h>
28#include <clang/Lex/MacroInfo.h>
29#include <clang/Lex/Preprocessor.h>
30#include <clang/Lex/Token.h>
31#include <llvm/ADT/Twine.h>
32
33
34void PreprocessorCallback::MacroExpands(const clang::Token &MacroNameTok, 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(SpellingLoc: loc);
50 if (!annotator.shouldProcess(FID))
51 return;
52
53 const char *begin = sm.getCharacterData(SL: Range.getBegin());
54 int len = sm.getCharacterData(SL: Range.getEnd()) - begin;
55 len += clang::Lexer::MeasureTokenLength(Loc: Range.getEnd(), SM: sm, LangOpts: 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(Result&: 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(K: 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(K: 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(K: clang::tok::raw_identifier))
86 PP.LookUpIdentifierInfo(Identifier&: tok);
87
88 tokens.push_back(x: tok);
89
90 } while (!tok.is(K: 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(Toks: 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(Result&: tok);
118 while (tok.isNot(K: clang::tok::eof)) {
119 if (seenPragma) {
120 // skip pragma
121 while (tok.isNot(K: clang::tok::eof) && tok.isNot(K: clang::tok::eod))
122 PP.Lex(Result&: tok);
123 seenPragma = false;
124 PP.Lex(Result&: 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: tok);
135
136 if (expansion.size() >= 30 * 1000) {
137 // Don't let the macro expansion grow too large.
138 expansion += "...";
139 while (tok.isNot(K: clang::tok::eof))
140 PP.LexUnexpandedToken(Result&: tok);
141 break;
142 }
143
144 PP.Lex(Result&: 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(SpellingLoc: defLoc);
155 llvm::SmallString<128> expansionBuffer;
156 std::string link;
157 std::string dataProj;
158 if (defFID != FID) {
159 link = annotator.pathTo(From: FID, To: defFID, proj: &dataProj);
160 if (link.empty()) {
161 std::string tag = "class=\"macro\" title=\""
162 % Generator::escapeAttr(expansion, buffer&: expansionBuffer) % "\" data-ref=\"" % ref % "\"";
163 annotator.generator(fid: FID).addTag(name: "span", attributes: tag, pos: sm.getFileOffset(SpellingLoc: loc),
164 len: MacroNameTok.getLength());
165 return;
166 }
167
168 if (!dataProj.empty()) {
169 dataProj = " data-proj=\"" % dataProj % "\"";
170 }
171 }
172
173 if (sm.getMainFileID() != defFID) {
174 annotator.registerMacro(ref, refLoc: MacroNameTok.getLocation(), declType: Annotator::Use_Call);
175 }
176
177 std::string tag = "class=\"macro\" href=\"" % link % "#"
178 % llvm::Twine(sm.getExpansionLineNumber(Loc: defLoc)).str() % "\" title=\""
179 % Generator::escapeAttr(expansion, buffer&: expansionBuffer) % "\" data-ref=\"" % ref % "\""
180 % dataProj;
181 annotator.generator(fid: FID).addTag(name: "a", attributes: tag, pos: sm.getFileOffset(SpellingLoc: loc), len: MacroNameTok.getLength());
182}
183
184void PreprocessorCallback::MacroDefined(const clang::Token &MacroNameTok,
185 const clang::MacroDirective *MD)
186{
187 clang::SourceLocation loc = MacroNameTok.getLocation();
188 if (!loc.isValid() || !loc.isFileID())
189 return;
190
191 clang::SourceManager &sm = annotator.getSourceMgr();
192 clang::FileID FID = sm.getFileID(SpellingLoc: loc);
193 if (!annotator.shouldProcess(FID))
194 return;
195
196 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
197
198 if (sm.getMainFileID() != FID) {
199 annotator.registerMacro(ref, refLoc: MacroNameTok.getLocation(), declType: Annotator::Declaration);
200 }
201
202 annotator.generator(fid: FID).addTag(name: "dfn",
203 attributes: "class=\"macro\" id=\"" % ref % "\" data-ref=\"" % ref % "\"",
204 pos: sm.getFileOffset(SpellingLoc: loc), len: MacroNameTok.getLength());
205}
206
207void PreprocessorCallback::MacroUndefined(const clang::Token &MacroNameTok,
208 PreprocessorCallback::MyMacroDefinition MD
209#if CLANG_VERSION_MAJOR >= 5
210 ,
211 const clang::MacroDirective *
212#endif
213)
214{
215 clang::SourceLocation loc = MacroNameTok.getLocation();
216 if (!loc.isValid() || !loc.isFileID())
217 return;
218
219 clang::SourceManager &sm = annotator.getSourceMgr();
220 clang::FileID FID = sm.getFileID(SpellingLoc: loc);
221 if (!annotator.shouldProcess(FID))
222 return;
223
224 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
225 std::string link;
226 std::string dataProj;
227 clang::SourceLocation defLoc;
228 clang::FileID defFID;
229
230 if (MD) {
231#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 7
232 auto *MI = MD.getMacroInfo();
233#else
234 auto *MI = MD->getMacroInfo();
235#endif
236 if (MI) {
237 defLoc = MI->getDefinitionLoc();
238 defFID = sm.getFileID(SpellingLoc: defLoc);
239 }
240 }
241
242 if (defFID.isInvalid() || defFID != FID) {
243 if (!defFID.isInvalid()) {
244 link = annotator.pathTo(From: FID, To: defFID, proj: &dataProj);
245 }
246 if (link.empty()) {
247 std::string tag = "class=\"macro\" data-ref=\"" % ref % "\"";
248 annotator.generator(fid: FID).addTag(name: "span", attributes: tag, pos: sm.getFileOffset(SpellingLoc: loc),
249 len: MacroNameTok.getLength());
250 return;
251 }
252
253 if (!dataProj.empty()) {
254 dataProj = " data-proj=\"" % dataProj % "\"";
255 }
256 }
257
258 if (sm.getMainFileID() != defFID) {
259 annotator.registerMacro(ref, refLoc: MacroNameTok.getLocation(), declType: Annotator::Use_Write);
260 }
261
262 std::string tag = "class=\"macro\" href=\"" % link % "#"
263 % llvm::Twine(sm.getExpansionLineNumber(Loc: defLoc)).str() % "\" data-ref=\"" % ref % "\""
264 % dataProj;
265 annotator.generator(fid: FID).addTag(name: "a", attributes: tag, pos: sm.getFileOffset(SpellingLoc: loc), len: MacroNameTok.getLength());
266}
267
268void PreprocessorCallback::InclusionDirective(
269 clang::SourceLocation HashLoc, const clang::Token &IncludeTok, llvm::StringRef FileName,
270 bool IsAngled, clang::CharSourceRange FilenameRange,
271#if CLANG_VERSION_MAJOR >= 16
272 clang::OptionalFileEntryRef File,
273#elif CLANG_VERSION_MAJOR >= 15
274 llvm::Optional<clang::FileEntryRef> File,
275#else
276 const clang::FileEntry *File,
277#endif
278 llvm::StringRef SearchPath, llvm::StringRef RelativePath, const clang::Module *Imported
279#if CLANG_VERSION_MAJOR >= 7
280 , clang::SrcMgr::CharacteristicKind
281#endif
282)
283{
284 if (!HashLoc.isValid() || !HashLoc.isFileID() || !File)
285 return;
286 clang::SourceManager &sm = annotator.getSourceMgr();
287 clang::FileID FID = sm.getFileID(SpellingLoc: HashLoc);
288 if (!annotator.shouldProcess(FID))
289 return;
290
291#if CLANG_VERSION_MAJOR >= 16
292 std::string link = annotator.pathTo(From: FID, To: *File);
293#elif CLANG_VERSION_MAJOR >= 15
294 std::string link = annotator.pathTo(FID, File.value());
295#else
296 std::string link = annotator.pathTo(FID, File);
297#endif
298 if (link.empty())
299 return;
300
301 auto B = sm.getFileOffset(SpellingLoc: FilenameRange.getBegin());
302 auto E = sm.getFileOffset(SpellingLoc: FilenameRange.getEnd());
303
304 annotator.generator(fid: FID).addTag(name: "a", attributes: "href=\"" % link % "\"", pos: B, len: E - B);
305}
306
307void PreprocessorCallback::Defined(const clang::Token &MacroNameTok, MyMacroDefinition MD,
308 clang::SourceRange Range)
309{
310 clang::SourceLocation loc = MacroNameTok.getLocation();
311 if (!loc.isValid() || !loc.isFileID())
312 return;
313
314 clang::SourceManager &sm = annotator.getSourceMgr();
315
316 clang::FileID FID = sm.getFileID(SpellingLoc: loc);
317 if (!annotator.shouldProcess(FID))
318 return;
319
320 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
321 std::string link;
322 std::string dataProj;
323 clang::SourceLocation defLoc;
324 clang::FileID defFID;
325
326 if (MD) {
327#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 7
328 auto *MI = MD.getMacroInfo();
329#else
330 auto *MI = MD->getMacroInfo();
331#endif
332 if (MI) {
333 defLoc = MI->getDefinitionLoc();
334 defFID = sm.getFileID(SpellingLoc: defLoc);
335 }
336 }
337
338 if (defFID.isInvalid() || defFID != FID) {
339 if (!defFID.isInvalid()) {
340 link = annotator.pathTo(From: FID, To: defFID, proj: &dataProj);
341 }
342 if (link.empty()) {
343 std::string tag = "class=\"macro\" data-ref=\"" % ref % "\"";
344 annotator.generator(fid: FID).addTag(name: "span", attributes: tag, pos: sm.getFileOffset(SpellingLoc: loc),
345 len: 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, refLoc: MacroNameTok.getLocation(), declType: Annotator::Use_Address);
356 }
357
358 std::string tag = "class=\"macro\" href=\"" % link % "#"
359 % llvm::Twine(sm.getExpansionLineNumber(Loc: defLoc)).str() % "\" data-ref=\"" % ref % "\""
360 % dataProj;
361 annotator.generator(fid: FID).addTag(name: "a", attributes: tag, pos: sm.getFileOffset(SpellingLoc: loc), len: MacroNameTok.getLength());
362}
363
364void PreprocessorCallback::HandlePPCond(clang::SourceLocation Loc, clang::SourceLocation IfLoc)
365{
366 if (!Loc.isValid() || !Loc.isFileID())
367 return;
368
369 clang::SourceManager &SM = annotator.getSourceMgr();
370 clang::FileID FID = SM.getFileID(SpellingLoc: Loc);
371 if (!annotator.shouldProcess(FID))
372 return;
373
374 while (ElifMapping.count(x: IfLoc)) {
375 IfLoc = Loc;
376 }
377
378 if (SM.getFileID(SpellingLoc: IfLoc) != FID) {
379 return;
380 }
381
382 annotator.generator(fid: FID).addTag(
383 name: "span", attributes: ("data-ppcond=\"" + clang::Twine(SM.getExpansionLineNumber(Loc: IfLoc)) + "\"").str(),
384 pos: SM.getFileOffset(SpellingLoc: Loc), len: clang::Lexer::MeasureTokenLength(Loc, SM, LangOpts: PP.getLangOpts()));
385}
386

source code of codebrowser/generator/preprocessorcallback.cpp