Merge "Added libprotobuf to extract and dump ABI." am: 7272d52f76
am: 4333aaad52
Change-Id: Icdea553f8b04f39f48cbdd70e69a25a2fc41a7e8
This commit is contained in:
@@ -25,7 +25,14 @@ cc_defaults {
|
|||||||
"-Wall",
|
"-Wall",
|
||||||
"-Werror",
|
"-Werror",
|
||||||
"-std=c++11",
|
"-std=c++11",
|
||||||
|
"-DGOOGLE_PROTOBUF_NO_RTTI",
|
||||||
],
|
],
|
||||||
|
|
||||||
|
target: {
|
||||||
|
windows: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cc_defaults {
|
cc_defaults {
|
||||||
@@ -34,6 +41,7 @@ cc_defaults {
|
|||||||
shared_libs: [
|
shared_libs: [
|
||||||
"libclang",
|
"libclang",
|
||||||
"libLLVM",
|
"libLLVM",
|
||||||
|
"libprotobuf-cpp-full",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +50,7 @@ cc_defaults {
|
|||||||
|
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"libclangTooling",
|
"libclangTooling",
|
||||||
|
"libclangToolingCore",
|
||||||
"libclangFrontendTool",
|
"libclangFrontendTool",
|
||||||
"libclangFrontend",
|
"libclangFrontend",
|
||||||
"libclangDriver",
|
"libclangDriver",
|
||||||
@@ -74,6 +83,30 @@ cc_defaults {
|
|||||||
"libLLVMMCDisassembler",
|
"libLLVMMCDisassembler",
|
||||||
"libLLVMSupport",
|
"libLLVMSupport",
|
||||||
],
|
],
|
||||||
|
|
||||||
|
shared_libs: [
|
||||||
|
"libprotobuf-cpp-full",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_library_static {
|
||||||
|
name: "libheader-checker-proto",
|
||||||
|
host_supported: true,
|
||||||
|
export_include_dirs: ["."],
|
||||||
|
|
||||||
|
srcs: [
|
||||||
|
"proto/abi_dump.proto",
|
||||||
|
],
|
||||||
|
|
||||||
|
proto: {
|
||||||
|
export_proto_headers: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
cflags: [
|
||||||
|
"-Wcast-qual",
|
||||||
|
"-Wno-long-long",
|
||||||
|
"-Wno-unused-parameter",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
cc_binary_host {
|
cc_binary_host {
|
||||||
@@ -84,20 +117,15 @@ cc_binary_host {
|
|||||||
"header-checker-lib-debug-defaults",
|
"header-checker-lib-debug-defaults",
|
||||||
],
|
],
|
||||||
|
|
||||||
srcs: ["header-abi-dumper/src/*.cpp"],
|
srcs: [
|
||||||
|
"header-abi-dumper/src/*.cpp",
|
||||||
|
],
|
||||||
|
|
||||||
|
static_libs: [
|
||||||
|
"libheader-checker-proto",
|
||||||
|
],
|
||||||
|
|
||||||
target: {
|
target: {
|
||||||
windows: {
|
|
||||||
host_ldlibs: [
|
|
||||||
"-limagehlp",
|
|
||||||
"-lole32",
|
|
||||||
"-lversion",
|
|
||||||
],
|
|
||||||
cflags: [
|
|
||||||
// Skip missing-field-initializer warnings for mingw.
|
|
||||||
"-Wno-error=missing-field-initializers",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
linux: {
|
linux: {
|
||||||
host_ldlibs: [
|
host_ldlibs: [
|
||||||
"-ldl",
|
"-ldl",
|
||||||
@@ -111,10 +139,4 @@ cc_binary_host {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
product_variables: {
|
|
||||||
unbundled_build: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,154 @@
|
|||||||
|
// Copyright (C) 2016 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// 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 "ast_processing.h"
|
#include "ast_processing.h"
|
||||||
|
|
||||||
bool HeaderASTVisitor::VisitRecordDecl(const clang::RecordDecl *decl) {
|
#include <clang/Lex/Token.h>
|
||||||
llvm::errs() << "struct: " << decl->getName() << "\n";
|
#include <clang/Tooling/Core/QualTypeNames.h>
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HeaderASTVisitor::VisitCXXRecordDecl(const clang::CXXRecordDecl *decl) {
|
#include <google/protobuf/text_format.h>
|
||||||
llvm::errs() << "class: " << decl->getName() << "\n";
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
HeaderASTVisitor::HeaderASTVisitor(
|
||||||
|
abi_dump::TranslationUnit *tu_ptr,
|
||||||
|
clang::MangleContext *mangle_contextp,
|
||||||
|
const clang::ASTContext *ast_contextp,
|
||||||
|
const clang::CompilerInstance *compiler_instance_p)
|
||||||
|
: tu_ptr_(tu_ptr),
|
||||||
|
mangle_contextp_(mangle_contextp),
|
||||||
|
ast_contextp_(ast_contextp),
|
||||||
|
cip_(compiler_instance_p) { }
|
||||||
|
|
||||||
|
bool HeaderASTVisitor::VisitRecordDecl(const clang::RecordDecl *decl) {
|
||||||
|
abi_dump::RecordDecl *record_decl = tu_ptr_->add_classes();
|
||||||
|
SetupClassFields(record_decl, decl);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HeaderASTVisitor::VisitFunctionDecl(const clang::FunctionDecl *decl) {
|
bool HeaderASTVisitor::VisitFunctionDecl(const clang::FunctionDecl *decl) {
|
||||||
llvm::errs() << "func: " << decl->getName() << "\n";
|
abi_dump::FunctionDecl *function_decl = tu_ptr_->add_functions();
|
||||||
|
// FIXME: Use return value.
|
||||||
|
SetupFunction(function_decl, decl);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HeaderASTConsumer::HeaderASTConsumer(
|
||||||
|
const std::string &file_name,
|
||||||
|
clang::CompilerInstance *compiler_instancep,
|
||||||
|
const std::string &out_dump_name)
|
||||||
|
: file_name_(file_name),
|
||||||
|
cip_(compiler_instancep),
|
||||||
|
out_dump_name_(out_dump_name) { }
|
||||||
|
|
||||||
void HeaderASTConsumer::HandleTranslationUnit(clang::ASTContext &ctx) {
|
void HeaderASTConsumer::HandleTranslationUnit(clang::ASTContext &ctx) {
|
||||||
llvm::errs() << "HandleTranslationUnit ------------------------------\n";
|
|
||||||
clang::TranslationUnitDecl* translation_unit = ctx.getTranslationUnitDecl();
|
clang::TranslationUnitDecl* translation_unit = ctx.getTranslationUnitDecl();
|
||||||
HeaderASTVisitor v;
|
std::unique_ptr<clang::MangleContext> mangle_contextp(
|
||||||
|
ctx.createMangleContext());
|
||||||
|
abi_dump::TranslationUnit tu;
|
||||||
|
HeaderASTVisitor v(&tu, mangle_contextp.get(), &ctx, cip_);
|
||||||
v.TraverseDecl(translation_unit);
|
v.TraverseDecl(translation_unit);
|
||||||
|
std::ofstream text_output(out_dump_name_ + ".txt");
|
||||||
|
std::fstream binary_output(
|
||||||
|
(out_dump_name_).c_str(),
|
||||||
|
std::ios::out | std::ios::trunc | std::ios::binary);
|
||||||
|
std::string str_out;
|
||||||
|
google::protobuf::TextFormat::PrintToString(tu, &str_out);
|
||||||
|
text_output << str_out;
|
||||||
|
if (!tu.SerializeToOstream(&binary_output)) {
|
||||||
|
llvm::errs() << "Serialization to ostream failed\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeaderASTConsumer::HandleVTable(clang::CXXRecordDecl *crd) {
|
void HeaderASTConsumer::HandleVTable(clang::CXXRecordDecl *crd) {
|
||||||
llvm::errs() << "HandleVTable: " << crd->getName() << "\n";
|
llvm::errs() << "HandleVTable: " << crd->getName() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
llvm::StringRef HeaderASTPPCallbacks::ToString(const clang::Token &tok) {
|
std::string HeaderASTVisitor::GetDeclSourceFile(const clang::NamedDecl *decl) {
|
||||||
return tok.getIdentifierInfo()->getName();
|
clang::SourceManager &SM = cip_->getSourceManager();
|
||||||
|
clang::SourceLocation location = decl->getLocation();
|
||||||
|
llvm::StringRef file_name= SM.getFilename(location);
|
||||||
|
return file_name.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string HeaderASTVisitor::GetMangledNameDecl(const clang::NamedDecl *decl) {
|
||||||
|
std::string mangled_or_demangled_name = decl->getName();
|
||||||
|
if (mangle_contextp_->shouldMangleDeclName(decl)) {
|
||||||
|
llvm::raw_string_ostream ostream(mangled_or_demangled_name);
|
||||||
|
mangle_contextp_->mangleName(decl, ostream);
|
||||||
|
ostream.flush();
|
||||||
|
}
|
||||||
|
return mangled_or_demangled_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HeaderASTVisitor::SetupFunction(abi_dump::FunctionDecl *functionp,
|
||||||
|
const clang::FunctionDecl *decl) {
|
||||||
|
// Go through all the parameters in the method and add them to the fields.
|
||||||
|
// Also get the fully qualfied name and mangled name and store them.
|
||||||
|
functionp->set_function_name(decl->getQualifiedNameAsString());
|
||||||
|
functionp->set_mangled_function_name(GetMangledNameDecl(decl));
|
||||||
|
functionp->set_source_file(GetDeclSourceFile(decl));
|
||||||
|
clang::QualType return_type =
|
||||||
|
decl->getReturnType().getDesugaredType(*ast_contextp_);
|
||||||
|
functionp->set_return_type(
|
||||||
|
clang::TypeName::getFullyQualifiedName(return_type, *ast_contextp_));
|
||||||
|
clang::FunctionDecl::param_const_iterator param_it = decl->param_begin();
|
||||||
|
while (param_it != decl->param_end()) {
|
||||||
|
abi_dump::FieldDecl *function_fieldp = functionp->add_parameters();
|
||||||
|
if (!function_fieldp) {
|
||||||
|
llvm::errs() << "Couldn't add parameter to method. Aborting";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function_fieldp->set_field_name((*param_it)->getName());
|
||||||
|
clang::QualType field_type =
|
||||||
|
(*param_it)->getType().getDesugaredType(*ast_contextp_);
|
||||||
|
|
||||||
|
function_fieldp->set_field_type(
|
||||||
|
clang::TypeName::getFullyQualifiedName(field_type, *ast_contextp_));
|
||||||
|
|
||||||
|
param_it++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HeaderASTVisitor::SetupClassFields(abi_dump::RecordDecl *classp,
|
||||||
|
const clang::RecordDecl *decl) {
|
||||||
|
classp->set_fully_qualified_name(decl->getQualifiedNameAsString());
|
||||||
|
classp->set_source_file(GetDeclSourceFile(decl));
|
||||||
|
classp->set_entity_type("class");
|
||||||
|
clang::RecordDecl::field_iterator field = decl->field_begin();
|
||||||
|
while (field != decl->field_end()) {
|
||||||
|
abi_dump::FieldDecl *class_fieldp = classp->add_fields();
|
||||||
|
if (!class_fieldp) {
|
||||||
|
llvm::errs() << " Couldn't add class field: " << field->getName()
|
||||||
|
<< " to reference dump\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
class_fieldp->set_field_name(field->getName());
|
||||||
|
//FIXME: This needs to change. Resolve typedef, class name, built-in etc.
|
||||||
|
clang::QualType field_type =
|
||||||
|
field->getType().getDesugaredType(*ast_contextp_);
|
||||||
|
class_fieldp->set_field_type(
|
||||||
|
clang::TypeName::getFullyQualifiedName(field_type, *ast_contextp_));
|
||||||
|
field++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HeaderASTPPCallbacks::MacroDefined(const clang::Token ¯o_name_tok,
|
void HeaderASTPPCallbacks::MacroDefined(const clang::Token ¯o_name_tok,
|
||||||
const clang::MacroDirective *) {
|
const clang::MacroDirective *) {
|
||||||
assert(macro_name_tok.isAnyIdentifier());
|
assert(macro_name_tok.isAnyIdentifier());
|
||||||
llvm::errs() << "defines: " << ToString(macro_name_tok) << "\n";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,83 @@
|
|||||||
|
// Copyright (C) 2016 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef AST_PROCESSING_H_
|
||||||
|
#define AST_PROCESSING_H_
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunused-parameter"
|
||||||
|
#pragma clang diagnostic ignored "-Wnested-anon-types"
|
||||||
|
#include "proto/abi_dump.pb.h"
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
#include <clang/AST/AST.h>
|
#include <clang/AST/AST.h>
|
||||||
#include <clang/AST/ASTConsumer.h>
|
#include <clang/AST/ASTConsumer.h>
|
||||||
|
#include <clang/AST/Mangle.h>
|
||||||
#include <clang/AST/RecursiveASTVisitor.h>
|
#include <clang/AST/RecursiveASTVisitor.h>
|
||||||
|
#include <clang/Frontend/CompilerInstance.h>
|
||||||
#include <clang/Lex/PPCallbacks.h>
|
#include <clang/Lex/PPCallbacks.h>
|
||||||
#include <clang/Lex/Preprocessor.h>
|
|
||||||
|
|
||||||
class HeaderASTVisitor
|
class HeaderASTVisitor
|
||||||
: public clang::RecursiveASTVisitor<HeaderASTVisitor> {
|
: public clang::RecursiveASTVisitor<HeaderASTVisitor> {
|
||||||
public:
|
public:
|
||||||
|
HeaderASTVisitor(abi_dump::TranslationUnit *tu_ptr,
|
||||||
|
clang::MangleContext *mangle_contextp,
|
||||||
|
const clang::ASTContext *ast_contextp,
|
||||||
|
const clang::CompilerInstance *compiler_instance_p);
|
||||||
|
|
||||||
bool VisitRecordDecl(const clang::RecordDecl *decl);
|
bool VisitRecordDecl(const clang::RecordDecl *decl);
|
||||||
bool VisitCXXRecordDecl(const clang::CXXRecordDecl *decl);
|
|
||||||
bool VisitFunctionDecl(const clang::FunctionDecl *decl);
|
bool VisitFunctionDecl(const clang::FunctionDecl *decl);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool SetupFunction(abi_dump::FunctionDecl *methodp,
|
||||||
|
const clang::FunctionDecl *decl);
|
||||||
|
|
||||||
|
bool SetupClassFields(abi_dump::RecordDecl *classp,
|
||||||
|
const clang::RecordDecl *decl);
|
||||||
|
|
||||||
|
std::string GetDeclSourceFile(const clang::NamedDecl *decl);
|
||||||
|
|
||||||
|
std::string GetMangledNameDecl(const clang::NamedDecl *decl);
|
||||||
|
|
||||||
|
private:
|
||||||
|
abi_dump::TranslationUnit *tu_ptr_;
|
||||||
|
clang::MangleContext *mangle_contextp_;
|
||||||
|
const clang::ASTContext *ast_contextp_;
|
||||||
|
const clang::CompilerInstance *cip_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HeaderASTConsumer : public clang::ASTConsumer {
|
class HeaderASTConsumer : public clang::ASTConsumer {
|
||||||
public:
|
public:
|
||||||
|
HeaderASTConsumer(const std::string &file_name,
|
||||||
|
clang::CompilerInstance *compiler_instancep,
|
||||||
|
const std::string &out_dump_name);
|
||||||
|
|
||||||
void HandleTranslationUnit(clang::ASTContext &ctx) override;
|
void HandleTranslationUnit(clang::ASTContext &ctx) override;
|
||||||
|
|
||||||
void HandleVTable(clang::CXXRecordDecl *crd) override;
|
void HandleVTable(clang::CXXRecordDecl *crd) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string file_name_;
|
||||||
|
clang::CompilerInstance *cip_;
|
||||||
|
std::string out_dump_name_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HeaderASTPPCallbacks : public clang::PPCallbacks {
|
class HeaderASTPPCallbacks : public clang::PPCallbacks {
|
||||||
private:
|
|
||||||
llvm::StringRef ToString(const clang::Token &tok);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void MacroDefined(const clang::Token ¯o_name_tok,
|
void MacroDefined(const clang::Token ¯o_name_tok,
|
||||||
const clang::MacroDirective *) override;
|
const clang::MacroDirective *) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // AST_PROCESSING_H_
|
||||||
|
|||||||
@@ -13,16 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include "frontend_action.h"
|
#include "frontend_action.h"
|
||||||
|
|
||||||
#include "ast_processing.h"
|
#include "ast_processing.h"
|
||||||
|
|
||||||
#include <clang/AST/AST.h>
|
|
||||||
#include <clang/AST/ASTConsumer.h>
|
#include <clang/AST/ASTConsumer.h>
|
||||||
#include <clang/Frontend/CompilerInstance.h>
|
#include <clang/Frontend/CompilerInstance.h>
|
||||||
#include <clang/Frontend/MultiplexConsumer.h>
|
#include <clang/Lex/Preprocessor.h>
|
||||||
#include <clang/Lex/Token.h>
|
|
||||||
#include <clang/Serialization/ASTWriter.h>
|
|
||||||
#include <llvm/ADT/STLExtras.h>
|
#include <llvm/ADT/STLExtras.h>
|
||||||
#include <llvm/Support/raw_ostream.h>
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -39,9 +36,5 @@ HeaderCheckerFrontendAction::CreateASTConsumer(clang::CompilerInstance &ci,
|
|||||||
pp.addPPCallbacks(llvm::make_unique<HeaderASTPPCallbacks>());
|
pp.addPPCallbacks(llvm::make_unique<HeaderASTPPCallbacks>());
|
||||||
|
|
||||||
// Create AST consumers.
|
// Create AST consumers.
|
||||||
std::vector<std::unique_ptr<clang::ASTConsumer>> consumers;
|
return llvm::make_unique<HeaderASTConsumer>(header_file, &ci, dump_name_);
|
||||||
consumers.push_back(llvm::make_unique<HeaderASTConsumer>());
|
|
||||||
// Still have a MultiplexConsumer in case other consumers need to be
|
|
||||||
// added later.
|
|
||||||
return llvm::make_unique<clang::MultiplexConsumer>(std::move(consumers));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|||||||
37
vndk/tools/header-checker/proto/abi_dump.proto
Normal file
37
vndk/tools/header-checker/proto/abi_dump.proto
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package abi_dump;
|
||||||
|
|
||||||
|
message FunctionDecl {
|
||||||
|
// Fully Qualified Name.
|
||||||
|
required string function_name = 1 [default = "NONE"];
|
||||||
|
|
||||||
|
// Mangled name.
|
||||||
|
required string mangled_function_name = 2 [default = "NONE"];
|
||||||
|
|
||||||
|
required string source_file = 3;
|
||||||
|
required string parent_name = 4 [default = "NONE"];
|
||||||
|
repeated string template_arguments = 5 ;
|
||||||
|
repeated FieldDecl parameters = 6;
|
||||||
|
required string return_type = 7 [default = "VOID"];
|
||||||
|
}
|
||||||
|
|
||||||
|
message FieldDecl {
|
||||||
|
required string field_name = 1 [default = "NONE"];
|
||||||
|
required string field_type = 2 [default = "VOID"];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RecordDecl {
|
||||||
|
repeated FieldDecl fields = 2;
|
||||||
|
repeated string inner_classes = 3;
|
||||||
|
repeated string base_classes = 4;
|
||||||
|
required string fully_qualified_name = 5 [default = "NONE"];
|
||||||
|
required int64 id = 6 [default = 0];
|
||||||
|
required string entity_type = 7 [default = "NONE"];
|
||||||
|
required string source_file = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TranslationUnit {
|
||||||
|
repeated RecordDecl classes = 1;
|
||||||
|
repeated FunctionDecl functions = 2;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user