mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Have tool recurse through all structs and TypeDefs.
This is an improvement from just comparing the fields of top-level structs. As a part of this, we handle anonymous records and arrays as well. PiperOrigin-RevId: 657652481
This commit is contained in:
@@ -12,8 +12,11 @@ cc_binary(
|
||||
"driver_ast_parser.h",
|
||||
],
|
||||
deps = [
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@llvm-project//clang:ast",
|
||||
"@llvm-project//clang:ast_matchers",
|
||||
"@llvm-project//clang:basic",
|
||||
"@llvm-project//clang:tooling",
|
||||
"@llvm-project//llvm:Support",
|
||||
"@nlohmann_json//:json",
|
||||
|
||||
@@ -14,16 +14,24 @@
|
||||
|
||||
#include "tools/nvidia_driver_differ/driver_ast_parser.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "nlohmann/json.hpp"
|
||||
#include "clang/include/clang/AST/Decl.h"
|
||||
#include "clang/include/clang/AST/Type.h"
|
||||
#include "clang/include/clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/include/clang/ASTMatchers/ASTMatchers.h"
|
||||
#include "clang/include/clang/Basic/SourceManager.h"
|
||||
#include "clang/include/clang/Tooling/CommonOptionsParser.h"
|
||||
#include "clang/include/clang/Tooling/Tooling.h"
|
||||
#include "llvm/include/llvm/Support/Casting.h"
|
||||
#include "llvm/include/llvm/Support/CommandLine.h"
|
||||
#include "llvm/include/llvm/Support/raw_ostream.h"
|
||||
|
||||
@@ -40,7 +48,9 @@ using clang::ast_matchers::MatchFinder;
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct DriverStructReporter : public MatchFinder::MatchCallback {
|
||||
json StructDefinitions;
|
||||
json RecordDefinitions;
|
||||
json TypeAliases;
|
||||
absl::flat_hash_set<std::string> ParsedTypes;
|
||||
|
||||
auto get_struct_matcher(std::string struct_name) {
|
||||
// Nvidia's driver typedefs all their struct. We search for the
|
||||
@@ -61,7 +71,7 @@ struct DriverStructReporter : public MatchFinder::MatchCallback {
|
||||
result.Nodes.getNodeAs<clang::TypedefDecl>("typedef_decl");
|
||||
if (typedef_decl == nullptr) {
|
||||
std::cerr << "Unable to find typedef decl\n";
|
||||
return;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const auto *struct_decl =
|
||||
@@ -69,28 +79,88 @@ struct DriverStructReporter : public MatchFinder::MatchCallback {
|
||||
if (struct_decl == nullptr) {
|
||||
std::cerr << "Unable to find struct decl for "
|
||||
<< typedef_decl->getNameAsString() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Add struct definition to json.
|
||||
// TODO(b/347796680): Consider improvements:
|
||||
// Store alignment attributes as well?
|
||||
// Make recursive? Relevant for recursive structs not defined in nvproxy,
|
||||
// or unnamed structs/unions.
|
||||
// Handle anonymous names?
|
||||
json fields = json::array();
|
||||
for (const auto *field : struct_decl->fields()) {
|
||||
fields.push_back(
|
||||
json::object({{"name", field->getNameAsString()},
|
||||
{"type", field->getType().getAsString()}}));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::string name = typedef_decl->getNameAsString();
|
||||
std::string source = typedef_decl->getLocation().printToString(
|
||||
result.Context->getSourceManager());
|
||||
const auto &sm = result.Context->getSourceManager();
|
||||
add_type_definition(result.Context->getTypeDeclType(struct_decl), name, sm);
|
||||
}
|
||||
|
||||
StructDefinitions[name] =
|
||||
json::object({{"fields", fields}, {"source", source}});
|
||||
// Adds the type definition of `type` to either `RecordDefinitions` or
|
||||
// `TypeAliases`, mapped to `name`. Recursively adds the type definitions
|
||||
// of any nested types.
|
||||
void add_type_definition(const clang::QualType &type, const std::string &name,
|
||||
const clang::SourceManager &sm) {
|
||||
// We've already handled this type.
|
||||
if (ParsedTypes.contains(name)) {
|
||||
return;
|
||||
}
|
||||
ParsedTypes.insert(name);
|
||||
|
||||
// We use the canonical type to get past any typedefs.
|
||||
const auto canonical_type = type.getCanonicalType();
|
||||
|
||||
if (canonical_type->isRecordType()) {
|
||||
const auto record_decl = canonical_type->getAsRecordDecl();
|
||||
|
||||
add_record_definition(record_decl, name, sm);
|
||||
} else {
|
||||
TypeAliases[name] = canonical_type.getAsString();
|
||||
}
|
||||
}
|
||||
|
||||
// Adds the type definition of `record_decl` to `RecordDefinitions`, mapped
|
||||
// to `name`. Recursively adds the type definitions of any nested types.
|
||||
void add_record_definition(const clang::RecordDecl *record_decl,
|
||||
const std::string &name,
|
||||
const clang::SourceManager &sm) {
|
||||
json fields;
|
||||
for (const auto *field : record_decl->fields()) {
|
||||
auto field_type = field->getType();
|
||||
|
||||
// If this is an array type, save the array size then get the underlying
|
||||
// element type to recurse on later.
|
||||
uint64_t array_size = 0;
|
||||
if (field_type->isConstantArrayType()) {
|
||||
const auto *CAT = llvm::dyn_cast<clang::ConstantArrayType>(
|
||||
field_type->castAsArrayTypeUnsafe());
|
||||
if (CAT == nullptr) {
|
||||
std::cerr << "Unable to cast to ConstantArrayType\n";
|
||||
exit(1);
|
||||
}
|
||||
array_size = CAT->getSize().getZExtValue();
|
||||
field_type = CAT->getElementType();
|
||||
}
|
||||
|
||||
// Get the type name. If the type is not named, we use the record name
|
||||
// and field name to create a fake type name.
|
||||
std::string base_type_name;
|
||||
if (field_type->hasUnnamedOrLocalType()) {
|
||||
base_type_name =
|
||||
absl::StrCat(name, "::", field->getNameAsString(), "_t");
|
||||
} else {
|
||||
base_type_name = field_type.getAsString();
|
||||
}
|
||||
|
||||
// If this is an array type, add the array size to the type name.
|
||||
std::string field_type_name = base_type_name;
|
||||
if (array_size > 0) {
|
||||
absl::StrAppend(&field_type_name, "[", array_size, "]");
|
||||
}
|
||||
|
||||
// Add field to json.
|
||||
fields.push_back(json::object(
|
||||
{{"name", field->getNameAsString()}, {"type", field_type_name}}));
|
||||
|
||||
// Recurse on the field type.
|
||||
add_type_definition(field_type, base_type_name, sm);
|
||||
}
|
||||
|
||||
std::string source = record_decl->getLocation().printToString(sm);
|
||||
|
||||
RecordDefinitions[name] =
|
||||
json::object({{"source", source}, {"fields", fields}});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,7 +178,7 @@ static llvm::cl::opt<std::string> StructNames(
|
||||
|
||||
static llvm::cl::opt<std::string> OutputFile(
|
||||
"output", "o",
|
||||
llvm::cl::desc("Path to the output file for the parsed structs. "
|
||||
llvm::cl::desc("Path to the output file for the parsed type definitions. "
|
||||
"By default, will print to stdout."),
|
||||
llvm::cl::cat(DriverASTParserCategory));
|
||||
|
||||
@@ -145,7 +215,8 @@ int main(int argc, const char **argv) {
|
||||
int ret = Tool.run(clang::tooling::newFrontendActionFactory(&finder).get());
|
||||
|
||||
// Print output.
|
||||
json output = json::object({{"structs", reporter.StructDefinitions}});
|
||||
json output = json::object({{"records", reporter.RecordDefinitions},
|
||||
{"aliases", reporter.TypeAliases}});
|
||||
if (OutputFile.empty()) {
|
||||
std::cout << output.dump() << "\n";
|
||||
} else {
|
||||
|
||||
@@ -76,35 +76,45 @@ func TestParser(t *testing.T) {
|
||||
t.Fatalf("failed to unmarshal output %s: %v", string(out), err)
|
||||
}
|
||||
expectedOutput := parser.OutputJSON{
|
||||
Structs: parser.StructDefs{
|
||||
"TestStruct": parser.StructDef{
|
||||
Fields: []parser.StructField{
|
||||
parser.StructField{
|
||||
Name: "a",
|
||||
Type: "int",
|
||||
},
|
||||
parser.StructField{
|
||||
Name: "b",
|
||||
Type: "int",
|
||||
},
|
||||
parser.StructField{
|
||||
Name: "c",
|
||||
Type: "OtherInt",
|
||||
},
|
||||
Records: parser.RecordDefs{
|
||||
"TestStruct": parser.RecordDef{
|
||||
Fields: []parser.RecordField{
|
||||
parser.RecordField{Name: "a", Type: "int"},
|
||||
parser.RecordField{Name: "b", Type: "int"},
|
||||
parser.RecordField{Name: "e", Type: "TestStruct::e_t[4]"},
|
||||
parser.RecordField{Name: "f", Type: "TestUnion"},
|
||||
},
|
||||
Source: "test_struct.cc:24:3",
|
||||
Source: "test_struct.cc:25:16",
|
||||
},
|
||||
"TestStruct::e_t": parser.RecordDef{
|
||||
Fields: []parser.RecordField{
|
||||
parser.RecordField{Name: "c", Type: "OtherInt"},
|
||||
parser.RecordField{Name: "d", Type: "OtherInt"},
|
||||
},
|
||||
Source: "test_struct.cc:28:3",
|
||||
},
|
||||
"TestUnion": parser.RecordDef{
|
||||
Fields: []parser.RecordField{
|
||||
parser.RecordField{Name: "u_a", Type: "int"},
|
||||
parser.RecordField{Name: "u_b", Type: "int"},
|
||||
},
|
||||
Source: "test_struct.cc:20:9",
|
||||
},
|
||||
},
|
||||
Aliases: parser.TypeAliases{
|
||||
"OtherInt": "int",
|
||||
"int": "int",
|
||||
},
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(expectedOutput, outputJSON, cmpopts.IgnoreFields(parser.StructDef{}, "Source")); diff != "" {
|
||||
if diff := cmp.Diff(expectedOutput, outputJSON, cmpopts.IgnoreFields(parser.RecordDef{}, "Source")); diff != "" {
|
||||
t.Fatalf("output mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Only check the source suffix since the absolute path will be different every run.
|
||||
for name, structDef := range outputJSON.Structs {
|
||||
if !strings.HasSuffix(structDef.Source, expectedOutput.Structs[name].Source) {
|
||||
t.Fatalf("source mismatch for %s: should end with %s, got %s", name, expectedOutput.Structs[name].Source, structDef.Source)
|
||||
for name, structDef := range outputJSON.Records {
|
||||
if !strings.HasSuffix(structDef.Source, expectedOutput.Records[name].Source) {
|
||||
t.Fatalf("source mismatch for %s: should end with %s, got %s", name, expectedOutput.Records[name].Source, structDef.Source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,36 +30,40 @@ type InputJSON struct {
|
||||
|
||||
// OutputJSON is the format for the output of driver_ast_parser.
|
||||
type OutputJSON struct {
|
||||
Structs StructDefs `json:"structs"`
|
||||
Records RecordDefs `json:"records"`
|
||||
Aliases TypeAliases `json:"aliases"`
|
||||
}
|
||||
|
||||
// StructField represents a field in a struct.
|
||||
type StructField struct {
|
||||
// RecordField represents a field in a record (struct or union).
|
||||
type RecordField struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
func (s StructField) String() string {
|
||||
func (s RecordField) String() string {
|
||||
return fmt.Sprintf("%s %s", s.Type, s.Name)
|
||||
}
|
||||
|
||||
// StructDef represents a struct definition.
|
||||
type StructDef struct {
|
||||
Fields []StructField
|
||||
// RecordDef represents the definition of a record (struct or union).
|
||||
type RecordDef struct {
|
||||
Fields []RecordField
|
||||
Source string
|
||||
}
|
||||
|
||||
// Equals returns true if the two struct definitions are equal. We only
|
||||
// Equals returns true if the two record definitions are equal. We only
|
||||
// compare the fields, not the source.
|
||||
func (s StructDef) Equals(other StructDef) bool {
|
||||
func (s RecordDef) Equals(other RecordDef) bool {
|
||||
return slices.Equal(s.Fields, other.Fields)
|
||||
}
|
||||
|
||||
// StructDefs is a map of struct name to struct definition.
|
||||
type StructDefs map[string]StructDef
|
||||
// RecordDefs is a map of type names to definitions.
|
||||
type RecordDefs map[string]RecordDef
|
||||
|
||||
// GetStructDiff prints a diff between two struct definitions.
|
||||
func GetStructDiff(name string, s1, s2 StructDef) string {
|
||||
// TypeAliases is a map of type aliases to their underlying type.
|
||||
type TypeAliases map[string]string
|
||||
|
||||
// GetRecordDiff prints a diff between two records.
|
||||
func GetRecordDiff(name string, s1, s2 RecordDef) string {
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "--- A: %s\n", s1.Source)
|
||||
fmt.Fprintf(&b, "+++ B: %s\n", s2.Source)
|
||||
|
||||
@@ -17,8 +17,17 @@
|
||||
|
||||
typedef int OtherInt;
|
||||
|
||||
typedef union {
|
||||
int u_a;
|
||||
int u_b;
|
||||
} TestUnion;
|
||||
|
||||
typedef struct TestStruct {
|
||||
int a;
|
||||
int b;
|
||||
OtherInt c;
|
||||
struct {
|
||||
OtherInt c;
|
||||
OtherInt d;
|
||||
} e[4];
|
||||
TestUnion f;
|
||||
} TestStruct;
|
||||
|
||||
Reference in New Issue
Block a user