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:
Anthony Cui
2024-07-30 10:51:57 -07:00
committed by gVisor bot
parent 7d35643f09
commit c671ffdf47
5 changed files with 153 additions and 56 deletions
+3
View File
@@ -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",
+93 -22
View File
@@ -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)
+10 -1
View File
@@ -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;