Skip to content

Commit 5daadc7

Browse files
authored
Merge pull request #1 from GDKsoftware/fix/setattribute-existing-key
Overwrite an existing attribute instead of writing through an uninitialised pointer
2 parents 9bb8fbf + 6b0d0ba commit 5daadc7

2 files changed

Lines changed: 82 additions & 14 deletions

File tree

‎Source/DelphiAST.Classes.pas‎

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -421,32 +421,38 @@ procedure TSyntaxNode.SetAttribute(const Key: TAttributeName; const Value: strin
421421
AttributeEntry: PAttributeEntry;
422422
len: Integer;
423423
begin
424-
if not HasAttribute(Key) then
424+
if (Value = '') then
425+
begin
426+
RemoveAttribute(Key);
427+
Exit;
428+
end;
429+
if not TryGetAttributeEntry(Key, AttributeEntry) then
425430
begin
426-
if (Value = '') then Exit; //no action needed
427431
len := Length(FAttributes);
428432
SetLength(FAttributes, len + 1);
429433
AttributeEntry := @FAttributes[len];
430434
AttributeEntry^.Key := Key;
431435
Include(FAttributesInUse, Key);
432436
end;
433-
if (Value = '') then RemoveAttribute(Key);
434437
AttributeEntry^.Value := Value;
435438
end;
436439

437440
procedure TSyntaxNode.RemoveAttribute(const Key: TAttributeName);
438-
const
439-
Size = SizeOf(TAttributeEntry);
440441
var
441-
Entry: PAttributeEntry;
442-
Index: integer;
443-
begin
444-
if HasAttribute(Key) then begin
445-
TryGetAttributeEntry(Key, Entry);
446-
Index:= (NativeUInt(Entry) - NativeUInt(@FAttributes[0])) + Size;
447-
Move(Entry^, Pointer(NativeUInt(Entry)+Size)^, (High(FAttributes) * Size) - Index);
448-
Exclude(FAttributesInUse, Key);
449-
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);
450456
end;
451457

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

‎Test/UnitTests/DelphiAST.Tests.pas‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,65 @@ procedure TestVariableEndPosition;
241241
end;
242242
end;
243243

244+
procedure TestAttributeOverwrite;
245+
var
246+
Node: TSyntaxNode;
247+
begin
248+
Node := TSyntaxNode.Create(ntMethod);
249+
try
250+
Node.SetAttribute(anName, 'First');
251+
Node.SetAttribute(anType, 'Kept');
252+
Node.SetAttribute(anName, 'Second');
253+
AssertEquals('Second', Node.GetAttribute(anName), 'Overwritten attribute.');
254+
AssertEquals('Kept', Node.GetAttribute(anType), 'Other attribute.');
255+
AssertEquals(2, Length(Node.Attributes), 'Overwriting must not add an entry.');
256+
finally
257+
Node.Free;
258+
end;
259+
end;
260+
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+
283+
procedure TestRepeatedCallingConvention;
284+
var
285+
Root, IntfNode: TSyntaxNode;
286+
begin
287+
Root := ParseSource('unit Example; interface ' +
288+
'procedure Same(A: Integer); stdcall; stdcall; external ''a.dll'' index 93; ' +
289+
'procedure Other; stdcall; cdecl; external ''a.dll''; implementation end.');
290+
try
291+
IntfNode := FindDescendant(Root, ntInterface);
292+
AssertNotNil(IntfNode, 'Missing interface');
293+
AssertEquals(2, CountDescendants(IntfNode, ntMethod), 'Method count.');
294+
AssertEquals('stdcall', IntfNode.ChildNodes[0].GetAttribute(anCallingConvention),
295+
'A repeated calling convention.');
296+
AssertEquals('cdecl', IntfNode.ChildNodes[1].GetAttribute(anCallingConvention),
297+
'The last of two calling conventions.');
298+
finally
299+
Root.Free;
300+
end;
301+
end;
302+
244303
procedure TestInvalidSyntax;
245304
var
246305
Root: TSyntaxNode;
@@ -295,6 +354,9 @@ procedure RunAllTests;
295354
RunTest('AST.SourcePositions', TestSourcePositions);
296355
RunTest('AST.ConstantEndPosition', TestConstantEndPosition);
297356
RunTest('AST.VariableEndPosition', TestVariableEndPosition);
357+
RunTest('Node.AttributeOverwrite', TestAttributeOverwrite);
358+
RunTest('Node.AttributeRemove', TestAttributeRemove);
359+
RunTest('AST.RepeatedCallingConvention', TestRepeatedCallingConvention);
298360
RunTest('Parser.InvalidSyntax', TestInvalidSyntax);
299361
{$IFNDEF FPC}
300362
RunTest('Serialization.BinaryRoundTrip', TestBinarySerializationRoundTrip);

0 commit comments

Comments
 (0)