What is the cause of (apparently) spurious warnings when compiling local functions of anonymous functions, and how do I eliminate them.
A simple function compiles clean - no warnings. If the function is a local function of another function, again there are no warnings. If the function is a local function of an anonymous function it gives rise to the following warnings:
[DCC Warning] Unit1.pas(57): W1036 Variable 'i' might not have been initialized
[DCC Warning] Unit1.pas(58): W1035 Return value of function 'StrToJType' might be undefined
Example code is set out below. Please note that though this code compiles, it gives warnings unrelated to this question because it is incomplete.
EDIT
Comments and responses suggest that the fact that the example code contains no return value in the anonymous function may be the cause of the problem. This edit amends the code to remedy this, to simplify the local function case, and to minimise the code. The problem is still the same.
unit Unit1;
interface
type
JType = (JAtLeastOnce, JConditionLine, JInfix, JIteration, JNonNullInfix );
TFuncTest = reference to function : JType;
function StrToJType(aString : string) : JType;
implementation
function StrToJType(aString : string) : JType;
// Basic function - does not give warnings
var
i : integer;
begin
i := Pos(aString, '+i*-?');
if i <> 0 then result := JType(i - 1) else result := High(JType);
end;
function Test : JType;
// Local function - does not give warnings
function StrToJType(aString : string) : JType;
var
i : integer;
begin
i := Pos(aString, '+i*-?');
if i <> 0 then result := JType(i - 1) else result := High(JType);
end;
begin
result := low(JType);
end;
function Test2 : TFuncTest;
// Local function of anonymous function - gives warnings
begin
result :=
function : JType
function StrToJType(aString : string) : JType;
var
i : integer;
begin
i := Pos(aString, '+i*-?');
if i <> 0 then result := JType(i - 1) else result := High(JType);
end;
begin
result := Low(JType);
end;
end;
end.
Your anonymous function does not assign its return value.
function Test2 : TFuncTest;
// Local function of anonymous function - gives warnings
begin
result :=
function : JStructureType
function StrToJType(aString : string) : JStructureType;
const
c : string = '+i*-?p!qoxsc<^>8|';
var
i : integer;
begin
i := Pos(aString, c);
if (aString <> '') and (i <> 0) then result := JStructureType(i - 1)
else result := High(JStructureType);
end;
begin
// need to return something here
end;
end;
The compiler should also object to you making the same mistake in Test.
If the compiler really is complaining about StrToJType after you've fixed all the other errors, that would be a compiler bug and should be submitted to Quality Portal.
Update
Your edit confirms that the warnings in the local function remain when you resolve the other mistakes. So this would appear to be a compiler bug. Please make a minimal reproduction and submit it.
Of course Delphi XE won't be updated in the future. Unless this bug still exists in XE8 there's probably no point in reporting a bug.
One might suspect that the compiler is confused by a local function, local to an anonymous method, that is never called
Related
I'll say how I reproduce the problem on lazarus.
I have a form and a datamodule using zeos to enstablish a connection with a local oracle db.
The problem born when I put some code to interlocute with the db.
Here is an example:
OracleMng.ZQuery1.SQL.Clear;
That is exactly the line going in error.
Here is the full code of the form:
unit form1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, DBGrids, StdCtrls,
datamodule2;
type
{ TLogin }
TLogin = class(TForm)
Button1: TButton;
DBGrid1: TDBGrid;
procedure Button1Click(Sender: TObject);
private
public
end;
var
Login: TLogin;
implementation
{$R *.lfm}
{ TLogin }
procedure TLogin.Button1Click(Sender: TObject);
begin
OracleMng.ZQuery1.SQL.Clear;
end;
end.
Here is the code of the datamodule:
unit datamodule2;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DB, ZConnection, ZDataset, ZSqlMonitor;
type
{ TOracleMng }
TOracleMng = class(TDataModule)
DataSource1: TDataSource;
ZConnection1: TZConnection;
ZQuery1: TZQuery;
private
public
end;
var
OracleMng: TOracleMng;
implementation
{$R *.lfm}
{ TOracleMng }
end.
I'm trying
if (OracleMng <> Nil) and (OracleMng.Zquery1 <> Nil) then OracleMng.ZQuery1.SQL.add('select * from help');
if (OracleMng <> Nil) and (OracleMng.Zquery1 <> Nil) then OracleMng.ZQuery1.ExecSQL;
dbgrid1.refresh;
I have no more errors but the DBGrid1 is not filled.
This is my project lpr file:
program project1;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset
Forms, zcomponent, datamodule2, form1
{ you can add units after this };
{$R *.res}
begin
RequireDerivedFormResource:=True;
Application.Scaled:=True;
Application.Initialize;
Application.CreateForm(TLogin, Login);
Application.Run;
end.
The fact that the change I suggested in my comment, namely
if (OracleMng <> Nil) and (OracleMng.Zquery1 <> Nil) then
OracleMng.ZQuery1.SQL.Clear
evidently stopped you getting the SIGSEGV error suggests that your DataModule and
form are being created in the wrong order, i.e. form first. Check this out by going to
Project | View Source in the IDE. If you see something like
program MyProgram;
[...]
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TDataModule1, DataModule1);
Application.Run;
end.
they are in the wrong order, so swap the two CreateForm lines
Application.CreateForm(TDataModule1, DataModule1);
Application.CreateForm(TForm1, Form1);
With that change, you should no longer need the
if (OracleMng <> Nil) and (OracleMng.Zquery1 <> Nil) then`
Next thing: You seem to be confused about when to use
ZQuery1.ExecSQL
and
ZQuery1.Open
Open is intended for when the SQL statement you are using produces a result set, that is
a collection of records which can be viewed in a TDBGrid. The most usual way to do this
is to use a SELECT statement as in
ZQuery1.SQL.Text := 'select * from MyTable';
ZQuery1.Open;
ExecQuery is intended for use where your SQL statement performs some operation on the database
which does not involve SELECTing records. The most common SQL statements which need ExecSQL are
UPDATE
INSERT
DELETE
though there are others, for example statements which execute stored procedures on the SQL Server
(note that some stored procedures return result sets and so need Open, rather than ExecSQL).
Note that ExecSQL will clear out any records which are in the dataset (ZQuery1) so after
you need to do Open again using a suitable SQL statement
var
S : String;
begin
S := 'update MyTable set number = number +1 where id = 5';
ZQuery.SQL.Text := S;
ZQuery1.ExecSQL; // no records shown in DBGrid1 from here
S := 'select * from MyTable';
ZQuery.SQL.Text := S;
ZQuery1.Open; // records shown in DBGrid1 again
end;
Note that I do
S := 'select * from MyTable';
ZQuery.SQL.Text := S;
instead of
ZQuery1.SQL.Clear;
ZQuery1.SQL.Add('select * from myTable');
The reason for this is that it's much easier to see the whole SQL statement in the debugger by
inspecting the variable S than inspecting the ZQuery1.SQL.Text property and much easier to
see any syntax errors.
You should always Close a dataset that you've Opened once you have finished working with it as it ensures what the data on disk is up to date. if the last SQL operation was ExecSQL, you don't need to close the dataset.
If you set the query's Text property the way I do, with ZQuery1.SQL.Text, you don't need to uses Clear. In any case, it is only equivalent to doing ZQuery1.SQL.Text := '' and it does not affect the state of the dataset - it only does anything when you call ExecSQL or Open.
I have this problem.
Im at the End of this Function:
FUNCTION ToString(Liste : Pokemon) : String;
VAR
RES : STRING;
BEGIN
ClrScr;
TextBackground(Green);
Writeln('DER POKEDEX:');
Writeln;
WHILE (Liste <> NIL) DO
BEGIN
RES := RES + Concat('#',IntToStr(Liste^.PkmnPos), ': ', Liste^.PkmnName, '. // ', IntToStr(Liste^.PkmnKG), ' kg', chr(13),chr(10),chr(13),chr(10));
Liste := Liste^.Next;
END;
TextBackground(Black);
ToString := Res;
END;
Now, I have the Procedure called "Submenu". So, when in the Main Program code, i can just call the procedure "Submenu()". But when im within this above functions, it wont compile my code. It says "identifier not found". But, after im done with this function the last thing it needs to do is go into submenu. And im really trying not to builed an infinite loop in wihtin the main program only to not have the programm end. What is the best way of doing that?
O, and I know, that if I have the function Submenu started before the other functions, it would work. But both functions call each other, so neither can be on top of each other because there will always be one, that wont work...
Regards.
Then you need a forward declaration:
FUNCTION ToString(Liste : Pokemon) : String; FORWARD;
FUNCTION Submenu();
BEGIN
..
ToString(Liste);
..
END;
FUNCTION ToString(Liste : Pokemon) : String;
BEGIN
// real implementation tostring
..
Submenu();
..
END;
Note the declaration with FORWARD
When using TJson.JsonToObject in a multi-thread environment random access violations occur. I was searching a long time for the problem and I could isolate it with the following code
JSON class
type
TParameter = class
public
FName : string;
FDataType : string;
FValue : string;
end;
Testfunction:
procedure Test();
var
myTasks: array of ITask;
i : integer;
max : integer;
begin
max := 50;
SetLength(myTasks, max);
for i := 0 to max -1 do begin
myTasks[i] := TTask.Create(procedure ()
var
json : string;
p : TParameter;
begin
json := '{"name":"NameOfParam","dataType":"TypeOfParam","value":"ValueOfParam"}';
p := TJson.JsonToObject<TParameter>(json);
p.Free;
end);
myTasks[i].Start;
end;
TTask.WaitForAll(myTasks);
ShowMessage('all done!');
end;
It's only a code snippet based of a much more complex source. As long I use this code in a single thread everything works without a problem. I'm wondering if there is anything wrong with the code.
The method TJSONUnMarshal.ObjectInstance in REST.JsonReflect.pas has a severe bug:
It calls FreeAndNil on a TRttiType instance. This should never be done because all TRtti*** instances are managed by the TRttiContext.
After I removed the FreeAndNil call I could not reproduce the access violation anymore.
Reported as: https://quality.embarcadero.com/browse/RSP-10035
P.S. I also think that https://quality.embarcadero.com/browse/RSP-9815 will affect your code.
I'm writing long digit arythmetics. This is a function for adding to longint long binary digits. I need to output the sum inside the function, to debug it. How could I do it, without creating new variables?
function add(var s1,s2:bindata;shift:longint):bindata;
var l,i:longint;
o:boolean;
begin
writeln(s1.len,' - ',s2.len);
o:=false;
l:=max(s1.len,s2.len);
add.len:=0;
for i:=1 to l do begin
if o then Begin
if s1.data[i+shift] then Begin
if (s2.data[i]) then add.data[i+shift]:=true
Else add.data[i+shift]:=false;
End
else if s2.data[i] then add.data[i+shift]:=false
else Begin
add.data[i+shift]:=true;
o:=false;
End;
End
Else Begin
if s1.data[i+shift] then Begin
if s2.data[i] then
Begin
add.data[i+shift]:=false;
o:=true;
End
Else add.data[i+shift]:=true;
End
else if s2.data[i] then add.data[i+shift]:=true
else add.data[i+shift]:=false;
End;
output(add); //Can I output a variable?
end;
add.len:=l;
if o then Begin
inc(add.len);
add.data[add.len]:=true;
End;
end;
You are accumulating the result of the function within the function result variable, which is generally fine, but uses an outdated style, and leads to exactly the problem you're facing here. You're trying to report an intermediate value of the function result, and to do that, you're trying to reference the name of the function, add. When you do that, though, the compiler interprets it as an attempt to report the function itself, rather than the expected return value of this particular invocation of the function. You'll get the address of the function, if output is defined to accept function addresses; otherwise, you'll get a compiler error.
If your compiler offers a certain common language extension, then you should use the implicit Result variable to refer to the intermediate return value instead of continuing to refer to it by the function name. Since Result is declared implicitly, you wouldn't have to create any other variables. The compiler automatically recognizes Result and uses it as an alias for the function's return value. Simply find every place you write add within the function and replace it with Result. For example:
if o then begin
Inc(Result.len);
Result.data[Result.len] := True;
end;
Turbo Pascal, Free Pascal, GNU Pascal, and Delphi all support the implicit Result variable, but if you've managed to get stuck with a compiler that doesn't offer that extension, then you have no choice but to declare another variable. You could name it Result, and then implement your function with one additional line at the end, like so:
function add(var s1, s2: bindata; shift: longint): bindata;
var
l, i: longint;
o: boolean;
Result: bindata;
begin
{
Previous function body goes here, but with
`add` replaced by `Result`
}
{ Finally, append this line to copy Result into the
function's return value immediately before returning. }
add := Result;
end;
Today I downloaded and installed MyConnector so I can use Mysql with ADO, everything installed, OK!, I can make connection with ODBC and do a connection from my delphi environment.
when I build my Query at runetime, I get an error saying :
Project Project1.exe raised exception class EOleException with message 'Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another'. Process stopped. Use Step or Run to continue.
function TForm1.CreateSQL : TADOQuery;
begin
result := TADOQuery.create(self);
with Result do
begin
Connection := MainConnection;
CursorLocation := clUseServer;
CursorType := ctStatic;
CacheSize := 50;
AutoCalcFields := true;
ParamCheck := true;
Prepared := true;
end;
end;
procedure TForm1.login();
begin
with CreateSQL do
try
with SQL do
begin
add('SELECT ');
add(' * ');
add('FROM ');
add(' LisenswebUsers ');
add('WHERE ');
add(' UserName = :MyUsername '); // debugger exception here
add('AND ');
add(' UserPassword = :MyPassword '); // debugger exception here
with Parameters do
begin
ParamByName('MyUsername').value := txtLogin.text;
ParamByName('MyPassword').value := strmd5(txtPassword.text);
end;
Open;
if Recordcount <> 1 then
begin
lblLoggedinAs.Text := format('Du er logget inn som: %s (%s)',[FieldByName('Username').AsString,FieldByName('UserEmailaddress').AsString]);
MainPageControl.ActivePageIndex := 1;
end else
begin
txtPassword.Text := '';
txtPassword.SetFocus;
end;
end;
finally
free;
end;
end;
The strangest thing is that this works if I turn off debugging in delphi.
I would try adding SQL.BeginUpdate/SQL.EndUpdate around the Adds, otherwise the SQL text will be parsed every time you call "Add".
This is generally a good idea, as ADOQuery.SQL is a TStringList that has an OnChange event that sets the CommandText. SetCommandText text then end up calling TADOCommand.AssignCommandText which does a fair amount of work parsing params, and setting CommandObject.CommandText. Sometimes drivers will fail with partial SQL statements, but this stuff looks OK.
I had a similar problem many years ago - that's why I learnt about this stuff!
procedure TForm1.login();
var
Qry : TADOQuery;
begin
Qry := CreateSQL;
try
Qry.SQL.BeginUpdate;
Qry.SQL.Add('SELECT');
Qry.SQL.Add(' *');
Qry.SQL.Add('FROM');
Qry.SQL.Add(' LisenswebUsers');
Qry.SQL.Add('WHERE UserName = :MyUsername '); // debugger exception here
Qry.SQL.Add(' AND UserPassword = :MyPassword '); // debugger exception here
Qry.SQL.EndUpdate;
Qry.Parameters.ParamByName('MyUsername').value := txtLogin.text;
Qry.Parameters.ParamByName('MyPassword').value := strmd5(txtPassword.text);
Qry.Open;
if Qry.Recordcount <> 1 then
begin
lblLoggedinAs.Text := format('Du er logget inn som: %s (%s)',[FieldByName('Username').AsString,FieldByName('UserEmailaddress').AsString]);
MainPageControl.ActivePageIndex := 1;
end
else
begin
txtPassword.Text := '';
txtPassword.SetFocus;
end;
finally
Qry.Free;
end;
end;
BTW, the nested withs are really ugly (let the holy war begin)
I will sometimes use with, but would never nest three levels! If you are, at least reduce the scope of with SQL so it ends before with Parameters.
Try setting an explicit datatype :
CreateSql.Parameters.ParamByName('MyUserName').DataType := ftString;
In my case, defining the parameters and assigning the query string before assigning the connection corrected the problem. The query executes successfully in both cases, but the TADOQuery component internally raises (and subsequently swallows) the EOleException noted in OP if the connection is assigned before the parameterized query.
//LADOQuery.Connection := LADOConnection; // Exception # LADOQuery.Text:=...
Param := LADOQuery.Parameters.AddParameter;
Param.Name := 'rid';
Param.DataType := ftFixedChar;
Param := LADOQuery.Parameters.AddParameter;
Param.Name := 'qd';
Param.DataType := ftLongWord;
LADOQuery.SQL.Clear;
LADOQuery.SQL.Text:='SELECT Val FROM table WHERE v1=:rid AND v2=:qd';
LADOQuery.Connection := LADOConnection; // OK!
I'm open to explanations as to why this is the case - nothing in the documentation seems to suggest a need for this order of operations.
The exception is raised in ADODB.pas in TADOCommand.AssignCommandText here
try
// Retrieve additional parameter info from the server if supported
Parameters.InternalRefresh;
where this branch is only followed if the TADOQuery is attached to a live connection. InternalRefresh performs :
if OLEDBParameters.GetParameterInfo(ParamCount,
PDBPARAMINFO(ParamInfo),
#NamesBuffer) = S_OK then
for I := 0 to ParamCount - 1 do
with ParamInfo[I] do
begin
// When no default name, fabricate one like ADO does
if pwszName = nil then
Name := 'Param' + IntToStr(I+1) else // Do not localize
Name := pwszName;
// ADO maps DBTYPE_BYTES to adVarBinary
if wType = DBTYPE_BYTES then wType := adVarBinary;
// ADO maps DBTYPE_STR to adVarChar
if wType = DBTYPE_STR then wType := adVarChar;
// ADO maps DBTYPE_WSTR to adVarWChar
if wType = DBTYPE_WSTR then wType := adVarWChar;
Direction := dwFlags and $F;
// Verify that the Direction is initialized
if Direction = adParamUnknown then Direction := adParamInput;
Parameter := Command.CommandObject.CreateParameter(Name, wType, Direction, ulParamSize, EmptyParam);
Parameter.Precision := bPrecision;
Parameter.NumericScale := ParamInfo[I].bScale;
// EOleException raised here vvvvvvvvv
Parameter.Attributes := dwFlags and $FFFFFFF0; //Mask out Input/Output flags
AddParameter.FParameter := Parameter;
end;
The problem definitely seems to be at the OLE level, probably because the MySQL ODBC driver does not support returning this information (or returns invalid information). The exception is raised behind the _Parameter interface when setting
Parameter.Attributes := dwFlags and $FFFFFFF0;
using what seem to be invalid values (dwFlags = 320 -> bits set above DBPARAMFLAGSENUM defined length) returned from GetParameterInfo. Exception handling for flow control seems the only option given that the interface does not provide any mechanism to check values before setting them (and triggering exceptions).
Update :
It turns out there is an open QC about this : http://qc.embarcadero.com/wc/qcmain.aspx?d=107267
BeginUpdate/EndUpdate pair is not adequate. Use AddParameter to add parameters explicity before assigning sql command. Like:
var
Qry : TADOQuery;
begin
Qry := CreateSQL;
try
with Qry.Parameters.AddParameter do
begin
Name := 'MyUsername';
DataType := ftString;
end;
with Qry.Parameters.AddParameter do
begin
Name := 'MyPassword';
DataType := ftString;
end;
Qry.SQL.BeginUpdate;
Qry.SQL.Add('SELECT');
Qry.SQL.Add(' *');
Qry.SQL.Add('FROM');
Qry.SQL.Add(' LisenswebUsers');
Qry.SQL.Add('WHERE UserName = :MyUsername '); // debugger exception here
Qry.SQL.Add(' AND UserPassword = :MyPassword '); // debugger exception here
Qry.SQL.EndUpdate;
...