Delphi: Code eliminated by linker incorrectly - delphi

I'm using Delphi 10.2. I'm having a problem calling TList<>.Last. The Evaluate window tells me the code for the function has been eliminated by the linker.
The code snippets:
uses
ModelObjects,
ProximitySearch,
System.Classes,
System.UITypes,
System.Generics.Collections,
Winsoft.FireMonkey.FPdfView,
Winsoft.FireMonkey.PDFium;
...
type
TWidgetFinder = class(TObject)
private
fFieldInfos: TList<TFieldInfo>;
fPAnnotation: FPDF_ANNOTATION;
...
procedure TWidgetFinder.ConfigureFieldInfo;
var
key: String;
buffer: TBytes;
textLen: LongWord;
temp: String;
begin
...
SetLength(buffer, KShortBufferLength);
textLen := FPDFAnnot_GetStringValue(fPAnnotation, ToStringType(key), buffer, Length(buffer))
temp := TEncoding.Unicode.GetString(buffer, 0, textLen - 2);
fFieldInfos.Last.Name := TEncoding.Unicode.GetString(buffer, 0, textLen - 2);
...
The problem was fFieldInfos.Last.Name was empty. I thought I was not converting the buffer to a string correctly. But the correct string is written to temp. When I Evaluate fFieldInfos.Last.Name after assigning to it I get the following message:
Function to be called, {System.Generics.Collections}TList<ModelObjects.TFieldInfo>.Last, was eliminated by linker
I've seen the SO solutions that suggest I call the eliminated function innocuously during initialization. But it cannot be that Delphi is eliminating code randomly and I must discover each elimination as a bug. I don't understand what I have done that tells the linker TList<>.Last is not being used when I am clearly using it. Can someone help me understand this?
Thanks

TList<T>.Last is a function marked as inline. Such methods usually are not contained in the binary so you cannot use them in the evaluator during debugging. The same is the case most likely if you type fFieldInfos[fFieldInfos.Count-1] because GetItem (the getter behind the index property) is marked as inline as well.
What you can type into the evaluator though is fFieldInfos.List[fFieldInfos.Count-1] to get the last item in the list.
P.S. As for the issue of Name being empty - if TFieldInfo is a record that assignment is not going to work because .Last will return a copy of that record and assign Name to that one not affecting the one inside the list.

Related

Value used as both output and (non-reference) input in a chain of methods

Consider the following minimal example of method chaining, where a floating-point variable is set (using an out parameter) by an early method and then passed (using a const parameter) to a later method in the chain:
program ChainedConundrum;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
ValueType = Double;
TRec = record
function GetValue(out AOutput: ValueType): TRec;
procedure ShowValue(const AInput: ValueType);
end;
function TRec.GetValue(out AOutput: ValueType): TRec;
begin
AOutput := 394;
Result := Self;
end;
procedure TRec.ShowValue(const AInput: ValueType);
begin
Writeln(AInput);
end;
var
R: TRec;
Value: ValueType = 713;
begin
R.GetValue(Value).ShowValue(Value);
Readln;
end.
I initially expected this to print the floating-point number 394 (in some format), but it doesn't (necessarily); when I build the program using the 32-bit compiler of Delphi 10.3.2, the program prints 713. Stepping through the program using the debugger confirms that the initial, pre-GetValue value of Value is passed to ShowValue.
However, if I build this using the 64-bit compiler, 394 is printed. Similarly, if I change ValueType from Double to Int32, I get 394 in both versions. Int64 yields 394 in 64-bit and 713 in 32-bit. Strings yield the updated value. Classes work just like records. Class methods, however, as opposed to instance methods, always give me the updated value. And, of course, abandoning method chaining (R.GetValue(Value); R.ShowValue(Value)) does the same.
Not surprisingly, if I change the AInput parameter of ShowValue from a const (or undecorated value) parameter to a var parameter, I always get the updated value.
My conclusion is that either
it is not allowed to both set and pass a variable in a chain of methods like this, or
there's a bug in the compiler.
My question is: which is it? And if it isn't allowed, where does the documentation state this? I have so far not been able to find the relevant passage. (The phrase "sequence point" seems very rarely to occur anywhere near the phrase "Delphi" on the WWW.)
Everyone who has commented on this issue here or elsewhere agrees that this either "feels like" or "clearly" is a compiler bug.
I've created issue RSP-29733 at the Embarcadero Jira.
Turning to possible workarounds, notice that the issue seems to be that an old value of a variable is used by the compiler. Hence, the problem arise when the value is changed close to the use of the variable.
However, the variable's address isn't changed, so if you pass the variable by reference instead of by value, the problem disappears. One way is to use a var parameter when the value is passed the second time, even though you don't need that, or even want that semantically.
Hence, a more natural approach seems to be to use a const [Ref] parameter:
procedure ShowValue(const [Ref] AInput: ValueType);
This has the same semantics as an undecorated const parameter but forces the compiler to pass the variable by reference, thus avoiding the bug.

Delphi: Types of Actual and Formal Parameters Must Be Identical

There are a lot of questions already with this title; however, none address my issue. I am actually trying to pass by reference, yet am receiving the E2033: Types of actual and formal var parameters must be indentical error when trying to compile my code. I am trying to pass three (3) variables, each is an Integer, two by reference (Var) and the other not.
I do not understand the issue with my code, below. I have included the declaration, the definition, and the call.
Declaration of Routine:
private
updateDeviceStatus(Var aReturnCount, aNotFoundCount: Integer; aNumOfDevices: Integer);
I have tried to not condense the declaration of the arguments and declared Var for the first two, explicitly' however, this did not work.
Question 1: is the error because I am mixing by reference and by value (if I remember, correctly, some languages do not permit this)?
Definition of Routine:
procedure TfrmReturnMeterToMfg.UpdateDeviceStatus(Var aReturnCount, aNotFoundCount: Integer; aNumOfDevices : Integer);
begin
// DO SOMETHING
end;
Really, the code in the body of the routine is trivial with regard to the problem and does not affect the problem (at least it shouldn't be the cause in this case).
The Call to Routine:
The following is contained within another routine's body:
// local variables:
var ReturnCount, NotFoundCount, NumOfDevices: Integer;
begin
// SOMETHING HAPPENS TO EACH OF THESE VALUES (THEY ARE INCREMENTED)
UpdateDeviceStatus([ReturnCount], [NotFoundCount], NumOfDevices);
end;
Then I receive the error.
Question 2: is this a result of my syntax when calling the routine (attempting to pass the arguments)?
EDIT
So, you may be wondering (you being a more experienced Delphi programmer), "Where did this lark pick up the [ and ] bit? Here's the resource I was consulting (and see why I looked at the wrong thing in the comments, below): consulted resource.
I guess the brackets are the problem.
This code works for me:
program Project12;
{$APPTYPE CONSOLE}
type
TfrmReturnMeterToMfg = class
private
procedure UpdateDeviceStatus(Var aReturnCount, aNotFoundCount: Integer; aNumOfDevices : Integer);
end;
procedure TfrmReturnMeterToMfg.UpdateDeviceStatus(Var aReturnCount, aNotFoundCount: Integer; aNumOfDevices : Integer);
begin
// DO SOMETHING
end;
var thing : TfrmReturnMeterToMfg;
ReturnCount, NotFoundCount, NumOfDevices: Integer;
begin
ReturnCount := 4;
NotFoundCount := 2;
NumOfDevices := 42;
thing := TfrmReturnMeterToMfg.Create;
thing.UpdateDeviceStatus( ReturnCount, NotFoundCount, NumOfDevices);
thing.Free;
end.
Adding brackets around the argument changes the meaning of the code completely, they mean something. In this particular case, by adding brackets you instruct the compiler to pass
UpdateDeviceStatus(
[ ReturnCount ], // array or set of integer (with one element)
[ NotFoundCount ], // same again here
NumOfDevices // integer
);
This is something completely different.

GetItem on TDictionary eleminated by linker

I am using a TDictionary of <string, string>. But for some reason, the linker decides that I do not want to get items out of it.
I have the following code:
function TSheet.GetFieldName(Field: string; Default: string): string;
begin
Result := Default;
if FFieldNames[Field] = '' then
Result := Field
else
Result := FFieldNames[Field];
end;
FFieldNames is a TDictionary<string, string>. On line 2 (if FFieldNames[Field] = '' then), it throws a 'File not found' exception. Adding FFieldNames[Field] to my watch tells me that Function to be called, {System.Generics.Collections}TDictionary.GetItem, was eliminated by linker.
Someone asked here on a similar issue on how to avoid the linker eliminating functions during debugging. From this I gathered, that the compiler/linker assumes that I am not using it. Someone suggested - during conversation - that I should try using it more.
So I created the following code:
FFieldNames.Add(Name, S);
V := FFieldNames.Items[Name];
Where S, Name and V are strings. This is from the code where FFieldNames is filled with data. V's only purpose is to obtain the just inserted S; it does nothing else.
Strangely, while the debugger tells me the same thing (i.e. GetItem being eliminated), V does get set to the expected value. But it does not in my TSheet.GetFieldName function. :|
What am I missing?
The same problem applies to TList<>. Even if the code is using a method in the class it is not accessible from the debugger ("xxx on TList eliminated by linker"). I guess this is a problem with generics in general.
If you make a descendent class it will not have this problem
type
TMyList = class(TList<TMyObject>)
end;
var
List : TMyList;
begin
...
end;

Initialise string function result?

I've just been debugging a problem with a function that returns a string that has got me worried. I've always assumed that the implicit Result variable for functions that return a string would be empty at the start of the function call, but the following (simplified) code produced an unexpected result:
function TMyObject.GenerateInfo: string;
procedure AppendInfo(const AppendStr: string);
begin
if(Result > '') then
Result := Result + #13;
Result := Result + AppendStr;
end;
begin
if(ACondition) then
AppendInfo('Some Text');
end;
Calling this function multiple times resulted in:
"Some Text"
the first time,
"Some Text"
"Some Text"
the second time,
"Some Text"
"Some Text"
"Some Text"
the third time, etc.
To fix it I had to initialise the Result:
begin
Result := '';
if(ACondition) then
AppendInfo('Some Text');
end;
Is it necessary to initialise a string function result? Why (technically)? Why does the compiler not emit a warning "W1035 Return value of function 'xxx' might be undefined" for string functions? Do I need to go through all my code to make sure a value is set as it is not reliable to expect an empty string from a function if the result is not explicitly set?
I've tested this in a new test application and the result is the same.
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
S := GenerateInfo;
ShowMessage(S); // 5 lines!
end;
This is not a bug, but "feature":
For a string, dynamic array, method
pointer, or variant result, the
effects are the same as if the
function result were declared as an
additional var parameter following the
declared parameters. In other words,
the caller passes an additional 32-bit
pointer that points to a variable in
which to return the function result.
I.e. your
function TMyObject.GenerateInfo: string;
Is really this:
procedure TMyObject.GenerateInfo(var Result: string);
Note "var" prefix (not "out" as you may expect!).
This is SUCH un-intuitive, so it leads to all kind of problems in the code. Code in question - just one example of results of this feature.
See and vote for this request.
We've run into this before, I think maybe as far back as Delphi 6 or 7. Yes, even though the compiler doesn't bother to give you a warning, you do need to initialize your string Result variables, for precisely the reason you ran into. The string variable is getting initialized -- it doesn't start as a garbage reference -- but it doesn't seem to get reinitialized when you expect it to.
As for why it happens... not sure. It's a bug, so it doesn't necessarily need a reason. We only saw it happen when we called the function repeatedly in a loop; if we called it outside a loop, it worked as expected. It looked like the caller was allocating space for the Result variable (and reusing it when it called the same function repeatedly, thus causing the bug), rather than the function allocating its own string (and allocating a new one on each call).
If you were using short strings, then the caller does allocate the buffer -- that's long-standing behavior for large value types. But that doesn't make sense for AnsiString. Maybe the compiler team just forgot to change the semantics when they first implemented long strings in Delphi 2.
This is not a Bug. By definition no variable inside function is initialized, including Result.
So your Result is undefind on first call, and can hold anything. How it is implemented in compiler is irrelevant, and you can have different results in different compilers.
It seems like your function should be simplified like this:
function TMyObject.GenerateInfo: string;
begin
if(ACondition) then
Result := 'Some Text'
else
Result := '';
end;
You typically don't want to use Result on the right side of an assignment in a function.
Anyway, strictly for illustrative purposes, you could also do this, though not recommended:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
begin
S := ''; // Clear before you call
S := GenerateInfo;
end;
ShowMessage(S); // 5 lines!
end;
This looks like a bug in D2007. I just tested it in Delphi 2010 and got the expected behavior. (1 line instead of 5.)
If you think that some automatic management of strings are made to make your life easier, you're only partly right. All such things are also done to make string logic consistent and side-effects free.
In plenty of places there are string passed by reference, passed by value, but all these lines are expecting VALID strings in whose memory-management counter is some valid, not a garbage value. So in order to keep strings valid the only thing for sure is that they should be initialized when they firstly introduced. For example, for any local variable string this is a necessity since this is the place a string is introduced. All other string usage including function(): string (that actually procedure(var Result: string) as Alexander correctly pointed out) just expects valid strings on the stack, not initialized. And validness here comes from the fact that (var Result: string) construction says that "I'm waiting for a valid variable that definetly was introduced before". UPDATE: Because of that the actual contents of Result is unexpected, but due to the same logic, if it's the only call to this function that has a local variable at the left, the emptiness of the string in this case is guaranteed.
Alex's answer is nearly always right and it answers why I was seeing the strange behaviour that I was, but it isn't the whole story.
The following, compiled without optimisation, produces the expected result of sTemp being an empty string. If you swap the function out for the procedure call you get a different result.
There seems to be a different rule for the actual program unit.
Admittedly this is a corner case.
program Project1;
{$APPTYPE CONSOLE}
uses System.SysUtils;
function PointlessFunction: string;
begin
end;
procedure PointlessProcedure(var AString: string);
begin
end;
var
sTemp: string;
begin
sTemp := '1234';
sTemp := PointlessFunction;
//PointlessProcedure(sTemp);
WriteLn('Result:' + sTemp);
ReadLn;
end.

How to know what type is a var?

TypeInfo(Type) returns the info about the specified type, is there any way to know the typeinfo of a var?
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= TypeInfo(S);
Info:= TypeInfo(Instance);
Info:= TypeInfo(Obj);
end
This code returns:
[DCC Error] Unit1.pas(354): E2133 TYPEINFO standard function expects a type identifier
I know a non instantiated var is only a pointer address.
At compile time, the compiler parses and do the type safety check.
At run time, is there any way to know a little more about a var, only passing its address?
No.
First, there's no such thing as a "non-instantiated variable." You instantiate it by the mere act of typing its name and type into your source file.
Second, you already know all there is to know about a variable by looking at it in your source code. The variable ceases to exist once your program is compiled. After that, it's all just bits.
A pointer only has a type at compile time. At run time, everything that can be done to that address has already been determined. The compiler checks for that, as you already noted. Checking the type of a variable at run time is only useful in languages where a variable's type could change, as in dynamic languages. The closest Delphi comes to that is with its Variant type. The type of the variable is always Variant, but you can store many types of values in it. To find out what it holds, you can use the VarType function.
Any time you could want to use TypeInfo to get the type information of the type associated with a variable, you can also directly name the type you're interested in; if the variable is in scope, then you can go find its declaration and use the declared type in your call to TypeInfo.
If you want to pass an arbitrary address to a function and have that function discover the type information for itself, you're out of luck. You will instead need to pass the PTypeInfo value as an additional parameter. That's what all the built-in Delphi functions do. For example, when you call New on a pointer variable, the compiler inserts an additional parameter that holds the PTypeInfo value for the type you're allocating. When you call SetLength on a dynamic array, the compiler inserts a PTypeInfo value for the array type.
The answer that you gave suggests that you're looking for something other than what you asked for. Given your question, I thought you were looking for a hypothetical function that could satisfy this code:
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= GetVariableTypeInfo(#S);
Assert(Info = TypeInfo(string));
Info:= GetVariableTypeInfo(#Instance);
Assert(Info = TypeInfo(IObjectType));
Info:= GetVariableTypeInfo(#Obj);
Assert(Info = TypeInfo(TDBGrid));
end;
Let's use the IsClass and IsObject functions from the JCL to build that function:
function GetVariableTypeInfo(pvar: Pointer): PTypeInfo;
begin
if not Assigned(pvar) then
Result := nil
else if IsClass(PPointer(pvar)^) then
Result := PClass(pvar).ClassInfo
else if IsObject(PPointer(pvar)^) then
Result := PObject(pvar).ClassInfo
else
raise EUnknownResult.Create;
end;
It obviously won't work for S or Instance above, but let's see what happens with Obj:
Info := GetVariableTypeInfo(#Obj);
That should give an access violation. Obj has no value, so IsClass and IsObject both will be reading an unspecified memory address, probably not one that belongs to your process. You asked for a routine that would use a variable's address as its input, but the mere address isn't enough.
Now let's take a closer look at how IsClass and IsObject really behave. Those functions take an arbitrary value and check whether the value looks like it might be a value of the given kind, either object (instance) or class. Use it like this:
// This code will yield no assertion failures.
var
p: Pointer;
o: TObject;
a: array of Integer;
begin
p := TDBGrid;
Assert(IsClass(p));
p := TForm.Create(nil);
Assert(IsObject(p));
// So far, so good. Works just as expected.
// Now things get interesting:
Pointer(a) := p;
Assert(IsObject(a));
Pointer(a) := nil;
// A dynamic array is an object? Hmm.
o := nil;
try
IsObject(o);
Assert(False);
except
on e: TObject do
Assert(e is EAccessViolation);
end;
// The variable is clearly a TObject, but since it
// doesn't hold a reference to an object, IsObject
// can't check whether its class field looks like
// a valid class reference.
end;
Notice that the functions tell you nothing about the variables, only about the values they hold. I wouldn't really consider those functions, then, to answer the question of how to get type information about a variable.
Furthermore, you said that all you know about the variable is its address. The functions you found do not take the address of a variable. They take the value of a variable. Here's a demonstration:
var
c: TClass;
begin
c := TDBGrid;
Assert(IsClass(c));
Assert(not IsClass(#c)); // Address of variable
Assert(IsObject(#c)); // Address of variable is an object?
end;
You might object to how I'm abusing these functions by passing what's obviously garbage into them. But I think that's the only way it makes sense to talk about this topic. If you know you'll never have garbage values, then you don't need the function you're asking for anyway because you already know enough about your program to use real types for your variables.
Overall, you're asking the wrong question. Instead of asking how you determine the type of a variable or the type of a value in memory, you should be asking how you got yourself into the position where you don't already know the types of your variables and your data.
With generics, it is now possible to get the type info without specifying it.
Certain users indicated the following code doesn't compile without errors.
As of Delphi 10 Seattle, version 23.0.20618.2753, it compiles without errors, as seen below in the screenshot.
program TypeInfos;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.TypInfo;
type
TTypeInfo = class
class procedure ShowTypeInfo<T>(const X: T);
end;
{ TTypeInfo }
class procedure TTypeInfo.ShowTypeInfo<T>(const X: T);
var
LTypeInfo: PTypeInfo;
begin
LTypeInfo := TypeInfo(T);
WriteLn(LTypeInfo.Name);
end;
var
L: Exception;
B: Boolean;
begin
// Console output
TTypeInfo.ShowTypeInfo(L); // Exception
TTypeInfo.ShowTypeInfo(B); // Boolean
end.
Not that I know of. You can get RTTI (Run Time Type Information) on published properties of a class, but not for "normal" variables like strings and integers and so forth. The information is simply not there.
Besides, the only way you could pass a var without passing a type is to use either a generic TObject parameter, a generic type (D2008, as in ), or as an untyped parameter. I can't think of another way of passing it that would even compile.

Resources