Skip to content

Commit 6b0d0ba

Browse files
author
Patrick Quist
committed
Remove an attribute by dropping its entry
SetAttribute with an empty value goes through RemoveAttribute, which did not remove anything. It computed the byte offset of the entry past the one being removed and then moved the entries one slot further along rather than back, so the removed entry stayed, the one before the last was overwritten, and the array kept its length. Only the key's bit in FAttributesInUse was cleared, which hid the stale entry from HasAttribute but not from Attributes, so the writers and the binary serializer still saw it. The Move also copied the string values bit for bit, leaving two entries owning one reference. RemoveAttribute now shifts the entries after the removed one back by assignment, which keeps the string reference counts right, and shortens the array by one.
1 parent 6964589 commit 6b0d0ba

2 files changed

Lines changed: 37 additions & 11 deletions

File tree

‎Source/DelphiAST.Classes.pas‎

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -438,18 +438,21 @@ procedure TSyntaxNode.SetAttribute(const Key: TAttributeName; const Value: strin
438438
end;
439439

440440
procedure TSyntaxNode.RemoveAttribute(const Key: TAttributeName);
441-
const
442-
Size = SizeOf(TAttributeEntry);
443441
var
444-
Entry: PAttributeEntry;
445-
Index: integer;
446-
begin
447-
if HasAttribute(Key) then begin
448-
TryGetAttributeEntry(Key, Entry);
449-
Index:= (NativeUInt(Entry) - NativeUInt(@FAttributes[0])) + Size;
450-
Move(Entry^, Pointer(NativeUInt(Entry)+Size)^, (High(FAttributes) * Size) - Index);
451-
Exclude(FAttributesInUse, Key);
452-
end;
442+
i, Kept: Integer;
443+
begin
444+
if not HasAttribute(Key) then
445+
Exit;
446+
Kept := 0;
447+
for i := 0 to High(FAttributes) do
448+
if FAttributes[i].Key <> Key then
449+
begin
450+
if Kept <> i then
451+
FAttributes[Kept] := FAttributes[i];
452+
Inc(Kept);
453+
end;
454+
SetLength(FAttributes, Kept);
455+
Exclude(FAttributesInUse, Key);
453456
end;
454457

455458
function SameText(const Needle: string; const HayStack: array of string): boolean; overload;

‎Test/UnitTests/DelphiAST.Tests.pas‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,28 @@ procedure TestAttributeOverwrite;
258258
end;
259259
end;
260260

261+
procedure TestAttributeRemove;
262+
var
263+
Node: TSyntaxNode;
264+
begin
265+
Node := TSyntaxNode.Create(ntMethod);
266+
try
267+
Node.SetAttribute(anName, 'Name');
268+
Node.SetAttribute(anType, 'Type');
269+
Node.SetAttribute(anKind, 'Kind');
270+
Node.SetAttribute(anName, '');
271+
AssertFalse(Node.HasAttribute(anName), 'An empty value removes the attribute.');
272+
AssertEquals(2, Length(Node.Attributes), 'Removing must drop the entry.');
273+
AssertEquals('Type', Node.GetAttribute(anType), 'First remaining attribute.');
274+
AssertEquals('Kind', Node.GetAttribute(anKind), 'Second remaining attribute.');
275+
Node.SetAttribute(anName, 'Again');
276+
AssertEquals('Again', Node.GetAttribute(anName), 'A removed attribute can be set again.');
277+
AssertEquals(3, Length(Node.Attributes), 'Setting it again adds one entry.');
278+
finally
279+
Node.Free;
280+
end;
281+
end;
282+
261283
procedure TestRepeatedCallingConvention;
262284
var
263285
Root, IntfNode: TSyntaxNode;
@@ -333,6 +355,7 @@ procedure RunAllTests;
333355
RunTest('AST.ConstantEndPosition', TestConstantEndPosition);
334356
RunTest('AST.VariableEndPosition', TestVariableEndPosition);
335357
RunTest('Node.AttributeOverwrite', TestAttributeOverwrite);
358+
RunTest('Node.AttributeRemove', TestAttributeRemove);
336359
RunTest('AST.RepeatedCallingConvention', TestRepeatedCallingConvention);
337360
RunTest('Parser.InvalidSyntax', TestInvalidSyntax);
338361
{$IFNDEF FPC}

0 commit comments

Comments
 (0)