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{
193 clang::SourceLocation loc = MacroNameTok.getLocation();
194 if (!loc.isValid() || !loc.isFileID())
195 return;
196
197 clang::SourceManager &sm = annotator.getSourceMgr();
198 clang::FileID FID = sm.getFileID(loc);
199 if (!annotator.shouldProcess(FID))
200 return;
201
202 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
203 std::string link;
204 std::string dataProj;
205 clang::SourceLocation defLoc;
206 clang::FileID defFID;
207
208 if (MD) {
209#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 7
210 auto *MI = MD.getMacroInfo();
211#else
212 auto *MI = MD->getMacroInfo();
213#endif
214 if (MI) {
215 defLoc = MI->getDefinitionLoc();
216 defFID = sm.getFileID(defLoc);
217 }
218 }
219
220 if (defFID.isInvalid() || defFID != FID) {
221 if (!defFID.isInvalid()) {
222 link = annotator.pathTo(FID, defFID, &dataProj);
223 }
224 if (link.empty()) {
225 std::string tag = "class=\"macro\" data-ref=\"" % ref % "\"";
226 annotator.generator(FID).addTag("span", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
227 return;
228 }
229
230 if (!dataProj.empty()) {
231 dataProj = " data-proj=\"" % dataProj % "\"";
232 }
233 }
234
235 if (sm.getMainFileID() != defFID) {
236 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Use_Write);
237 }
238
239 std::string tag = "class=\"macro\" href=\"" % link % "#" % llvm::Twine(sm.getExpansionLineNumber(defLoc)).str()
240 % "\" data-ref=\"" % ref % "\"" % dataProj;
241 annotator.generator(FID).addTag("a", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
242}
243
244bool PreprocessorCallback::FileNotFound(llvm::StringRef FileName, llvm::SmallVectorImpl<char> &RecoveryPath)
245{
246 if (!recoverIncludePath)
247 return false;
248 clang::SourceLocation currentLoc = static_cast<clang::Lexer *>(PP.getCurrentLexer())->getSourceLocation();
249 auto &SM = annotator.getSourceMgr();
250 const clang::FileEntry* entry = SM.getFileEntryForID(SM.getFileID(currentLoc));
251 if (!entry || !entry->getName())
252 return false;
253 std::string recovery = annotator.projectManager.includeRecovery(FileName, entry->getName());
254 if (recovery.empty() || !llvm::StringRef(recovery).endswith(FileName))
255 return false;
256 RecoveryPath.clear();
257 RecoveryPath.append(recovery.begin(), recovery.begin() + recovery.size() - FileName.size());
258 currentLoc.dump(SM);
259 llvm::errs() << " WARNING: File not found '" << FileName << "'. Recovering using "
260 << llvm::StringRef(RecoveryPath.data(), RecoveryPath.size()) << "\n";
261
262 return true;
263}
264
265
266void PreprocessorCallback::InclusionDirective(clang::SourceLocation HashLoc, const clang::Token& IncludeTok,
267 llvm::StringRef FileName, bool IsAngled,
268 clang::CharSourceRange FilenameRange, const clang::FileEntry* File,
269 llvm::StringRef SearchPath, llvm::StringRef RelativePath,
270 const clang::Module* Imported)
271{
272 if (!HashLoc.isValid() || !HashLoc.isFileID() || !File)
273 return;
274 clang::SourceManager &sm = annotator.getSourceMgr();
275 clang::FileID FID = sm.getFileID(HashLoc);
276 if (!annotator.shouldProcess(FID))
277 return;
278
279 std::string link = annotator.pathTo(FID, File);
280 if (link.empty())
281 return;
282
283 auto B = sm.getFileOffset(FilenameRange.getBegin());
284 auto E = sm.getFileOffset(FilenameRange.getEnd());
285
286 annotator.generator(FID).addTag("a", "href=\"" % link % "\"", B, E-B);
287}
288
289void PreprocessorCallback::Defined(const clang::Token& MacroNameTok, MyMacroDefinition MD,
290 clang::SourceRange Range)
291{
292 clang::SourceLocation loc = MacroNameTok.getLocation();
293 if (!loc.isValid() || !loc.isFileID())
294 return;
295
296 clang::SourceManager &sm = annotator.getSourceMgr();
297
298 clang::FileID FID = sm.getFileID(loc);
299 if (!annotator.shouldProcess(FID))
300 return;
301
302 std::string ref = llvm::Twine("_M/", MacroNameTok.getIdentifierInfo()->getName()).str();
303 std::string link;
304 std::string dataProj;
305 clang::SourceLocation defLoc;
306 clang::FileID defFID;
307
308 if (MD) {
309#if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 7
310 auto *MI = MD.getMacroInfo();
311#else
312 auto *MI = MD->getMacroInfo();
313#endif
314 if (MI) {
315 defLoc = MI->getDefinitionLoc();
316 defFID = sm.getFileID(defLoc);
317 }
318 }
319
320 if (defFID.isInvalid() || defFID != FID) {
321 if (!defFID.isInvalid()) {
322 link = annotator.pathTo(FID, defFID, &dataProj);
323 }
324 if (link.empty()) {
325 std::string tag = "class=\"macro\" data-ref=\"" % ref % "\"";
326 annotator.generator(FID).addTag("span", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
327 return;
328 }
329
330 if (!dataProj.empty()) {
331 dataProj = " data-proj=\"" % dataProj % "\"";
332 }
333 }
334
335 if (sm.getMainFileID() != defFID) {
336 annotator.registerMacro(ref, MacroNameTok.getLocation(), Annotator::Use_Address);
337 }
338
339 std::string tag = "class=\"macro\" href=\"" % link % "#" % llvm::Twine(sm.getExpansionLineNumber(defLoc)).str()
340 % "\" data-ref=\"" % ref % "\"" % dataProj;
341 annotator.generator(FID).addTag("a", tag, sm.getFileOffset(loc), MacroNameTok.getLength());
342}
343
344void PreprocessorCallback::HandlePPCond(clang::SourceLocation Loc, clang::SourceLocation IfLoc)
345{
346 if (!Loc.isValid() || !Loc.isFileID())
347 return;
348
349 clang::SourceManager &SM = annotator.getSourceMgr();
350 clang::FileID FID = SM.getFileID(Loc);
351 if (!annotator.shouldProcess(FID))
352 return;
353
354 while(ElifMapping.count(IfLoc)) {
355 IfLoc = Loc;
356 }
357
358 if (SM.getFileID(IfLoc) != FID) {
359 return;
360 }
361
362 annotator.generator(FID).addTag("span", ("data-ppcond=\"" + clang::Twine(SM.getExpansionLineNumber(IfLoc)) + "\"").str(),
363 SM.getFileOffset(Loc), clang::Lexer::MeasureTokenLength(Loc, SM, PP.getLangOpts()));
364}
365