Skip to content

Commit

Permalink
[clangd] Show size, offset and padding for bit fields on hover
Browse files Browse the repository at this point in the history
Examle:
```
struct test {
	char a;
	char b : 3;
	char c : 5;
	int d;
	int e : 27;
};
```

{F27617774}
{F27617776}
{F27617777}
{F27617780}

Reviewed By: sammccall

Differential Revision: https://reviews.llvm.org/D151128
  • Loading branch information
sr-tream authored and sam-mccall committed Jun 5, 2023
1 parent 17b0d90 commit 4cb5e43
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 30 deletions.
52 changes: 35 additions & 17 deletions clang-tools-extra/clangd/Hover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ HoverInfo getStringLiteralContents(const StringLiteral *SL,
HoverInfo HI;

HI.Name = "string-literal";
HI.Size = (SL->getLength() + 1) * SL->getCharByteWidth();
HI.Size = (SL->getLength() + 1) * SL->getCharByteWidth() * 8;
HI.Type = SL->getType().getAsString(PP).c_str();

return HI;
Expand Down Expand Up @@ -1000,33 +1000,34 @@ void addLayoutInfo(const NamedDecl &ND, HoverInfo &HI) {
const auto &Ctx = ND.getASTContext();
if (auto *RD = llvm::dyn_cast<RecordDecl>(&ND)) {
if (auto Size = Ctx.getTypeSizeInCharsIfKnown(RD->getTypeForDecl()))
HI.Size = Size->getQuantity();
HI.Size = Size->getQuantity() * 8;
return;
}

if (const auto *FD = llvm::dyn_cast<FieldDecl>(&ND)) {
const auto *Record = FD->getParent();
if (Record)
Record = Record->getDefinition();
if (Record && !Record->isInvalidDecl() && !Record->isDependentType() &&
!FD->isBitField()) {
if (Record && !Record->isInvalidDecl() && !Record->isDependentType()) {
const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(Record);
HI.Offset = Layout.getFieldOffset(FD->getFieldIndex()) / 8;
if (auto Size = Ctx.getTypeSizeInCharsIfKnown(FD->getType())) {
HI.Size = FD->isZeroSize(Ctx) ? 0 : Size->getQuantity();
HI.Offset = Layout.getFieldOffset(FD->getFieldIndex());
if (FD->isBitField())
HI.Size = FD->getBitWidthValue(Ctx);
else if (auto Size = Ctx.getTypeSizeInCharsIfKnown(FD->getType()))
HI.Size = FD->isZeroSize(Ctx) ? 0 : Size->getQuantity() * 8;
if (HI.Size) {
unsigned EndOfField = *HI.Offset + *HI.Size;

// Calculate padding following the field.
if (!Record->isUnion() &&
FD->getFieldIndex() + 1 < Layout.getFieldCount()) {
// Measure padding up to the next class field.
unsigned NextOffset =
Layout.getFieldOffset(FD->getFieldIndex() + 1) / 8;
unsigned NextOffset = Layout.getFieldOffset(FD->getFieldIndex() + 1);
if (NextOffset >= EndOfField) // next field could be a bitfield!
HI.Padding = NextOffset - EndOfField;
} else {
// Measure padding up to the end of the object.
HI.Padding = Layout.getSize().getQuantity() - EndOfField;
HI.Padding = Layout.getSize().getQuantity() * 8 - EndOfField;
}
}
// Offset in a union is always zero, so not really useful to report.
Expand Down Expand Up @@ -1401,6 +1402,24 @@ std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
return HI;
}

// Sizes (and padding) are shown in bytes if possible, otherwise in bits.
static std::string formatSize(uint64_t SizeInBits) {
uint64_t Value = SizeInBits % 8 == 0 ? SizeInBits / 8 : SizeInBits;
const char *Unit = Value != 0 && Value == SizeInBits ? "bit" : "byte";
return llvm::formatv("{0} {1}{2}", Value, Unit, Value == 1 ? "" : "s").str();
}

// Offsets are shown in bytes + bits, so offsets of different fields
// can always be easily compared.
static std::string formatOffset(uint64_t OffsetInBits) {
const auto Bytes = OffsetInBits / 8;
const auto Bits = OffsetInBits % 8;
auto Offset = formatSize(Bytes * 8);
if (Bits != 0)
Offset += " and " + formatSize(Bits);
return Offset;
}

markup::Document HoverInfo::present() const {
markup::Document Output;

Expand Down Expand Up @@ -1464,14 +1483,13 @@ markup::Document HoverInfo::present() const {
}

if (Offset)
Output.addParagraph().appendText(
llvm::formatv("Offset: {0} byte{1}", *Offset, *Offset == 1 ? "" : "s")
.str());
Output.addParagraph().appendText("Offset: " + formatOffset(*Offset));
if (Size) {
auto &P = Output.addParagraph().appendText(
llvm::formatv("Size: {0} byte{1}", *Size, *Size == 1 ? "" : "s").str());
if (Padding && *Padding != 0)
P.appendText(llvm::formatv(" (+{0} padding)", *Padding).str());
auto &P = Output.addParagraph().appendText("Size: " + formatSize(*Size));
if (Padding && *Padding != 0) {
P.appendText(
llvm::formatv(" (+{0} padding)", formatSize(*Padding)).str());
}
}

if (CalleeArgInfo) {
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/Hover.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ struct HoverInfo {
std::optional<std::vector<Param>> TemplateParameters;
/// Contains the evaluated value of the symbol if available.
std::optional<std::string> Value;
/// Contains the byte-size of fields and types where it's interesting.
/// Contains the bit-size of fields and types where it's interesting.
std::optional<uint64_t> Size;
/// Contains the offset of fields within the enclosing class.
std::optional<uint64_t> Offset;
Expand Down
68 changes: 56 additions & 12 deletions clang-tools-extra/clangd/unittests/HoverTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ TEST(Hover, Structured) {
HI.Definition = "char bar";
HI.Type = "char";
HI.Offset = 0;
HI.Size = 1;
HI.Padding = 7;
HI.Size = 8;
HI.Padding = 56;
HI.AccessSpecifier = "private";
}},
// Union field
Expand All @@ -107,8 +107,8 @@ TEST(Hover, Structured) {
HI.Kind = index::SymbolKind::Field;
HI.Definition = "char bar";
HI.Type = "char";
HI.Size = 1;
HI.Padding = 15;
HI.Size = 8;
HI.Padding = 120;
HI.AccessSpecifier = "public";
}},
// Bitfield
Expand All @@ -125,6 +125,8 @@ TEST(Hover, Structured) {
HI.Kind = index::SymbolKind::Field;
HI.Definition = "int x : 1";
HI.Type = "int";
HI.Offset = 0;
HI.Size = 1;
HI.AccessSpecifier = "public";
}},
// Local to class method.
Expand Down Expand Up @@ -188,7 +190,7 @@ TEST(Hover, Structured) {
HI.Definition = "char bar";
HI.Type = "char";
HI.Offset = 0;
HI.Size = 1;
HI.Size = 8;
HI.AccessSpecifier = "public";
}},
// Struct definition shows size.
Expand All @@ -200,7 +202,7 @@ TEST(Hover, Structured) {
HI.Name = "X";
HI.Kind = index::SymbolKind::Struct;
HI.Definition = "struct X {}";
HI.Size = 1;
HI.Size = 8;
}},
// Variable with template type
{R"cpp(
Expand Down Expand Up @@ -1285,6 +1287,26 @@ class Foo final {})cpp";
HI.Kind = index::SymbolKind::Field;
HI.Definition = "m_int arr[Size]";
HI.Type = {"m_int[Size]", "int[Size]"};
}},
{// Bitfield offset, size and padding
R"cpp(
struct Foo {
char x;
char [[^y]] : 1;
int z;
};
)cpp",
[](HoverInfo &HI) {
HI.NamespaceScope = "";
HI.LocalScope = "Foo::";
HI.Name = "y";
HI.Kind = index::SymbolKind::Field;
HI.Definition = "char y : 1";
HI.Type = "char";
HI.Offset = 8;
HI.Size = 1;
HI.Padding = 23;
HI.AccessSpecifier = "public";
}}};
for (const auto &Case : Cases) {
SCOPED_TRACE(Case.Code);
Expand Down Expand Up @@ -1508,7 +1530,7 @@ TEST(Hover, All) {
{"auto s = ^[[\"Hello, world!\"]]; // string literal",
[](HoverInfo &HI) {
HI.Name = "string-literal";
HI.Size = 14;
HI.Size = 112;
HI.Type = "const char[14]";
}},
{
Expand Down Expand Up @@ -3218,7 +3240,7 @@ TEST(Hover, Present) {
{
[](HoverInfo &HI) {
HI.Kind = index::SymbolKind::Class;
HI.Size = 10;
HI.Size = 80;
HI.TemplateParameters = {
{{"typename"}, std::string("T"), std::nullopt},
{{"typename"}, std::string("C"), std::string("bool")},
Expand Down Expand Up @@ -3274,16 +3296,38 @@ template <typename T, typename C = bool> class Foo {})",
HI.Name = "foo";
HI.Type = {"type", "can_type"};
HI.Definition = "def";
HI.Size = 4;
HI.Offset = 12;
HI.Padding = 4;
HI.Size = 32;
HI.Offset = 96;
HI.Padding = 32;
},
R"(field foo
Type: type (aka can_type)
Value = value
Offset: 12 bytes
Size: 4 bytes (+4 padding)
Size: 4 bytes (+4 bytes padding)
// In test::Bar
def)",
},
{
[](HoverInfo &HI) {
HI.Kind = index::SymbolKind::Field;
HI.LocalScope = "test::Bar::";
HI.Value = "value";
HI.Name = "foo";
HI.Type = {"type", "can_type"};
HI.Definition = "def";
HI.Size = 25;
HI.Offset = 35;
HI.Padding = 4;
},
R"(field foo
Type: type (aka can_type)
Value = value
Offset: 4 bytes and 3 bits
Size: 25 bits (+4 bits padding)
// In test::Bar
def)",
Expand Down

0 comments on commit 4cb5e43

Please sign in to comment.