1/****************************************************************************
2 * Copyright (C) 2013-2016 Woboq GmbH
3 * Olivier Goffart <contact at woboq.com>
4 * https://woboq.com/
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <clang/Frontend/FrontendAction.h>
21#include <clang/Frontend/FrontendActions.h>
22#include <clang/Tooling/Tooling.h>
23#include <clang/Driver/Driver.h>
24#include <clang/Driver/Compilation.h>
25#include <clang/Driver/Tool.h>
26#include <clang/Basic/DiagnosticIDs.h>
27#include <clang/Lex/LexDiagnostic.h>
28
29#include <clang/Driver/Job.h>
30#include "clang/Frontend/TextDiagnosticPrinter.h"
31#include <clang/Lex/Preprocessor.h>
32#include <clang/AST/ASTContext.h>
33#include <clang/AST/DeclCXX.h>
34#include <llvm/Support/Host.h>
35
36#include <vector>
37#include <iostream>
38
39#include "mocastconsumer.h"
40#include "generator.h"
41#include "mocppcallbacks.h"
42#include "embedded_includes.h"
43
44struct MocOptions {
45 bool NoInclude = false;
46 std::vector<std::string> Includes;
47 std::string Output;
48 std::string OutputTemplateHeader;
49 std::vector<std::pair<llvm::StringRef, llvm::StringRef>> MetaData;
50 void addOutput(llvm::StringRef);
51} Options;
52
53void MocOptions::addOutput(llvm::StringRef Out)
54{
55 if (Output.empty()) {
56 Output = Out.str();
57 } else if (OutputTemplateHeader.empty()) {
58 OutputTemplateHeader = Out.str();
59 } else {
60 std::cerr << "moc-ng: Too many output file specified" << std::endl;
61 }
62}
63
64
65/* Proxy that changes some errors into warnings */
66struct MocDiagConsumer : clang::DiagnosticConsumer {
67 std::unique_ptr<DiagnosticConsumer> Proxy;
68 MocDiagConsumer(std::unique_ptr<DiagnosticConsumer> Previous) : Proxy(std::move(Previous)) {}
69
70 int HadRealError = 0;
71
72#if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 2
73 DiagnosticConsumer* clone(clang::DiagnosticsEngine& Diags) const override {
74 return new MocDiagConsumer { Proxy->clone(Diags) };
75 }
76#endif
77 void BeginSourceFile(const clang::LangOptions& LangOpts, const clang::Preprocessor* PP = 0) override {
78 Proxy->BeginSourceFile(LangOpts, PP);
79 }
80 void clear() override {
81 Proxy->clear();
82 }
83 void EndSourceFile() override {
84 Proxy->EndSourceFile();
85 }
86 void finish() override {
87 Proxy->finish();
88 }
89 void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic& Info) override {
90
91 /* Moc ignores most of the errors since it even can operate on non self-contained headers.
92 * So try to change errors into warning.
93 */
94
95 auto DiagId = Info.getID();
96 auto Cat = Info.getDiags()->getDiagnosticIDs()->getCategoryNumberForDiag(DiagId);
97
98 bool ShouldReset = false;
99
100 if (DiagLevel >= clang::DiagnosticsEngine::Error ) {
101 if (Cat == 2 || Cat == 4
102 || DiagId == clang::diag::err_param_redefinition
103 || DiagId == clang::diag::err_pp_expr_bad_token_binop ) {
104 if (!HadRealError)
105 ShouldReset = true;
106 DiagLevel = clang::DiagnosticsEngine::Warning;
107 } else {
108 HadRealError++;
109 }
110 }
111
112 DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info);
113 Proxy->HandleDiagnostic(DiagLevel, Info);
114
115 if (ShouldReset) {
116 // FIXME: is there another way to ignore errors?
117 const_cast<clang::DiagnosticsEngine *>(Info.getDiags())->Reset();
118 }
119 }
120};
121
122
123
124struct MocNGASTConsumer : public MocASTConsumer {
125 std::string InFile;
126 MocNGASTConsumer(clang::CompilerInstance& ci, llvm::StringRef InFile) : MocASTConsumer(ci), InFile(InFile) { }
127
128
129#if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR < 8
130 // Clang 3.8 changed when Initialize is called. It is now called before the main file has been entered.
131 // But with Clang < 3.8 it is called after, and PPCallbacks::FileChanged is not called when entering the main file
132 void Initialize(clang::ASTContext& Ctx) override {
133 MocASTConsumer::Initialize(Ctx);
134 PPCallbacks->EnterMainFile(InFile);
135 }
136#endif
137
138 bool shouldParseDecl(clang::Decl * D) override {
139 // We only want to parse the Qt macro in classes that are in the main file.
140 auto SL = D->getSourceRange().getBegin();
141 SL = ci.getSourceManager().getExpansionLoc(SL);
142 if (ci.getSourceManager().getFileID(SL) != ci.getSourceManager().getMainFileID())
143 return false;
144 return true;
145 }
146
147 void HandleTranslationUnit(clang::ASTContext& Ctx) override {
148
149 if (ci.getDiagnostics().hasErrorOccurred())
150 return;
151
152 if (!objects.size() && !namespaces.size()) {
153 ci.getDiagnostics().Report(ci.getSourceManager().getLocForStartOfFile(ci.getSourceManager().getMainFileID()),
154 ci.getDiagnostics().getCustomDiagID(clang::DiagnosticsEngine::Warning,
155 "No relevant classes found. No output generated"));
156 //actually still create an empty file like moc does.
157 ci.createOutputFile(Options.Output, false, true, "", "", false, false);
158 return;
159 }
160
161 // createOutputFile returns a raw_pwrite_stream* before Clang 3.9, and a std::unique_ptr<raw_pwrite_stream> after
162 auto OS = ci.createOutputFile(Options.Output, false, true, "", "", false, false);
163
164 if (!OS) return;
165 llvm::raw_ostream &Out = *OS;
166
167 auto WriteHeader = [&](llvm::raw_ostream & Out) {
168 Out << "/****************************************************************************\n"
169 "** Meta object code from reading C++ file '" << InFile << "'\n"
170 "**\n"
171 "** Created by MOC-NG version " MOCNG_VERSION_STR " by Woboq [https://woboq.com]\n"
172 "** WARNING! All changes made in this file will be lost!\n"
173 "*****************************************************************************/\n\n";
174 };
175 WriteHeader(Out);
176
177 if (!Options.NoInclude) {
178 for (auto &s : Options.Includes) {
179 Out << s << "\n";
180 }
181
182 auto spos = InFile.rfind('/');
183 auto ppos = InFile.rfind('.');
184 // If it finished by .h, or if there is no dot after a slash, we should include the source file
185 if (ppos == std::string::npos || (spos != std::string::npos && spos > ppos) || llvm::StringRef(InFile).endswith(".h"))
186 Out << "#include \"" << InFile << "\"\n";
187 if (Moc.HasPlugin)
188 Out << "#include <QtCore/qplugin.h>\n";
189 }
190
191 Out << "#include <QtCore/qbytearray.h>\n";
192
193 Out << "#if !defined(Q_MOC_OUTPUT_REVISION)\n"
194 "#error \"The header file '" << InFile << "' doesn't include <QObject>.\"\n"
195 "#elif Q_MOC_OUTPUT_REVISION != " << mocOutputRevision << "\n"
196 "#error \"This file was generated using MOC-NG " MOCNG_VERSION_STR ".\"\n"
197 "#error \"It cannot be used with the include files from this version of Qt.\"\n"
198 "#endif\n\n"
199 "QT_BEGIN_MOC_NAMESPACE\n"
200 "#ifdef QT_WARNING_DISABLE_DEPRECATED\n"
201 "QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED\n"
202 "#endif\n";
203
204 decltype(OS) OS_TemplateHeader = nullptr;
205 if (!Options.OutputTemplateHeader.empty()) {
206 OS_TemplateHeader =
207 ci.createOutputFile(Options.OutputTemplateHeader, false, true, "", "", false, false);
208 if (!OS_TemplateHeader)
209 return;
210 WriteHeader(*OS_TemplateHeader);
211 (*OS_TemplateHeader) << "QT_BEGIN_MOC_NAMESPACE\n"
212 "#ifdef QT_WARNING_DISABLE_DEPRECATED\n"
213 "QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED\n"
214 "#endif\n";
215 }
216
217 for (const ClassDef &Def : objects ) {
218 Generator G(&Def, Out, Ctx, &Moc,
219 Def.Record->getDescribedClassTemplate() ? &*OS_TemplateHeader : nullptr);
220 G.MetaData = Options.MetaData;
221 if (llvm::StringRef(InFile).endswith("global/qnamespace.h"))
222 G.IsQtNamespace = true;
223 G.GenerateCode();
224 };
225 for (const NamespaceDef &Def : namespaces) {
226 Generator G(&Def, Out, Ctx, &Moc);
227 G.MetaData = Options.MetaData;
228 G.GenerateCode();
229 };
230
231 llvm::StringRef footer =
232 "QT_END_MOC_NAMESPACE\n"
233 "#ifdef QT_WARNING_DISABLE_DEPRECATED\n"
234 "QT_WARNING_POP\n"
235 "#endif\n";
236 Out << footer;
237 if (OS_TemplateHeader) {
238 (*OS_TemplateHeader) << footer;
239 }
240 }
241};
242
243class MocAction : public clang::ASTFrontendAction {
244protected:
245#if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5
246 clang::ASTConsumer *
247#else
248 std::unique_ptr<clang::ASTConsumer>
249#endif
250 CreateASTConsumer(clang::CompilerInstance &CI, llvm::StringRef InFile) override {
251
252 CI.getFrontendOpts().SkipFunctionBodies = true;
253 CI.getPreprocessor().enableIncrementalProcessing(true);
254 CI.getPreprocessor().SetSuppressIncludeNotFoundError(true);
255 CI.getLangOpts().DelayedTemplateParsing = true;
256
257 //enable all the extension
258 CI.getLangOpts().MicrosoftExt = true;
259 CI.getLangOpts().DollarIdents = true;
260 CI.getLangOpts().CPlusPlus11 = true;
261#if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5
262 CI.getLangOpts().CPlusPlus1y = true;
263#else
264 CI.getLangOpts().CPlusPlus14 = true;
265#endif
266 CI.getLangOpts().GNUMode = true;
267
268 CI.getDiagnostics().setClient(new MocDiagConsumer(
269 std::unique_ptr<clang::DiagnosticConsumer>(CI.getDiagnostics().takeClient())));
270
271 return maybe_unique(new MocNGASTConsumer(CI, InFile));
272 }
273
274public:
275 // CHECK
276 virtual bool hasCodeCompletionSupport() const { return true; }
277};
278
279static void showVersion(bool /*Long*/) {
280 std::cerr << "moc-ng version " MOCNG_VERSION_STR " by Woboq [https://woboq.com]" << std::endl;
281}
282
283static void showHelp() {
284 std::cerr << "Usage moc: [options] <header-file>\n"
285 " -o<file> write output to file rather than stdout\n"
286 " -I<dir> add dir to the include path for header files\n"
287 " -E preprocess only; do not generate meta object code\n"
288 " -D<macro>[=<def>] define macro, with optional definition\n"
289 " -U<macro> undefine macro\n"
290 " -M<key=valye> add key/value pair to plugin meta data\n"
291 " -i do not generate an #include statement\n"
292// " -p<path> path prefix for included file\n"
293// " -f[<file>] force #include, optional file name\n"
294// " -nn do not display notes\n"
295// " -nw do not display warnings\n"
296// " @<file> read additional options from file\n"
297 " -v display version of moc-ng\n"
298 " -include <file> Adds an implicit #include into the predefines buffer which is read before the source file is preprocessed\n"
299
300/* undocumented options
301 " -W<warnings> Enable the specified warning\n"
302 " -f<option> clang option\n"
303 " -X<ext> <arg> extensions arguments\n"
304*/
305
306
307
308 << std::endl;
309
310
311 showVersion(false);
312}
313
314
315int main(int argc, const char **argv)
316{
317 bool PreprocessorOnly = false;
318 std::vector<std::string> Argv;
319 Argv.push_back(argv[0]);
320 Argv.push_back("-x"); // Type need to go first
321 Argv.push_back("c++");
322 Argv.push_back("-fPIE");
323 Argv.push_back("-fPIC");
324 Argv.push_back("-Wno-microsoft"); // get rid of a warning in qtextdocument.h
325 Argv.push_back("-Wno-pragma-once-outside-header");
326#if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5
327 Argv.push_back("-std=c++11");
328#else
329 Argv.push_back("-std=c++14");
330#endif
331
332 bool NextArgNotInput = false;
333 bool HasInput = false;
334 llvm::StringRef InputFile;
335
336 for (int I = 1 ; I < argc; ++I) {
337 if (argv[I][0] == '-') {
338 NextArgNotInput = false;
339 switch (argv[I][1]) {
340 case 'h':
341 case '?':
342 showHelp();
343 return EXIT_SUCCESS;
344 case 'v':
345 showVersion(true);
346 return EXIT_SUCCESS;
347 case 'o':
348 if (argv[I][2]) Options.addOutput(&argv[I][2]);
349 else if ((++I) < argc) Options.addOutput(argv[I]);
350 continue;
351 case 'i':
352 if (argv[I] == llvm::StringRef("-i")) {
353 Options.NoInclude = true;
354 continue;
355 } else if (argv[I] == llvm::StringRef("-include")) {
356 NextArgNotInput = true;
357 break;
358 }
359 goto invalidArg;
360 case 'M': {
361 llvm::StringRef Arg;
362 if (argv[I][2]) Arg = &argv[I][2];
363 else if ((++I) < argc) Arg = argv[I];
364 size_t Eq = Arg.find('=');
365 if (Eq == llvm::StringRef::npos) {
366 std::cerr << "moc-ng: missing key or value for option '-M'" << std::endl;
367 return EXIT_FAILURE;
368 }
369 Options.MetaData.push_back({Arg.substr(0, Eq), Arg.substr(Eq+1)});
370 continue;
371 }
372 case 'E':
373 PreprocessorOnly = true;
374 break;
375 case 'I':
376 case 'U':
377 case 'D':
378 NextArgNotInput = (argv[I][2] == '\0');
379 break;
380 case 'X':
381 NextArgNotInput = true;
382 break;
383 case 'f': //this is understood as compiler option rather than moc -f
384 case 'W': // same
385 break;
386 case 'n': //not implemented, silently ignored
387 continue;
388 case '-':
389 if (llvm::StringRef(argv[I]) == "--include" ||
390 llvm::StringRef(argv[I]).startswith("--include=")) {
391 llvm::StringRef File;
392 if (llvm::StringRef(argv[I]).startswith("--include=")) {
393 File = llvm::StringRef(argv[I]).substr(llvm::StringRef("--include=").size());
394 } else if (I + 1 < argc) {
395 File = llvm::StringRef(argv[++I]);
396 }
397 if (File.endswith("/moc_predefs.h")) {
398 // qmake generates moc_predefs with compiler defined stuff.
399 // We can't support it because everything is already pre-defined.
400 // So skip it.
401 continue;
402 }
403 Argv.push_back("-include");
404 Argv.push_back(File);
405 continue;
406 }
407 if (llvm::StringRef(argv[I]).startswith("--compiler-flavor")) {
408 if (llvm::StringRef(argv[I]) == "--compiler-flavor")
409 ++I;
410 // MSVC flavor not yet implemented
411 continue;
412 }
413 LLVM_FALLTHROUGH;
414 default:
415invalidArg:
416 std::cerr << "moc-ng: Invalid argument '" << argv[I] << "'" << std::endl;
417 showHelp();
418 return EXIT_FAILURE;
419 }
420 } else if (!NextArgNotInput) {
421 if (HasInput) {
422 std::cerr << "error: Too many input files specified" << std::endl;
423 return EXIT_FAILURE;
424 }
425 HasInput = true;
426 InputFile = argv[I];
427 }
428 Argv.push_back(argv[I]);
429 }
430
431 if (Options.Output.empty())
432 Options.Output = "-";
433
434 if (!HasInput)
435 Argv.push_back("-");
436
437 //FIXME
438 Argv.push_back("-I/usr/include/qt5");
439 Argv.push_back("-I/usr/include/qt5/QtCore");
440 Argv.push_back("-I/usr/include/qt");
441 Argv.push_back("-I/usr/include/qt/QtCore");
442 Argv.push_back("-I.uic"); // workaround the fact that the uic generated code cannot be found
443
444 Argv.push_back("-I/builtins");
445
446 clang::FileManager FM({"."});
447 FM.Retain();
448
449 if (PreprocessorOnly) {
450 Argv.push_back("-P");
451 clang::tooling::ToolInvocation Inv(Argv, new clang::PrintPreprocessedAction, &FM);
452 return !Inv.run();
453 }
454
455 Argv.push_back("-fsyntax-only");
456
457 if (!InputFile.endswith("qobject.h") && !InputFile.endswith("qnamespace.h")) {
458 // QObject should always be included
459 // But not not for qobject.h (or we would not detect the main file correctly) or
460 // qnamespace.h (that would break the Q_MOC_RUN workaround from MocPPCallbacks::EnterMainFile)
461 Argv.push_back("-include");
462 Argv.push_back("QtCore/qobject.h");
463 }
464
465 clang::tooling::ToolInvocation Inv(Argv, new MocAction, &FM);
466
467 const EmbeddedFile *f = EmbeddedFiles;
468 while (f->filename) {
469 Inv.mapVirtualFile(f->filename, {f->content , f->size } );
470 f++;
471 }
472
473 return !Inv.run();
474}
475
476
477
478
479