1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <unistd.h>
18 
19 #include <cerrno>
20 #include <cstdio>
21 #include <cstring>
22 #include <filesystem>
23 #include <fstream>
24 #include <iomanip>
25 #include <iostream>
26 #include <queue>
27 #include <regex>
28 #include <sstream>
29 #include <vector>
30 
31 #include "declarations.h"
32 #include "language_y.h"
33 #include "struct_parser_generator.h"
34 
35 int yylex_init(void**);
36 int yylex_destroy(void*);
37 void yyset_debug(int, void*);
38 void yyset_in(FILE*, void*);
39 
40 bool generate_cpp_headers_one_file(const Declarations& decls, bool generate_fuzzing,
41                                    bool generate_tests, const std::filesystem::path& input_file,
42                                    const std::filesystem::path& include_dir,
43                                    const std::filesystem::path& out_dir,
44                                    const std::string& root_namespace);
45 
parse_declarations_one_file(const std::filesystem::path & input_file,Declarations * declarations)46 bool parse_declarations_one_file(const std::filesystem::path& input_file,
47                                  Declarations* declarations) {
48   void* scanner;
49   yylex_init(&scanner);
50 
51   FILE* in_file = fopen(input_file.string().c_str(), "r");
52   if (in_file == nullptr) {
53     std::cerr << "can't open " << input_file << ": " << strerror(errno) << std::endl;
54     return false;
55   }
56 
57   yyset_in(in_file, scanner);
58 
59   int ret = yy::parser(scanner, declarations).parse();
60   if (ret != 0) {
61     std::cerr << "yylex parsing failed: returned " << ret << std::endl;
62     return false;
63   }
64 
65   yylex_destroy(scanner);
66 
67   fclose(in_file);
68 
69   // Set endianess before returning
70   for (auto& s : declarations->type_defs_queue_) {
71     if (s.second->GetDefinitionType() == TypeDef::Type::STRUCT) {
72       auto* struct_def = static_cast<StructDef*>(s.second);
73       struct_def->SetEndianness(declarations->is_little_endian);
74     }
75   }
76 
77   for (auto& packet_def : declarations->packet_defs_queue_) {
78     packet_def.second->SetEndianness(declarations->is_little_endian);
79     if (packet_def.second->parent_ != nullptr) {
80       packet_def.second->parent_->children_.push_back(packet_def.second);
81     }
82   }
83 
84   return true;
85 }
86 
87 // TODO(b/141583809): stop leaks
__asan_default_options()88 extern "C" const char* __asan_default_options() { return "detect_leaks=0"; }
89 
usage(const char * prog)90 void usage(const char* prog) {
91   auto& ofs = std::cerr;
92 
93   ofs << "Usage: " << prog << " [OPTIONS] file1 file2..." << std::endl;
94 
95   ofs << std::setw(24) << "--out= ";
96   ofs << "Root directory for generated output (relative to cwd)." << std::endl;
97 
98   ofs << std::setw(24) << "--include= ";
99   ofs << "Generate namespaces relative to this path per file." << std::endl;
100 
101   ofs << std::setw(24) << "--root_namespace= ";
102   ofs << "Change root namespace (default = bluetooth)." << std::endl;
103 
104   ofs << std::setw(24) << "--source_root= ";
105   ofs << "Root path to the source directory. Find input files relative to this." << std::endl;
106 }
107 
main(int argc,const char ** argv)108 int main(int argc, const char** argv) {
109   std::filesystem::path out_dir;
110   std::filesystem::path include_dir;
111   std::filesystem::path cwd = std::filesystem::current_path();
112   std::filesystem::path source_root = cwd;
113   std::string root_namespace = "bluetooth";
114   bool generate_fuzzing = false;
115   bool generate_tests = false;
116   std::queue<std::filesystem::path> input_files;
117 
118   const std::string arg_out = "--out=";
119   const std::string arg_include = "--include=";
120   const std::string arg_namespace = "--root_namespace=";
121   const std::string arg_fuzzing = "--fuzzing";
122   const std::string arg_testing = "--testing";
123   const std::string arg_source_root = "--source_root=";
124 
125   // Parse the source root first (if it exists) since it will be used for other
126   // paths.
127   for (int i = 1; i < argc; i++) {
128     std::string arg = argv[i];
129     if (arg.find(arg_source_root) == 0) {
130       source_root = std::filesystem::path(arg.substr(arg_source_root.size()));
131     }
132   }
133 
134   for (int i = 1; i < argc; i++) {
135     std::string arg = argv[i];
136     if (arg.find(arg_out) == 0) {
137       out_dir = cwd / std::filesystem::path(arg.substr(arg_out.size()));
138     } else if (arg.find(arg_include) == 0) {
139       include_dir = source_root / std::filesystem::path(arg.substr(arg_include.size()));
140     } else if (arg.find(arg_namespace) == 0) {
141       root_namespace = arg.substr(arg_namespace.size());
142     } else if (arg.find(arg_fuzzing) == 0) {
143       generate_fuzzing = true;
144     } else if (arg.find(arg_testing) == 0) {
145       generate_tests = true;
146     } else if (arg.find(arg_source_root) == 0) {
147       // Do nothing (just don't treat it as input_files)
148     } else {
149       input_files.emplace(source_root / std::filesystem::path(arg));
150     }
151   }
152   if (out_dir == std::filesystem::path() || include_dir == std::filesystem::path()) {
153     usage(argv[0]);
154     return 1;
155   }
156 
157   std::cout << "out dir: " << out_dir << std::endl;
158 
159   while (!input_files.empty()) {
160     Declarations declarations;
161     std::cout << "parsing: " << input_files.front() << std::endl;
162     if (!parse_declarations_one_file(input_files.front(), &declarations)) {
163       std::cerr << "Cannot parse " << input_files.front() << " correctly" << std::endl;
164       return 2;
165     }
166     std::cout << "generating c++" << std::endl;
167     if (!generate_cpp_headers_one_file(declarations, generate_fuzzing, generate_tests,
168                                        input_files.front(), include_dir, out_dir, root_namespace)) {
169       std::cerr << "Didn't generate cpp headers for " << input_files.front() << std::endl;
170       return 3;
171     }
172     input_files.pop();
173   }
174 
175   return 0;
176 }
177