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