1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "clangparser.h"
6#include "clangcursor.h"
7
8#include <QDir>
9#include <QDebug>
10#include <clang-c/Index.h>
11#include <linux/limits.h>
12
13using Dict = QMap<QString, QStringList>;
14
15CXChildVisitResult cursorVisitor(CXCursor _cursor, CXCursor _parent, CXClientData client_data);
16
17static QString kLinesep = "\n";
18static QString kRootName = "Cxx";
19
20bool ClangParser::parse(const QString &workSpace, const QString &symbolLocation, const QString &language)
21{
22 QDir dir(workSpace);
23
24 if (!dir.exists()) {
25 qWarning() << "Error: " << dir << " does not exist";
26 return false;
27 }
28 // only support c++ now.
29 if (language == "C/C++") {
30 QStringList filters;
31 filters << "cpp" << "h" << "hpp" << "cxx" << "hxx";
32 QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
33
34 for (auto entry : entries) {
35 if (entry.isFile()) {
36 if (filters.contains(entry.suffix())) {
37 QString cxxSymbolDir = symbolLocation + "/" + kRootName;
38 if (!QDir().exists(cxxSymbolDir)) {
39 QDir().mkdir(cxxSymbolDir);
40 }
41 parseSingleFile(entry.absoluteFilePath(), cxxSymbolDir);
42 }
43 } else {
44 parse(entry.absoluteFilePath(), symbolLocation, language);
45 }
46 }
47 }
48 return true;
49}
50
51bool ClangParser::parseSingleFile(const QString &filePath, const QString &symbolLocation)
52{
53 // Create an index object
54 CXIndex index = clang_createIndex(1, 0);
55
56 // Parse the source code
57 auto stdString = filePath.toStdString();
58 CXTranslationUnit translationUnit = clang_parseTranslationUnit(index, stdString.c_str(), nullptr,
59 0, nullptr, 0,
60 CXTranslationUnit_None);
61
62 // Retrieve the created AST
63 CXCursor rootCursor = clang_getTranslationUnitCursor(translationUnit);
64
65 stdString = symbolLocation.toStdString();
66 char *storagePtr = const_cast<char *>(stdString.c_str());
67 // Visit all nodes of the AST
68 clang_visitChildren(rootCursor, *cursorVisitor, storagePtr);
69
70 // Release all resources used
71 clang_disposeTranslationUnit(translationUnit);
72 clang_disposeIndex(index);
73
74 return true;
75}
76
77Dict declKindFilter()
78{
79 return {
80 {"TranslationUnit", {"Namespace", "StructDecl", "ClassDecl", "UnionDecl", "TypdefDecl", "VarDecl", "FunctionDecl"}},
81 {"StructDecl", {"StructDecl", "FieldDecl", "CxxMethod", "Constructor", "Destructor"}},
82 {"UnionDecl", {"FieldDecl", "StructDecl", "FunctionDecl"}},
83 {"ClassDecl", {"ClassDecl", "UnionDecl", "StructDecl", "TypedefDecl", "FieldDecl", "CxxMethod", "Constructor", "Destructor"}},
84 {"Namespace", {"Namespace", "ClassDecl", "UnionDecl", "StructDecl", "TypedefDecl", "FieldDecl", "VarDecl", "CxxMethod", "FunctionDecl"}}
85 };
86}
87
88Dict defKindFilter()
89{
90 return {
91 {"ClassDecl", {"CompoundStmt"}},
92 {"StructDecl", {"CompoundStmt"}},
93 {"CxxMethod", {"CompoundStmt"}},
94 {"Destructor", {"CompoundStmt"}},
95 {"Constructor", {"CompoundStmt"}}
96 };
97}
98
99Dict refKindFilter()
100{
101 return {
102 {"Destructor", {"VarDecl"}},
103 {"Constructor", {"VarDecl"}},
104 {"CxxMethod", {"VarDecl"}},
105 {"ClassDecl", {"FieldDecl"}},
106 {"StructDecl", {"FieldDecl"}}
107 };
108}
109
110QString record()
111{
112 return ".record";
113}
114
115QString declared()
116{
117 return ".declared";
118}
119
120QString definitions()
121{
122 return ".definitions";
123}
124
125QString reference()
126{
127 return ".reference";
128}
129
130QString getCursorDirName(const ClangCursor &cursor)
131{
132 QString findName = cursor.displayName();
133 QString cursorType = cursor.typeSpelling();
134 QString cursorKindName = cursor.kindName();
135 QString cursorSpelling = cursor.spelling();
136 if (cursorKindName == "FieldDecl") {
137 findName = cursorType + " " + cursorSpelling;
138 } else if (cursorKindName == "CxxMethod") {
139 findName = cursorType + " " + cursorSpelling;
140 } else if (cursorKindName == "Namespace") {
141 findName = "Namespace " + cursorSpelling;
142 } else if (cursorKindName == "StructDecl") {
143 findName = "struct " + cursorSpelling;
144 } else if (cursorKindName == "ClassDecl") {
145 findName = "class " + cursorSpelling;
146 } else if (cursorKindName == "UnionDecl") {
147 findName = "union " + cursorSpelling;
148 } else if (cursorKindName == "VarDecl") {
149 findName = cursorType + " " + cursorSpelling;
150 } else {
151 // nothing to do
152 }
153
154 QString anonymous = "(anonymous)";
155 if (findName.contains(anonymous)) {
156 QStringList items = findName.split(anonymous);
157 findName = items.join("");
158 } else {
159 findName = findName.replace("/", " ");
160 }
161 return findName;
162}
163
164QString getCursorDirPath(const QString &storage, const ClangCursor &cursor)
165{
166 if (cursor.isValid()) {
167 QString suffixPath = "/" + getCursorDirName(cursor);
168 auto parentCursor = cursor.sematicParent();
169 QString parentKindName = parentCursor.kindName();
170 while (parentKindName != "TranslationUnit") {
171 suffixPath = "/" + getCursorDirName(parentCursor) + suffixPath;
172 parentCursor = parentCursor.sematicParent();
173 parentKindName = parentCursor.kindName();
174 }
175 return storage + suffixPath;
176 }
177 return storage;
178}
179
180void writeRecordFile(const QString &cursor_map_path, const ClangCursor &cursor)
181{
182 if (QDir().exists(cursor_map_path)) {
183 if (cursor.typeKindName() == "Record") {
184 QString recodeFile = cursor_map_path + "/" + record();
185 QString writeLine = "kind.name=" + cursor.kindName() + "\r\n" \
186 + "displayname=" + cursor.displayName();
187 QFile file(recodeFile);
188 file.open(QFile::ReadWrite);
189 file.write(writeLine.toUtf8());
190 file.close();
191 }
192 }
193}
194
195bool fileHasLine(const QString &_file, const QString &line)
196{
197 bool hasLine = false;
198 if (QFile::exists(_file)) {
199 QFile file(_file);
200 file.open(QFile::ReadOnly);
201 while (file.canReadLine()) {
202 QString rLine = file.readLine();
203 if (rLine == line) {
204 hasLine = true;
205 break;
206 }
207 }
208 file.close();
209 }
210 return hasLine;
211}
212
213void writeDeclaredFile(const QString &cursorMapPath, const ClangCursor &cursor)
214{
215 if (!QDir().exists(cursorMapPath))
216 return;
217
218 QString declaredFile = cursorMapPath + "/" + declared();
219 QString file;
220 uint line = 0;
221 uint column = 0;
222 cursor.location(file, line, column);
223 QString writeLine = file + ':' + \
224 QString::number(line) + ':' + \
225 QString::number(column) + kLinesep;
226
227 if (!fileHasLine(declaredFile, writeLine)) {
228 QFile file(declaredFile);
229 file.open(QFile::Append);
230 file.write(writeLine.toUtf8());
231 file.close();
232 }
233}
234
235void writeDefinitionsFile(const QString &cursorMapPath, const ClangCursor &cursor)
236{
237 if (!QDir().exists(cursorMapPath))
238 return;
239
240 QString defFile = cursorMapPath + "/" + definitions();
241 QString file;
242 uint line = 0;
243 uint column = 0;
244 cursor.location(file, line, column);
245 QString writeLine = file + ':' + \
246 QString::number(line) + ':' + \
247 QString::number(column) + kLinesep;
248
249 if (!fileHasLine(defFile, writeLine)) {
250 QFile file(defFile);
251 file.open(QFile::Append);
252 file.write(writeLine.toUtf8());
253 file.close();
254 }
255}
256
257void writeReferenceFile(const QString &cursorMapPath, const ClangCursor &cursor)
258{
259 if (!QDir().exists(cursorMapPath))
260 return;
261 QString refFile = cursorMapPath + "/" + reference();
262 QString file;
263 uint line = 0;
264 uint column = 0;
265 cursor.location(file, line, column);
266 QString write_line = file + ':' + \
267 QString::number(line) + ':' + \
268 QString::number(column) + kLinesep;
269
270 if (!fileHasLine(refFile, write_line)) {
271 QFile file(refFile);
272 file.open(QFile::Append);
273 file.write(write_line.toUtf8());
274 file.close();
275 }
276}
277
278QString findRecordMapPath(const QString &storage, const QString &recodeDisplayName)
279{
280 QDir dir(storage);
281 QStringList nameFilters;
282 nameFilters << record();
283 QStringList files = dir.entryList(nameFilters, QDir::Files | QDir::Readable);
284
285 for (auto f : files) {
286 QFile file(f);
287 file.open(QFile::ReadOnly);
288 while (file.canReadLine()) {
289 QString line = file.readLine();
290 if (line.split("=").contains(recodeDisplayName))
291 return storage;
292 }
293 file.close();
294 }
295 return "";
296}
297
298bool visitDeclKind(const QString &storage, const ClangCursor &cursor)
299{
300 QString cursorMapPath = getCursorDirPath(storage, cursor);
301 QStringList cursorMapPathList = cursorMapPath.split("/");
302 for (auto item : cursorMapPathList) {
303 if (item.size() > NAME_MAX)
304 return false;
305 }
306 cursorMapPathList.removeLast();
307 QString cursor_parent_map_path = cursorMapPathList.join("/");
308 if (!QDir(cursorMapPath).exists() && QDir(cursor_parent_map_path).exists()) {
309 QDir().mkdir(cursorMapPath);
310 }
311 writeRecordFile(cursorMapPath, cursor);
312
313 QString cursorFile;
314 uint line = 0;
315 uint column = 0;
316 cursor.location(cursorFile, line, column);
317 QString translationUnitFile = cursor.translationUnitSpelling();
318 if (translationUnitFile == cursorFile) {
319 writeDeclaredFile(cursorMapPath, cursor);
320 }
321 return true;
322}
323
324void visitDefKind(const QString &storage, const ClangCursor &parent)
325{
326 QString parentCursorMapPath = getCursorDirPath(storage, parent);
327 writeDefinitionsFile(parentCursorMapPath, parent);
328}
329
330void visitRefKind(const QString &storage, const ClangCursor &cursor)
331{
332 if (cursor.kindName() == "Record") {
333 QString refMapPath = findRecordMapPath(storage, cursor.typeSpelling());
334 if (!refMapPath.isEmpty())
335 writeReferenceFile(refMapPath, cursor);
336 }
337}
338
339CXChildVisitResult cursorVisitor(CXCursor _cursor, CXCursor _parent, CXClientData client_data)
340{
341 char *storage = static_cast<char *>(client_data);
342 ClangCursor cursor(_cursor);
343 ClangCursor parent(_parent);
344 QString kindName = cursor.kindName();
345 QString spellName = cursor.spelling();
346
347 auto declKindFilters = declKindFilter();
348 auto defKindFilters = defKindFilter();
349 auto refKindFilters = refKindFilter();
350
351 QString parentKindName = parent.kindName();
352
353 if (declKindFilters.keys().contains(parentKindName) && declKindFilters[parentKindName].contains(kindName)) {
354 if(!visitDeclKind(storage, cursor))
355 return CXChildVisit_Recurse;
356 }
357
358 if (defKindFilters.keys().contains(parentKindName) && defKindFilters[parentKindName].contains(kindName)) {
359 visitDefKind(storage, parent);
360 }
361
362 if (refKindFilters.keys().contains(parentKindName) && refKindFilters[parentKindName].contains(kindName)) {
363 visitRefKind(storage, cursor);
364 }
365 return CXChildVisit_Recurse;
366}
367