Recent

Author Topic: FPC Unleashed (inline vars, statement expr, tuples, match, indexed/lazy labels)  (Read 40639 times)

LeP

  • Sr. Member
  • ****
  • Posts: 331
feature request:
Add the compound operator "not in" and allow "in" and "not in" to test for a value being in or not in a range ("borrowed" from SQL.)  Note that this is for range testing, not set membership testing.
Only for note, this is already added to Delphi in the last release: https://docwiki.embarcadero.com/RADStudio/Florence/en/Expressions_(Delphi)#The_is_not_and_not_in_Operators
Un Sistema per domarli, un IDE per trovarli, un codice per ghermirli e nel framework incatenarli.
An operating system to tame them, an IDE to find them, a code to catch them and in the framework chain them.

Fibonacci

  • Hero Member
  • *****
  • Posts: 997
  • Behold, I bring salvation - FPC Unleashed
So Delphi has it?

Well... if Delphi has it, then FPC should have it too. I will look into it and probably implement it in Unleashed.



BTW, @Okoba: {$modeswitch implicitgenerics} should work. I will announce it later.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Thaddy

  • Hero Member
  • *****
  • Posts: 19267
  • Glad to be alive.
Implicitgenerics are already in trunk..... Did you damage it?
objects are fine constructs. You can even initialize them with constructors.

Fibonacci

  • Hero Member
  • *****
  • Posts: 997
  • Behold, I bring salvation - FPC Unleashed
Implicitgenerics are already in trunk..... Did you damage it?

In modes other than Delphi?
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6531
feature request:
Add the compound operator "not in" and allow "in" and "not in" to test for a value being in or not in a range ("borrowed" from SQL.)  Note that this is for range testing, not set membership testing.
Only for note, this is already added to Delphi in the last release: https://docwiki.embarcadero.com/RADStudio/Florence/en/Expressions_(Delphi)#The_is_not_and_not_in_Operators
I didn't know that.  Thank you LeP for pointing it out.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

speter

  • Hero Member
  • *****
  • Posts: 532
Why not expand it to if x > y < z and family. If you test for ranges, this is probably the same code.
+1 :)

cheers
S.
PS: I would also like to see sets with more than 256 elements.
I climbed mighty mountains, and saw that they were actually tiny foothills. :)

Fibonacci

  • Hero Member
  • *****
  • Posts: 997
  • Behold, I bring salvation - FPC Unleashed
New features and tweaks

Several things landed in main: implicit generics syntax in non-Delphi modes, scoped cleanup with defer and autofree, with gets inline vars + autofree, the for-loop counter is now preserved on exit, with shadowing warnings now cover methods too, and the Lazarus minimap can keep syntax colors. As always, all features are gated by individual modeswitches; many are on by default in {$mode unleashed}.



Implicit generics in non-Delphi modes

Modeswitch implicitgenerics. Off by default in {$mode unleashed} -- explicit opt-in.

Stock FPC accepts the short generic syntax (no generic / specialize keywords, plain <T> in declarations and specializations) only in {$mode delphi}. Unleashed lifts that recognition out into its own modeswitch, so you can keep objfpc (or unleashed) and just opt into the short syntax.

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2. {$modeswitch implicitgenerics}
  3.  
  4. type
  5.   TList<T> = class
  6.     procedure Add(const Item: T);
  7.   end;
  8.  
  9. var
  10.   L: TList<integer>;

Without the switch in non-Delphi modes you still write the explicit form (generic TList<T> / specialize TList<integer>); the switch only adds the implicit form on top, it does not remove anything.



Scoped cleanup: defer and autofree

Modeswitch autofree. On by default in {$mode unleashed}. Enables keywords autofree and defer.

Scope-based resource management without try..finally boilerplate. Two new keywords:

  • defer STATEMENT; - register a statement to fire when the enclosing block exits
  • autofree EXPR - prefix on a class instance, calls Free (or Destroy if no Free) at scope exit

defer

Multiple defers fire in LIFO order. The deferred body is evaluated at exit time, not at registration - so it sees the variable's last value.

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. procedure test;
  4. var
  5.   i: Integer;
  6. begin
  7.   i := 55;
  8.   defer writeln('i = ', i);
  9.   i := 123;
  10.   // prints "i = 123"
  11. end;
  12.  
  13. begin
  14.   test;
  15. end.

The scope is the enclosing begin..end block (or a scoped with body, see below). Defers fire on:
  • normal end of the block
  • exit / exit(value) (result is computed first, then defers, then the actual return)
  • break / continue / goto out of the block
  • exception propagating out

They do not fire on Halt, RunError, or signal-based termination.

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. procedure foo;
  4. begin
  5.   Lock.Enter;
  6.   defer Lock.Leave;        // released no matter how foo exits
  7.  
  8.   AssignFile(F, 'x.txt');
  9.   Reset(F);
  10.   defer CloseFile(F);
  11.  
  12.   // ... work ...
  13. end;

autofree

Sugar that registers a Free (or Destry) defer for a class instance you just allocated. Cleanup uses a nil-guarded pattern, so a manual x.Free; x := nil; earlier in the same scope makes the auto-cleanup a no-op rather than crashing on a double-free. Works in two forms:

Code: Pascal  [Select][+][-]
  1. // inline-var form
  2. begin
  3.   var x := autofree TStringList.Create;
  4.   x.Add('hello');
  5. end;
  6. // x.Free called here
  7.  
  8. // classic-var form
  9. var
  10.   x: TStringList;
  11. begin
  12.   x := autofree TStringList.Create;
  13.   x.Add('hello');
  14. end;
  15. // x.Free called here

If the class has no Free method, Destroy is called instead. Multiple autofrees in the same scope free in LIFO order. If the constructor raises, the auto-Free does not fire on the half-built instance - FPC's normal "automatic destroy on constructor failure" still runs, so each successful Create is matched by exactly one cleanup.

autofree/defer demo:

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. type
  4.   TMyClass = class
  5.     v: Integer;
  6.     constructor Create;
  7.     destructor Destroy; override;
  8.     procedure Free;
  9.     procedure doSomething(test: string);
  10.   end;
  11.  
  12.   TMyClassWithNoFree = class
  13.     v: Integer;
  14.     constructor Create;
  15.     destructor Destroy; override;
  16.     procedure doSomethingWithNoFree(test: string);
  17.   end;
  18.  
  19. { *** TMyClass *** }
  20.  
  21. constructor TMyClass.Create;
  22. begin
  23.   writeln('TMyClass.Create');
  24. end;
  25.  
  26. destructor TMyClass.Destroy;
  27. begin
  28.   writeln('TMyClass.Destroy');
  29. end;
  30.  
  31. procedure TMyClass.Free;
  32. begin
  33.   writeln('TMyClass.Free');
  34. end;
  35.  
  36. procedure TMyClass.doSomething(test: string);
  37. begin
  38.   writeln('TMyClass.doSomething('+test+')');
  39. end;
  40.  
  41. { *** TMyClassWithNoFree *** }
  42.  
  43. constructor TMyClassWithNoFree.Create;
  44. begin
  45.   writeln('TMyClassWithNoFree.Create');
  46. end;
  47.  
  48. destructor TMyClassWithNoFree.Destroy;
  49. begin
  50.   writeln('TMyClassWithNoFree.Destroy');
  51. end;
  52.  
  53. procedure TMyClassWithNoFree.doSomethingWithNoFree(test: string);
  54. begin
  55.   writeln('TMyClassWithNoFree.doSomethingWithNoFree('+test+')');
  56. end;
  57.  
  58. { *** tests *** }
  59.  
  60. procedure t1;
  61. begin
  62.   var c := autofree TMyClass.Create;
  63.   c.doSomething('t1');
  64. end;
  65.  
  66. procedure t2;
  67. begin
  68.   var c := TMyClass.Create;
  69.   defer c.Free;
  70.   defer writeln('end of test 2');
  71.   c.doSomething('t2');
  72. end;
  73.  
  74. procedure t3;
  75. begin
  76.   var c := TMyClass.Create;
  77.   with autofree c do c.doSomething('t3');
  78. end;
  79.  
  80. procedure t4;
  81. begin
  82.   with var c := TMyClass.Create do begin
  83.     defer writeln('escaping <with> block');
  84.     defer c.Free;
  85.     c.doSomething('t4');
  86.   end;
  87. end;
  88.  
  89. procedure t5;
  90. begin
  91.   with var c := autofree TMyClass.Create do c.doSomething('t5');
  92. end;
  93.  
  94. procedure t6;
  95. begin
  96.   with autofree TMyClass.Create do doSomething('t6');
  97. end;
  98.  
  99. procedure t7;
  100. begin
  101.   var c := TMyClass.Create;
  102.   with c do doSomething('t7');
  103.   c.Free;
  104. end;
  105.  
  106. procedure t8;
  107. begin
  108.   with var c1 := autofree TMyClass.Create, var c2 := TMyClass.Create do begin
  109.     doSomething('t8');
  110.   end;
  111. end;
  112.  
  113. procedure t9;
  114. begin
  115.   with autofree TMyClass.Create, autofree TMyClass.Create do begin
  116.     doSomething('t9');
  117.     v := 42;
  118.   end;
  119. end;
  120.  
  121. procedure t10;
  122. begin
  123.   var c := autofree TMyClassWithNoFree.Create;
  124.   c.doSomethingWithNoFree('t10');
  125. end;
  126.  
  127. begin
  128.   writeln('*** test 1 ***');  t1;  writeln;
  129.   writeln('*** test 2 ***');  t2;  writeln;
  130.   writeln('*** test 3 ***');  t3;  writeln;
  131.   writeln('*** test 4 ***');  t4;  writeln;
  132.   writeln('*** test 5 ***');  t5;  writeln;
  133.   writeln('*** test 6 ***');  t6;  writeln;
  134.   writeln('*** test 7 ***');  t7;  writeln;
  135.   writeln('*** test 8 ***');  t8;  writeln;
  136.   writeln('*** test 9 ***');  t9;  writeln;
  137.   writeln('*** test 10 ***'); t10; writeln;
  138.   readln;
  139. end.

Output:

Code: Text  [Select][+][-]
  1. *** test 1 ***
  2. TMyClass.Create
  3. TMyClass.doSomething(t1)
  4. TMyClass.Free
  5.  
  6. *** test 2 ***
  7. TMyClass.Create
  8. TMyClass.doSomething(t2)
  9. end of test 2
  10. TMyClass.Free
  11.  
  12. *** test 3 ***
  13. TMyClass.Create
  14. TMyClass.doSomething(t3)
  15. TMyClass.Free
  16.  
  17. *** test 4 ***
  18. TMyClass.Create
  19. TMyClass.doSomething(t4)
  20. TMyClass.Free
  21. escaping <with> block
  22.  
  23. *** test 5 ***
  24. TMyClass.Create
  25. TMyClass.doSomething(t5)
  26. TMyClass.Free
  27.  
  28. *** test 6 ***
  29. TMyClass.Create
  30. TMyClass.doSomething(t6)
  31. TMyClass.Free
  32.  
  33. *** test 7 ***
  34. TMyClass.Create
  35. TMyClass.doSomething(t7)
  36. TMyClass.Free
  37.  
  38. *** test 8 ***
  39. TMyClass.Create
  40. TMyClass.Create
  41. TMyClass.doSomething(t8)
  42. TMyClass.Free
  43.  
  44. *** test 9 ***
  45. TMyClass.Create
  46. TMyClass.Create
  47. TMyClass.doSomething(t9)
  48. TMyClass.Free
  49. TMyClass.Free
  50.  
  51. *** test 10 ***
  52. TMyClassWithNoFree.Create
  53. TMyClassWithNoFree.doSomethingWithNoFree(t10)
  54. TMyClassWithNoFree.Destroy



Scoped with: inline vars and autofree

The with statement gets three new clause forms under the autofree modeswitch. They all bind a class instance to a name (or a hidden holder) that the with body sees in scope, with optional auto-cleanup. Multiple inline vars per with are allowed, mixed forms too.

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. uses fpHTTPClient;
  4.  
  5. procedure main;
  6. var
  7.   s: String;
  8. begin
  9.   // Form A: hidden holder (no name needed in the body)
  10.   with autofree TFPHTTPClient.Create(nil) do
  11.     s := Get('http://httpbin.org/ip');
  12.  
  13.   // Form B: bind to an existing local
  14.   var http: TFPHTTPClient;
  15.   with http := autofree TFPHTTPClient.Create(nil) do
  16.     s := http.Get('http://httpbin.org/ip');
  17.  
  18.   // Form C: inline-var with optional autofree
  19.   with var http := autofree TFPHTTPClient.Create(nil) do
  20.     s := http.Get('http://httpbin.org/ip');
  21.  
  22.   // Multiple inline vars + mixed forms
  23.   with var a := autofree TFoo.Create,
  24.        var b := autofree TBar.Create,
  25.        existing_c do
  26.     Use(a, b, existing_c);
  27.   // LIFO: b.Free first, then a.Free
  28. end;
  29.  
  30. begin
  31.   main;
  32. end.

A defer written inside the body of a scoped with is scoped to that with, not the enclosing routine, and fires before the autofree cleanup. Classic with foo do ... (no var / autofree / name :=) is unchanged - the new forms are additive.



Tweak: for-loop counter preserved on exit

Standard Pascal leaves the for-loop counter undefined after the loop exits. The optimizer is allowed to leave any value behind; on stock FPC, for i := 1 to 10 do ; typically leaves i = 11 because the cheapest exit-condition encoding overshoots by one.

In {$mode unleashed} the counter keeps its last assigned value:

Code: Pascal  [Select][+][-]
  1. for i := 1 to N do
  2.   if X[i] = target then
  3.     break;
  4. // i is guaranteed to equal the index where break fired (or N if never)

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2. var
  3.   i: Integer;
  4. begin
  5.   for i := 1 to 10 do {...};
  6.   // i is guaranteed to be 10
  7. end.

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2. var
  3.   i: Integer;
  4. begin
  5.   for i := 1 to 10 do if i = 5 then break;
  6.   // i is guaranteed to be 5
  7. end.

The cost is one extra assignment on the natural exit path. Nothing on break, continue, or exit. No dedicated modeswitch - this is unleashed-only. If you want the standard "undefined on exit" semantics back for a hot loop, switch the mode locally:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. var
  3.   i: Integer;
  4. begin
  5.   for i := 1 to 10 do {...};
  6.   // i is undefined, can contain garbage
  7. end.



Tweak: with shadowing warnings cover methods too

The with field-shadowing warning announced earlier now also fires when a method name is shadowed by a later with entry, not just data fields. Same diagnostic mechanism, broader coverage - catches the case where two records / classes in a with list expose a same-named method and the later one quietly wins:

Code: Pascal  [Select][+][-]
  1. with First, Second do
  2.   DoIt; // warning if both First.DoIt and Second.DoIt exist



IDE: minimap can keep syntax colors

The Lazarus Unleashed minimap package now has a Keep font color unchanged option in its settings. With it on, the minimap shows your normal syntax-highlighted code in miniature instead of recoloring everything to a single font color.



IDE: full autocomplete for new syntax

CodeTools fixes for all the recently landed unleashed syntax. Inline var declarations (including autofree), with var x := ... forms, scoped with with multiple inline vars, defer-bound locals, and the surrounding identifier completion / parameter hints all behave correctly.
« Last Edit: May 02, 2026, 11:02:04 am by Fibonacci »
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

ASerge

  • Hero Member
  • *****
  • Posts: 2497
As far as I understand, defer is an short form of the "try finally end".

LeP

  • Sr. Member
  • ****
  • Posts: 331
Regarding the changes that differentiate normal operation (classic FPC) from the UNLEASHED version, I believe that ensuring the index variable remains outside the loop is a mistake.

This behavior cannot be differentiated programmatically between the two versions, and this could cause serious problems in the future, for example, with third-party packages.

Furthermore, this is my personal opinion: since the index variable is "in scope" only within the loop, its validity outside of it should not be guaranteed (nor should its visibility be granted).

In fact, this is used in Delphi specifically for inline vars used as indexes (whose scope and visibility are valid only within the loop).

To better specify what I find problematic:
- loop used with UNLEASHED -> no modifier, index valid after exit;
- loop used with FPC -> no modifier, index NOT valid after exit;
Un Sistema per domarli, un IDE per trovarli, un codice per ghermirli e nel framework incatenarli.
An operating system to tame them, an IDE to find them, a code to catch them and in the framework chain them.

Fibonacci

  • Hero Member
  • *****
  • Posts: 997
  • Behold, I bring salvation - FPC Unleashed
As far as I understand, defer is an short form of the "try finally end".

Yes, single defer lowers to try..finally exactly. With multiple defers you get one try..finally whose finally part runs them in LIFO order, gated on per-defer boolean flags so that only those whose registration line was actually reached fire (a raise between two defers will skip the later one).



I believe that ensuring the index variable remains outside the loop is a mistake.
(...)
this could cause serious problems in the future, for example, with third-party packages.
(...)
its validity outside of it should not be guaranteed (nor should its visibility be granted).
(...)
In fact, this is used in Delphi specifically for inline vars used as indexes (whose scope and visibility are valid only within the loop).

The change is strictly additive. The only way it bites you is if your code already relies on garbage.

If you treat the counter as undefined after the loop - which is what the FPC docs say, what every Pascal book teaches, and what portable code does - nothing changes. Same source compiles and runs identically under stock FPC, Delphi, and Unleashed. No third-party package written against the documented rules can break, because no such package reads the counter after the loop and expects a specific value.

What changes: if you know you're on unleashed, you can now read it and rely on it. That's the whole feature - opt-in capability for code that explicitly targets {$mode unleashed}, not a silent shift for portable code.

And if you want the scope to truly end with the loop, inline-var already does it.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6531
Feature request: Add C99's Flexible Array Members to FPC. 

Characteristics of a flexible array member are:

1. The FAM must be the last field in the record

2. The record must have at least one other field before the FAM

3. sizeof(record) does not include the FAM — returns size of fixed part only

4. The FAM has no fixed size — extent determined at runtime by allocation

5. A record containing a FAM may not be nested inside another record

6. A record containing a FAM may not be used as an array element type

7. A variable of a FAM record type may not be declared on the stack — must be allocated dynamically

Syntax example:

Code: Pascal  [Select][+][-]
  1. type
  2.    TMessage = packed record
  3.       code   : integer;
  4.       length : integer;
  5.       data   : array [] of byte;    { flexible array member }
  6.    end
  7.  
Flexible array members are very useful when interfacing with the Windows API as there are a large number of structures whose last field are flexible array members even though, at this time, most of them are declared as arrays ANYSIZE_ARRAY which occupies one byte.

This should be fairly easy to implement :)

FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Fibonacci

  • Hero Member
  • *****
  • Posts: 997
  • Behold, I bring salvation - FPC Unleashed
Could you show a concrete usage example - how you would write it today (with the workaround you currently use), and how the same code would look with FAM implemented? A short snippet, just enough to see the actual delta.

The current FPC idiom (packed record ending in array[0..0] of byte plus GetMem(SizeOf(THeader) + payload)) is already close to what FAM would compile to - the visible differences seem to be mostly sizeof and the compiler-enforced rules from your list (FAM-must-be-last, no nesting, heap-only, etc.), not the generated code itself. A real-world side-by-side from your Windows API work would help me weigh whether this is worth a new syntax form.

3. sizeof(record) does not include the FAM — returns size of fixed part only

Is this the main reason you want this feature?
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6531
3. sizeof(record) does not include the FAM — returns size of fixed part only

Is this the main reason you want this feature?
That's a big part of the reason but not the only part.

When a record has a FAM, the compiler can enforce that the record cannot be nested in another record and, currently there is no way to prevent that mistake in FPC since FPC has no way of knowing that the last field is just an array marker.

Another problem solved by the FAM is that of alignment.  In C, the FAM's alignment can be specified as 64 or 128 or some other power of two.  There is no way to do that in FPC.  The workaround to that problem is error prone and laborious.  it requires a custom memory allocator and the code is often specific to the structure (though not always.)

Another problem solved by the FAM, is that a pointer to it can be obtained using the @ operator.  That is not doable in FPC because, since the field must be typed, it will change the size of the structure, therefore the field has to be omitted which prevents getting its address.  Again, alignment comes into play, if the field is not aligned on a byte boundary, obtaining the correct value to where the field actually starts requires binary acrobatics.

When dealing with FAMs, pointers are absolutely necessary, in FPC since the field definition cannot be part of the record then, the problem becomes that the compiler has no way of ensuring the programmer defines the pointer type correctly.  I mean if the FAM happens to be an array of boolean and the programmer mistakenly defines a pointer to qword instead of a pointer to boolean (as it should be), the compiler cannot catch that error because it does not know that the FAM is for a boolean type.  Again there is no workaround to that problem.

FAMs are somewhat rare but, having to handle FAMs in FPC is a nightmare.  There is no simple, easy workaround which is why I won't present one.  Too much hassle.

You asked for workarounds, in some cases there simply aren't any, you're programming without a safety net that even C provides, IOW, in this case, C has stronger type checking than Pascal (at least it knows the FAM's type, FPC doesn't).  In other cases, the workarounds are a very significant pain in the neck to implement and there is no help from the compiler to ensure the workaround is correct.

Admittedly, it's mostly a low level feature used when implementing some form of custom memory management.  Because of that, it's not often used by Pascal programmers.

ETA:

I ran into the FAMs problem a number of years ago (when MS started using it) and asked for ways of declaring a C struct with an equivalent Pascal record.  There wasn't and, there still isn't, a solution.  You can find the original thread at:
https://forum.lazarus.freepascal.org/index.php/topic,45998.msg326158.html#msg326158

All the workarounds are saddled with very undesirable problems.


« Last Edit: May 03, 2026, 06:46:04 am by 440bx »
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

creaothceann

  • Sr. Member
  • ****
  • Posts: 375
The change is strictly additive. The only way it bites you is if your code already relies on garbage.

In this regard I like the attitude of C/C++, i.e. "striving for absolute performance, within the feature set enabled by the programmer". And who knows, it might do make a difference in a nested loop in an image-processing application.

for is not really used that often in the performance-critical parts of the code I write, so I don't have strong feelings about it being enabled or disabled by default. But I do think that needing to switch to a whole different mode is not as fine-grained as it probably ought to be. Having a modeswitch to control it would be preferable imo.

Fibonacci

  • Hero Member
  • *****
  • Posts: 997
  • Behold, I bring salvation - FPC Unleashed
Couldn't post yesterday - forum was down for most of the day...

@440bx: take a look at branch feat/fam. When you have a moment, please check whether it works as expected and let me know if you spot any bugs or missing pieces.

On a side note - I noticed that "control characters" in source code break syntax highlighting in the IDE when mixed with inline vars, so I had to patch that. Looks like a complete archaism though. Does anyone actually use them? Any real-world code that relies on this? I'd happily rip it out entirely if no one needs it.

Demo:

(specifically written to use as many unleashed features as possible :D)

Code: Pascal  [Select][+][-]
  1. program tokenprivilegesdemo;
  2.  
  3. {$mode unleashed}
  4.  
  5. uses SysUtils, Windows;
  6.  
  7. const
  8.   SE_PRIVILEGE_REMOVED = $00000004; // missing in Windows
  9.  
  10. type
  11.   // FAM record
  12.   TOKEN_PRIVILEGES = packed record
  13.     PrivilegeCount: DWORD;
  14.     Privileges: array[] of LUID_AND_ATTRIBUTES;
  15.   end;
  16.  
  17. procedure fatal(msg: string);
  18. begin
  19.   writeln('FATAL: ', msg);
  20.   readln;
  21.   halt(1);
  22. end;
  23.  
  24. function describePrivilege(la: LUID_AND_ATTRIBUTES): (name: WideString; attrs: string);
  25. begin
  26.   var buf: array[0..255] of WideChar;
  27.   var len: dword := length(buf);
  28.   if not LookupPrivilegeNameW(nil, la.Luid, @buf[0], len) then buf[0] := #0;
  29.   result.name := WideCharToString(buf);
  30.  
  31.   result.attrs := '';
  32.   match all
  33.     la.Attributes and SE_PRIVILEGE_ENABLED            <> 0: result.attrs += 'ENABLED ';
  34.     la.Attributes and SE_PRIVILEGE_ENABLED_BY_DEFAULT <> 0: result.attrs += 'DEFAULT ';
  35.     la.Attributes and SE_PRIVILEGE_REMOVED            <> 0: result.attrs += 'REMOVED ';
  36.     la.Attributes and SE_PRIVILEGE_USED_FOR_ACCESS    <> 0: result.attrs += 'USED ';
  37.   end;
  38.   if result.attrs = '' then result.attrs := '-';
  39. end;
  40.  
  41. function queryTokenSize(token: THANDLE): (ok: boolean; size: dword);
  42. begin
  43.   result.size := 0;
  44.   GetTokenInformation(token, TokenPrivileges, nil, 0, result.size);
  45.   result.ok := GetLastError = ERROR_INSUFFICIENT_BUFFER;
  46. end;
  47.  
  48. procedure main;
  49. begin
  50.   // open access token of current process
  51.   var token: THANDLE;
  52.   if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, token) then fatal('OpenProcessToken failed');
  53.   defer CloseHandle(token);
  54.  
  55.   // first call, get required buffer size
  56.   var (ok, size) := queryTokenSize(token);
  57.   if not ok then fatal('GetTokenInformation (size query) failed');
  58.  
  59.   // second call, fetch actual data
  60.   var privs: ^TOKEN_PRIVILEGES;
  61.   GetMem(privs, size);
  62.   defer FreeMem(privs);
  63.  
  64.   if not GetTokenInformation(token, TokenPrivileges, privs, size, size) then fatal('GetTokenInformation failed');
  65.  
  66.   writeln('privilege count: ', privs^.PrivilegeCount);
  67.  
  68.   for var i := 0 to privs^.PrivilegeCount - 1 do begin
  69.     var (name, attrs) := describePrivilege(privs^.Privileges[i]);
  70.     writeln(Format('  %-42s  %s', [WideCharToString(@name[1]), attrs]));
  71.   end;
  72. end;
  73.  
  74. begin
  75.   main;
  76.   readln;
  77. end.

Output:

Code: Text  [Select][+][-]
  1. privilege count: 6
  2.   SeLockMemoryPrivilege                       -
  3.   SeShutdownPrivilege                         -
  4.   SeChangeNotifyPrivilege                     ENABLED DEFAULT
  5.   SeUndockPrivilege                           -
  6.   SeIncreaseWorkingSetPrivilege               -
  7.   SeTimeZonePrivilege                         -

Or as an admin:

Code: Text  [Select][+][-]
  1. privilege count: 25
  2.   SeLockMemoryPrivilege                       -
  3.   SeIncreaseQuotaPrivilege                    -
  4.   SeSecurityPrivilege                         -
  5.   SeTakeOwnershipPrivilege                    -
  6.   SeLoadDriverPrivilege                       -
  7.   SeSystemProfilePrivilege                    -
  8.   SeSystemtimePrivilege                       -
  9.   SeProfileSingleProcessPrivilege             -
  10.   SeIncreaseBasePriorityPrivilege             -
  11.   SeCreatePagefilePrivilege                   -
  12.   SeBackupPrivilege                           -
  13.   SeRestorePrivilege                          -
  14.   SeShutdownPrivilege                         -
  15.   SeDebugPrivilege                            -
  16.   SeSystemEnvironmentPrivilege                -
  17.   SeChangeNotifyPrivilege                     ENABLED DEFAULT
  18.   SeRemoteShutdownPrivilege                   -
  19.   SeUndockPrivilege                           -
  20.   SeManageVolumePrivilege                     -
  21.   SeImpersonatePrivilege                      ENABLED DEFAULT
  22.   SeCreateGlobalPrivilege                     ENABLED DEFAULT
  23.   SeIncreaseWorkingSetPrivilege               -
  24.   SeTimeZonePrivilege                         -
  25.   SeCreateSymbolicLinkPrivilege               -
  26.   SeDelegateSessionUserImpersonatePrivilege   -
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

 

TinyPortal © 2005-2018