1 /*
2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3 *
4 * HDF is dual licensed: you can use it either under the terms of
5 * the GPL, or the BSD license, at your option.
6 * See the LICENSE file in the root of this repository for complete details.
7 */
8
9 #include "preprocessor/preprocessor.h"
10
11 #include <algorithm>
12 #include <queue>
13
14 #include "util/common.h"
15 #include "util/file.h"
16 #include "util/logger.h"
17 #include "util/options.h"
18 #include "util/string_builder.h"
19
20 namespace OHOS {
21 namespace HDI {
Dump() const22 std::string FileDetail::Dump() const
23 {
24 StringBuilder sb;
25 sb.AppendFormat("filePath:%s\n", filePath_.c_str());
26 sb.AppendFormat("fileName:%s\n", fileName_.c_str());
27 sb.AppendFormat("packageName:%s\n", packageName_.c_str());
28 if (imports_.size() == 0) {
29 sb.Append("import:[]\n");
30 } else {
31 sb.Append("import:[\n");
32 for (const auto &importStr : imports_) {
33 sb.AppendFormat("%s,\n", importStr.c_str());
34 }
35 sb.Append("]\n");
36 }
37 return sb.ToString();
38 }
39
Preprocess(std::vector<FileDetail> & fileDetails)40 bool Preprocessor::Preprocess(std::vector<FileDetail> &fileDetails)
41 {
42 std::set<std::string> sourceFiles = Options::GetInstance().GetSourceFiles();
43
44 // check all path of idl files
45 if (!CheckAllFilesPath(sourceFiles)) {
46 return false;
47 }
48
49 // analyse impirt infomation of all idl file
50 FileDetailMap allFileDetails;
51 if (!AnalyseImportInfo(sourceFiles, allFileDetails)) {
52 return false;
53 }
54
55 // calculate the order of idl files to compile by counter-topological sorting
56 if (!CheckCircularReference(allFileDetails, fileDetails)) {
57 return false;
58 }
59
60 return true;
61 }
62
UnitPreprocess(FileDetailMap & fileDetails)63 bool Preprocessor::UnitPreprocess(FileDetailMap &fileDetails)
64 {
65 std::set<std::string> sourceFiles = Options::GetInstance().GetSourceFiles();
66 // check all path of idl files
67 if (!CheckAllFilesPath(sourceFiles)) {
68 return false;
69 }
70
71 for (const auto &sourceFile : sourceFiles) {
72 FileDetail info;
73 if (!ParseFileDetail(sourceFile, info)) {
74 return false;
75 }
76 fileDetails[info.GetFullName()] = info;
77 }
78
79 return true;
80 }
81
CheckAllFilesPath(const std::set<std::string> & sourceFiles)82 bool Preprocessor::CheckAllFilesPath(const std::set<std::string> &sourceFiles)
83 {
84 if (sourceFiles.empty()) {
85 Logger::E(TAG, "no source files");
86 return false;
87 }
88
89 bool ret = true;
90 for (const auto &filePath : sourceFiles) {
91 if (!File::CheckValid(filePath)) {
92 Logger::E(TAG, "invailed file path '%s'.", filePath.c_str());
93 ret = false;
94 }
95 }
96
97 return ret;
98 }
99
AnalyseImportInfo(std::set<std::string> sourceFiles,FileDetailMap & allFileDetails)100 bool Preprocessor::AnalyseImportInfo(std::set<std::string> sourceFiles, FileDetailMap &allFileDetails)
101 {
102 std::set<std::string> processSource(sourceFiles);
103 while (!processSource.empty()) {
104 auto fileIter = processSource.begin();
105 FileDetail info;
106 if (!ParseFileDetail(*fileIter, info)) {
107 return false;
108 }
109
110 processSource.erase(fileIter);
111 allFileDetails[info.GetFullName()] = info;
112 if (!LoadOtherIdlFiles(info, allFileDetails, processSource)) {
113 Logger::E(TAG, "failed to load other by %s", info.GetFullName().c_str());
114 return false;
115 }
116 }
117
118 return true;
119 }
120
ParseFileDetail(const std::string & sourceFile,FileDetail & info)121 bool Preprocessor::ParseFileDetail(const std::string &sourceFile, FileDetail &info)
122 {
123 Lexer lexer;
124 if (!lexer.Reset(sourceFile)) {
125 Logger::E(TAG, "failed to open file '%s'.", sourceFile.c_str());
126 return false;
127 }
128
129 info.filePath_ = lexer.GetFilePath();
130 size_t startIndex = info.filePath_.rfind(SEPARATOR);
131 size_t endIndex = info.filePath_.rfind(".idl");
132 if (startIndex == std::string::npos || endIndex == std::string::npos || (startIndex >= endIndex)) {
133 Logger::E(TAG, "failed to get file name from '%s'.", info.filePath_.c_str());
134 return false;
135 }
136 info.fileName_ = StringHelper::SubStr(info.filePath_, startIndex + 1, endIndex);
137
138 if (!ParsePackage(lexer, info)) {
139 return false;
140 }
141
142 if (!ParseImports(lexer, info)) {
143 return false;
144 }
145 return true;
146 }
147
ParsePackage(Lexer & lexer,FileDetail & info)148 bool Preprocessor::ParsePackage(Lexer &lexer, FileDetail &info)
149 {
150 Token token = lexer.PeekToken();
151 if (token.kind != TokenType::PACKAGE) {
152 Logger::E(TAG, "%s: expected 'package' before '%s' token", LocInfo(token).c_str(), token.value.c_str());
153 return false;
154 }
155 lexer.GetToken();
156
157 token = lexer.PeekToken();
158 if (token.kind != TokenType::ID) {
159 Logger::E(TAG, "%s: expected package name before '%s' token", LocInfo(token).c_str(), token.value.c_str());
160 return false;
161 }
162
163 if (!CheckPackageName(info.filePath_, token.value)) {
164 Logger::E(TAG, "%s:package name '%s' does not match file path '%s'", LocInfo(token).c_str(),
165 token.value.c_str(), info.filePath_.c_str());
166 return false;
167 }
168 info.packageName_ = token.value;
169 lexer.GetToken();
170
171 token = lexer.PeekToken();
172 if (token.kind != TokenType::SEMICOLON) {
173 Logger::E(TAG, "%s:expected ';' before '%s' token", LocInfo(token).c_str(), token.value.c_str());
174 return false;
175 }
176 lexer.GetToken();
177 return true;
178 }
179
ParseImports(Lexer & lexer,FileDetail & info)180 bool Preprocessor::ParseImports(Lexer &lexer, FileDetail &info)
181 {
182 Token token = lexer.PeekToken();
183 while (token.kind != TokenType::END_OF_FILE) {
184 if (token.kind != TokenType::IMPORT) {
185 lexer.GetToken();
186 token = lexer.PeekToken();
187 continue;
188 }
189
190 lexer.GetToken();
191 token = lexer.PeekToken();
192 if (token.kind != TokenType::ID) {
193 Logger::E(TAG, "%s: expected import name before '%s' token", LocInfo(token).c_str(), token.value.c_str());
194 return false;
195 }
196
197 if (!File::CheckValid(Options::GetInstance().GetImportFilePath(token.value))) {
198 Logger::E(TAG, "%s: import invalid package '%s'", LocInfo(token).c_str(), token.value.c_str());
199 return false;
200 }
201
202 info.imports_.emplace(token.value);
203 lexer.GetToken();
204
205 token = lexer.PeekToken();
206 if (token.kind != TokenType::SEMICOLON) {
207 Logger::E(TAG, "%s:expected ';' before '%s' token", LocInfo(token).c_str(), token.value.c_str());
208 return false;
209 }
210 lexer.GetToken();
211
212 token = lexer.PeekToken();
213 }
214 return true;
215 }
216
LoadOtherIdlFiles(const FileDetail & ownerFileDetail,FileDetailMap & allFileDetails,std::set<std::string> & sourceFiles)217 bool Preprocessor::LoadOtherIdlFiles(
218 const FileDetail &ownerFileDetail, FileDetailMap &allFileDetails, std::set<std::string> &sourceFiles)
219 {
220 for (const auto &importName : ownerFileDetail.imports_) {
221 if (allFileDetails.find(importName) != allFileDetails.end()) {
222 continue;
223 }
224
225 std::string otherFilePath = Options::GetInstance().GetImportFilePath(importName);
226 if (otherFilePath.empty()) {
227 Logger::E(TAG, "importName:%s, is failed", importName.c_str());
228 return false;
229 }
230
231 sourceFiles.insert(otherFilePath);
232 }
233 return true;
234 }
235
CheckCircularReference(const FileDetailMap & allFileDetails,std::vector<FileDetail> & compileSourceFiles)236 bool Preprocessor::CheckCircularReference(const FileDetailMap &allFileDetails,
237 std::vector<FileDetail> &compileSourceFiles)
238 {
239 FileDetailMap allFileDetailsTemp = allFileDetails;
240 std::queue<FileDetail> fileQueue;
241 for (const auto &filePair : allFileDetailsTemp) {
242 const FileDetail &file = filePair.second;
243 if (file.imports_.size() == 0) {
244 fileQueue.push(file);
245 }
246 }
247
248 compileSourceFiles.clear();
249 while (!fileQueue.empty()) {
250 FileDetail curFile = fileQueue.front();
251 fileQueue.pop();
252 compileSourceFiles.push_back(allFileDetails.at(curFile.GetFullName()));
253
254 for (auto &filePair : allFileDetailsTemp) {
255 FileDetail &otherFile = filePair.second;
256 if (otherFile.imports_.empty()) {
257 continue;
258 }
259
260 auto position = otherFile.imports_.find(curFile.GetFullName());
261 if (position != otherFile.imports_.end()) {
262 otherFile.imports_.erase(position);
263 }
264
265 if (otherFile.imports_.size() == 0) {
266 fileQueue.push(otherFile);
267 }
268 }
269 }
270
271 if (compileSourceFiles.size() == allFileDetailsTemp.size()) {
272 return true;
273 }
274
275 PrintCyclefInfo(allFileDetailsTemp);
276 return false;
277 }
278
PrintCyclefInfo(FileDetailMap & allFileDetails)279 void Preprocessor::PrintCyclefInfo(FileDetailMap &allFileDetails)
280 {
281 for (FileDetailMap::iterator it = allFileDetails.begin(); it != allFileDetails.end();) {
282 if (it->second.imports_.size() == 0) {
283 it = allFileDetails.erase(it);
284 } else {
285 ++it;
286 }
287 }
288
289 for (const auto &filePair : allFileDetails) {
290 std::vector<std::string> traceNodes;
291 FindCycle(filePair.second.GetFullName(), allFileDetails, traceNodes);
292 }
293 }
294
FindCycle(const std::string & curNode,FileDetailMap & allFiles,std::vector<std::string> & trace)295 void Preprocessor::FindCycle(const std::string &curNode, FileDetailMap &allFiles, std::vector<std::string> &trace)
296 {
297 auto iter = std::find_if(trace.begin(), trace.end(), [curNode](const std::string &name) {
298 return name == curNode;
299 });
300 if (iter != trace.end()) {
301 if (iter == trace.begin()) {
302 // print circular reference infomation
303 StringBuilder sb;
304 for (const auto &nodeName : trace) {
305 sb.AppendFormat("%s -> ", nodeName.c_str());
306 }
307 sb.AppendFormat("%s", curNode.c_str());
308 Logger::E(TAG, "error: there are circular reference:\n%s", sb.ToString().c_str());
309 }
310 return;
311 }
312
313 trace.push_back(curNode);
314 for (const auto &importFileName : allFiles[curNode].imports_) {
315 FindCycle(importFileName, allFiles, trace);
316 }
317
318 trace.pop_back();
319 }
320
321 /*
322 * filePath: ./ohos/interface/foo/v1_0/IFoo.idl
323 * package ohos.hdi.foo.v1_0;
324 */
CheckPackageName(const std::string & filePath,const std::string & packageName)325 bool Preprocessor::CheckPackageName(const std::string &filePath, const std::string &packageName)
326 {
327 std::string pkgToPath = Options::GetInstance().GetPackagePath(packageName);
328
329 size_t index = filePath.rfind(SEPARATOR);
330 if (index == std::string::npos) {
331 return false;
332 }
333
334 std::string parentDir = filePath.substr(0, index);
335 return parentDir == pkgToPath;
336 }
337 } // namespace HDI
338 } // namespace OHOS