1 //===-- Fuzzer.cpp - Fuzz the pseudoparser --------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "clang-pseudo/DirectiveTree.h"
10 #include "clang-pseudo/Forest.h"
11 #include "clang-pseudo/GLR.h"
12 #include "clang-pseudo/Token.h"
13 #include "clang-pseudo/grammar/Grammar.h"
14 #include "clang-pseudo/grammar/LRTable.h"
15 #include "clang/Basic/LangOptions.h"
16 #include "llvm/ADT/StringRef.h"
17 #include "llvm/Support/MemoryBuffer.h"
18 #include "llvm/Support/raw_ostream.h"
19 #include <algorithm>
20 
21 namespace clang {
22 namespace pseudo {
23 namespace {
24 
25 class Fuzzer {
26   clang::LangOptions LangOpts = clang::pseudo::genericLangOpts();
27   std::unique_ptr<Grammar> G;
28   LRTable T;
29   bool Print;
30 
31 public:
32   Fuzzer(llvm::StringRef GrammarPath, bool Print) : Print(Print) {
33     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> GrammarText =
34         llvm::MemoryBuffer::getFile(GrammarPath);
35     if (std::error_code EC = GrammarText.getError()) {
36       llvm::errs() << "Error: can't read grammar file '" << GrammarPath
37                    << "': " << EC.message() << "\n";
38       std::exit(1);
39     }
40     std::vector<std::string> Diags;
41     G = Grammar::parseBNF(GrammarText->get()->getBuffer(), Diags);
42     if (!Diags.empty()) {
43       for (const auto &Diag : Diags)
44         llvm::errs() << Diag << "\n";
45       std::exit(1);
46     }
47     T = LRTable::buildSLR(*G);
48   }
49 
50   void operator()(llvm::StringRef Code) {
51     std::string CodeStr = Code.str(); // Must be null-terminated.
52     auto RawStream = lex(CodeStr, LangOpts);
53     auto DirectiveStructure = DirectiveTree::parse(RawStream);
54     clang::pseudo::chooseConditionalBranches(DirectiveStructure, RawStream);
55     // FIXME: strip preprocessor directives
56     auto ParseableStream =
57         clang::pseudo::stripComments(cook(RawStream, LangOpts));
58 
59     clang::pseudo::ForestArena Arena;
60     clang::pseudo::GSS GSS;
61     auto &Root =
62         glrParse(ParseableStream, clang::pseudo::ParseParams{*G, T, Arena, GSS},
63                  *G->findNonterminal("translation-unit"));
64     if (Print)
65       llvm::outs() << Root.dumpRecursive(*G);
66   }
67 };
68 
69 Fuzzer *Fuzz = nullptr;
70 
71 } // namespace
72 } // namespace pseudo
73 } // namespace clang
74 
75 extern "C" {
76 
77 // Set up the fuzzer from command line flags:
78 //  -grammar=<file> (required) - path to cxx.bnf
79 //  -print                     - used for testing the fuzzer
80 int LLVMFuzzerInitialize(int *Argc, char ***Argv) {
81   llvm::StringRef GrammarFile;
82   bool PrintForest = false;
83   auto ConsumeArg = [&](llvm::StringRef Arg) -> bool {
84     if (Arg.consume_front("-grammar=")) {
85       GrammarFile = Arg;
86       return true;
87     } else if (Arg == "-print") {
88       PrintForest = true;
89       return true;
90     }
91     return false;
92   };
93   *Argc = std::remove_if(*Argv + 1, *Argv + *Argc, ConsumeArg) - *Argv;
94 
95   if (GrammarFile.empty()) {
96     fprintf(stderr, "Fuzzer needs -grammar=/path/to/cxx.bnf\n");
97     exit(1);
98   }
99   clang::pseudo::Fuzz = new clang::pseudo::Fuzzer(GrammarFile, PrintForest);
100   return 0;
101 }
102 
103 int LLVMFuzzerTestOneInput(uint8_t *Data, size_t Size) {
104   (*clang::pseudo::Fuzz)(llvm::StringRef(reinterpret_cast<char *>(Data), Size));
105   return 0;
106 }
107 }
108