* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "preprocessor/preprocessor.h"
#include <algorithm>
#include <queue>
#include "util/common.h"
#include "util/file.h"
#include "util/logger.h"
#include "util/options.h"
#include "util/string_builder.h"
namespace OHOS {
namespace Idl {
std::string FileDetail::Dump() const
{
StringBuilder sb;
sb.AppendFormat("filePath:%s\n", filePath_.c_str());
sb.AppendFormat("fileName:%s\n", fileName_.c_str());
sb.AppendFormat("packageName:%s\n", packageName_.c_str());
if (imports_.empty()) {
sb.Append("import:[]\n");
} else {
sb.Append("import:[\n");
for (const auto &importStr : imports_) {
sb.AppendFormat("%s,\n", importStr.c_str());
}
sb.Append("]\n");
}
return sb.ToString();
}
bool Preprocessor::Preprocess(std::vector<FileDetail> &fileDetails)
{
std::set<std::string> sourceFiles = Options::GetInstance().GetSourceFiles();
if (!CheckAllFilesPath(sourceFiles)) {
return false;
}
FileDetailMap allFileDetails;
if (!AnalyseImportInfo(sourceFiles, allFileDetails)) {
return false;
}
if (!CheckCircularReference(allFileDetails, fileDetails)) {
return false;
}
return true;
}
bool Preprocessor::UnitPreprocess(FileDetailMap &fileDetails)
{
std::set<std::string> sourceFiles = Options::GetInstance().GetSourceFiles();
if (!CheckAllFilesPath(sourceFiles)) {
return false;
}
for (const auto &sourceFile : sourceFiles) {
FileDetail info;
if (!ParseFileDetail(sourceFile, info)) {
return false;
}
fileDetails[info.GetFullName()] = info;
}
return true;
}
bool Preprocessor::CheckAllFilesPath(const std::set<std::string> &sourceFiles)
{
if (sourceFiles.empty()) {
Logger::E(TAG, "no source files");
return false;
}
bool ret = true;
for (const auto &filePath : sourceFiles) {
if (!File::CheckValid(filePath)) {
Logger::E(TAG, "invailed file path '%s'.", filePath.c_str());
ret = false;
}
}
return ret;
}
bool Preprocessor::AnalyseImportInfo(std::set<std::string> sourceFiles, FileDetailMap &allFileDetails)
{
std::set<std::string> processSource(sourceFiles);
while (!processSource.empty()) {
auto fileIter = processSource.begin();
FileDetail info;
if (!ParseFileDetail(*fileIter, info)) {
return false;
}
processSource.erase(fileIter);
allFileDetails[info.GetFullName()] = info;
if (!LoadOtherIdlFiles(info, allFileDetails, processSource)) {
Logger::E(TAG, "failed to load other by %s", info.GetFullName().c_str());
return false;
}
}
return true;
}
bool Preprocessor::ParseFileDetail(const std::string &sourceFile, FileDetail &info)
{
Lexer lexer;
if (!lexer.Reset(sourceFile)) {
Logger::E(TAG, "failed to open file '%s'.", sourceFile.c_str());
return false;
}
info.filePath_ = lexer.GetFilePath();
size_t startIndex = info.filePath_.rfind(SEPARATOR);
size_t endIndex = info.filePath_.rfind(".idl");
if (startIndex == std::string::npos || endIndex == std::string::npos || (startIndex >= endIndex)) {
Logger::E(TAG, "failed to get file name from '%s'.", info.filePath_.c_str());
return false;
}
info.fileName_ = StringHelper::SubStr(info.filePath_, startIndex + 1, endIndex);
if (!ParsePackage(lexer, info)) {
return false;
}
if (!ParseImports(lexer, info)) {
return false;
}
return true;
}
bool Preprocessor::ParsePackage(Lexer &lexer, FileDetail &info)
{
InterfaceType interfaceType = Options::GetInstance().GetInterfaceType();
if (interfaceType == InterfaceType::HDI ||
interfaceType == InterfaceType::SM ||
interfaceType == InterfaceType::SAM ||
interfaceType == InterfaceType::SAM_SM ||
interfaceType == InterfaceType::SAM_UDS ||
interfaceType == InterfaceType::SM_UDS) {
Token token = lexer.PeekToken();
if (token.kind != TokenType::PACKAGE) {
Logger::E(TAG, "%s: expected 'package' before '%s' token", LocInfo(token).c_str(), token.value.c_str());
return false;
}
lexer.GetToken();
token = lexer.PeekToken();
if (token.kind != TokenType::ID) {
Logger::E(TAG, "%s: expected package name before '%s' token", LocInfo(token).c_str(), token.value.c_str());
return false;
}
if (!CheckPackageName(info.filePath_, token.value)) {
Logger::E(TAG, "%s:package name '%s' does not match file path '%s'", LocInfo(token).c_str(),
token.value.c_str(), info.filePath_.c_str());
return false;
}
info.packageName_ = token.value;
lexer.GetToken();
token = lexer.PeekToken();
if (token.kind != TokenType::SEMICOLON) {
Logger::E(TAG, "%s:expected ';' before '%s' token", LocInfo(token).c_str(), token.value.c_str());
return false;
}
lexer.GetToken();
}
return true;
}
bool Preprocessor::ParseImports(Lexer &lexer, FileDetail &info)
{
Options &option = Options::GetInstance();
Token token = lexer.PeekToken();
while (token.kind != TokenType::END_OF_FILE) {
if (token.kind != TokenType::IMPORT) {
lexer.GetToken();
token = lexer.PeekToken();
continue;
}
lexer.GetToken();
token = lexer.PeekToken();
if (token.kind != TokenType::ID) {
Logger::E(TAG, "%s: expected import name before '%s' token", LocInfo(token).c_str(), token.value.c_str());
return false;
}
if (option.GetInterfaceType() == InterfaceType::SA && option.GetLanguage() == Language::CPP) {
#ifdef __MINGW32__
std::replace(token.value.begin(), token.value.end(), '/', '\\');
#endif
}
if (!File::CheckValid(option.GetImportFilePath(token.value, info.filePath_))) {
Logger::E(TAG, "%s: import invalid package '%s'", LocInfo(token).c_str(), token.value.c_str());
return false;
}
if (option.GetInterfaceType() == InterfaceType::SA && option.GetLanguage() == Language::CPP) {
info.imports_.emplace(option.GetImportFilePath(token.value, info.filePath_));
} else {
info.imports_.emplace(token.value);
}
lexer.GetToken();
token = lexer.PeekToken();
if (token.kind != TokenType::SEMICOLON) {
Logger::E(TAG, "%s:expected ';' before '%s' token", LocInfo(token).c_str(), token.value.c_str());
return false;
}
lexer.GetToken();
token = lexer.PeekToken();
}
return true;
}
bool Preprocessor::LoadOtherIdlFiles(
const FileDetail &ownerFileDetail, FileDetailMap &allFileDetails, std::set<std::string> &sourceFiles)
{
Options &option = Options::GetInstance();
for (const auto &importName : ownerFileDetail.imports_) {
if (allFileDetails.find(importName) != allFileDetails.end()) {
continue;
}
std::string otherFilePath;
if (option.GetInterfaceType() == InterfaceType::SA && option.GetLanguage() == Language::CPP) {
otherFilePath = importName;
} else {
otherFilePath = Options::GetInstance().GetImportFilePath(importName, ownerFileDetail.filePath_);
}
if (otherFilePath.empty()) {
Logger::E(TAG, "importName:%s, is failed", importName.c_str());
return false;
}
sourceFiles.insert(otherFilePath);
}
return true;
}
bool Preprocessor::CheckCircularReference(const FileDetailMap &allFileDetails,
std::vector<FileDetail> &compileSourceFiles)
{
FileDetailMap allFileDetailsTemp = allFileDetails;
std::queue<FileDetail> fileQueue;
for (const auto &filePair : allFileDetailsTemp) {
const FileDetail &file = filePair.second;
if (file.imports_.size() == 0) {
fileQueue.push(file);
}
}
compileSourceFiles.clear();
while (!fileQueue.empty()) {
FileDetail curFile = fileQueue.front();
fileQueue.pop();
compileSourceFiles.push_back(allFileDetails.at(curFile.GetFullName()));
for (auto &filePair : allFileDetailsTemp) {
FileDetail &otherFile = filePair.second;
if (otherFile.imports_.empty()) {
continue;
}
auto position = otherFile.imports_.find(curFile.GetFullName());
if (position != otherFile.imports_.end()) {
otherFile.imports_.erase(position);
}
if (otherFile.imports_.size() == 0) {
fileQueue.push(otherFile);
}
}
}
if (compileSourceFiles.size() == allFileDetailsTemp.size()) {
return true;
}
PrintCyclefInfo(allFileDetailsTemp);
return false;
}
void Preprocessor::PrintCyclefInfo(FileDetailMap &allFileDetails)
{
for (FileDetailMap::iterator it = allFileDetails.begin(); it != allFileDetails.end();) {
if (it->second.imports_.size() == 0) {
it = allFileDetails.erase(it);
} else {
++it;
}
}
for (const auto &filePair : allFileDetails) {
std::vector<std::string> traceNodes;
FindCycle(filePair.second.GetFullName(), allFileDetails, traceNodes);
}
}
void Preprocessor::FindCycle(const std::string &curNode, FileDetailMap &allFiles, std::vector<std::string> &trace)
{
auto iter = std::find_if(trace.begin(), trace.end(), [curNode](const std::string &name) {
return name == curNode;
});
if (iter != trace.end()) {
if (iter == trace.begin()) {
StringBuilder sb;
for (const auto &nodeName : trace) {
sb.AppendFormat("%s -> ", nodeName.c_str());
}
sb.AppendFormat("%s", curNode.c_str());
Logger::E(TAG, "error: there are circular reference:\n%s", sb.ToString().c_str());
}
return;
}
trace.push_back(curNode);
for (const auto &importFileName : allFiles[curNode].imports_) {
FindCycle(importFileName, allFiles, trace);
}
trace.pop_back();
}
* filePath: ./ohos/interface/foo/v1_0/IFoo.idl
* package ohos.hdi.foo.v1_0;
*/
bool Preprocessor::CheckPackageName(const std::string &filePath, const std::string &packageName)
{
std::string pkgToPath = Options::GetInstance().GetPackagePath(packageName);
size_t index = filePath.rfind(SEPARATOR);
if (index == std::string::npos) {
return false;
}
std::string parentDir = filePath.substr(0, index);
return parentDir == pkgToPath;
}
}
}