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/Grammar.h"
13 #include "clang-pseudo/LRTable.h"
14 #include "clang-pseudo/Token.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 = glrParse(ParseableStream,
62                           clang::pseudo::ParseParams{*G, T, Arena, GSS});
63     if (Print)
64       llvm::outs() << Root.dumpRecursive(*G);
65   }
66 };
67 
68 Fuzzer *Fuzz = nullptr;
69 
70 } // namespace
71 } // namespace pseudo
72 } // namespace clang
73 
74 extern "C" {
75 
76 // Set up the fuzzer from command line flags:
77 //  -grammar=<file> (required) - path to cxx.bnf
78 //  -print                     - used for testing the fuzzer
79 int LLVMFuzzerInitialize(int *Argc, char ***Argv) {
80   llvm::StringRef GrammarFile;
81   bool PrintForest = false;
82   auto ConsumeArg = [&](llvm::StringRef Arg) -> bool {
83     if (Arg.consume_front("-grammar=")) {
84       GrammarFile = Arg;
85       return true;
86     } else if (Arg == "-print") {
87       PrintForest = true;
88       return true;
89     }
90     return false;
91   };
92   *Argc = std::remove_if(*Argv + 1, *Argv + *Argc, ConsumeArg) - *Argv;
93 
94   if (GrammarFile.empty()) {
95     fprintf(stderr, "Fuzzer needs -grammar=/path/to/cxx.bnf\n");
96     exit(1);
97   }
98   clang::pseudo::Fuzz = new clang::pseudo::Fuzzer(GrammarFile, PrintForest);
99   return 0;
100 }
101 
102 int LLVMFuzzerTestOneInput(uint8_t *Data, size_t Size) {
103   (*clang::pseudo::Fuzz)(llvm::StringRef(reinterpret_cast<char *>(Data), Size));
104   return 0;
105 }
106 }
107