mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-05 22:08:11 +02:00
1004 lines
37 KiB
ObjectPascal
1004 lines
37 KiB
ObjectPascal
{
|
|
DFA
|
|
|
|
Copyright (c) 2007 by Florian Klaempfl
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
****************************************************************************
|
|
}
|
|
|
|
{ $define DEBUG_DFA}
|
|
{ $define EXTDEBUG_DFA}
|
|
|
|
{ this unit implements routines to perform dfa }
|
|
unit optdfa;
|
|
|
|
{$i fpcdefs.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
node,optutils;
|
|
|
|
type
|
|
TDFABuilder = class
|
|
protected
|
|
procedure CreateLifeInfo(node : tnode;map : TIndexedNodeSet);
|
|
public
|
|
resultnode : tnode;
|
|
nodemap : TIndexedNodeSet;
|
|
{ reset all dfa info, this is required before creating dfa info
|
|
if the tree has been changed without updating dfa }
|
|
procedure resetdfainfo(node : tnode);
|
|
|
|
procedure createdfainfo(node : tnode);
|
|
procedure redodfainfo(node : tnode);
|
|
destructor destroy;override;
|
|
end;
|
|
|
|
procedure CheckAndWarn(code : tnode;nodetosearch : tnode);
|
|
|
|
implementation
|
|
|
|
uses
|
|
globtype,
|
|
systems,
|
|
constexp,
|
|
verbose,
|
|
symconst,symdef,symsym,
|
|
defutil,
|
|
procinfo,
|
|
nutils,htypechk,
|
|
nbas,nflw,ncal,nset,nld,nadd,
|
|
optbase;
|
|
|
|
|
|
(*
|
|
function initnodes(var n:tnode; arg: pointer) : foreachnoderesult;
|
|
begin
|
|
{ node worth to add? }
|
|
if (node_complexity(n)>1) and (tstoreddef(n.resultdef).is_intregable or tstoreddef(n.resultdef).is_fpuregable) then
|
|
begin
|
|
plists(arg)^.nodelist.Add(n);
|
|
plists(arg)^.locationlist.Add(@n);
|
|
result:=fen_false;
|
|
end
|
|
else
|
|
result:=fen_norecurse_false;
|
|
end;
|
|
*)
|
|
|
|
{
|
|
x:=f; read: [f]
|
|
|
|
while x do read: []
|
|
|
|
a:=b; read: [a,b,d] def: [a] life: read*def=[a]
|
|
c:=d; read: [a,d] def: [a,c] life: read*def=[a]
|
|
e:=a; read: [a] def: [a,c,e] life: read*def=[a]
|
|
|
|
|
|
function f(b,d,x : type) : type;
|
|
|
|
begin
|
|
while x do alive: b,d,x
|
|
begin
|
|
a:=b; alive: b,d,x
|
|
c:=d; alive: a,d,x
|
|
e:=a+c; alive: a,c,x
|
|
dec(x); alive: c,e,x
|
|
end;
|
|
result:=c+e; alive: c,e
|
|
end; alive: result
|
|
|
|
}
|
|
|
|
type
|
|
tdfainfo = record
|
|
use : PDFASet;
|
|
def : PDFASet;
|
|
map : TIndexedNodeSet
|
|
end;
|
|
pdfainfo = ^tdfainfo;
|
|
|
|
function AddDefUse(var n: tnode; arg: pointer): foreachnoderesult;
|
|
begin
|
|
case n.nodetype of
|
|
tempcreaten:
|
|
begin
|
|
if assigned(ttempcreatenode(n).tempinfo^.tempinitcode) then
|
|
begin
|
|
pdfainfo(arg)^.map.Add(n);
|
|
DFASetInclude(pdfainfo(arg)^.def^,n.optinfo^.index);
|
|
end;
|
|
end;
|
|
temprefn,
|
|
loadn:
|
|
begin
|
|
pdfainfo(arg)^.map.Add(n);
|
|
if nf_modify in n.flags then
|
|
begin
|
|
DFASetInclude(pdfainfo(arg)^.use^,n.optinfo^.index);
|
|
DFASetInclude(pdfainfo(arg)^.def^,n.optinfo^.index)
|
|
end
|
|
else if nf_write in n.flags then
|
|
DFASetInclude(pdfainfo(arg)^.def^,n.optinfo^.index)
|
|
else
|
|
DFASetInclude(pdfainfo(arg)^.use^,n.optinfo^.index);
|
|
end;
|
|
else
|
|
;
|
|
end;
|
|
result:=fen_false;
|
|
end;
|
|
|
|
|
|
function ResetProcessing(var n: tnode; arg: pointer): foreachnoderesult;
|
|
begin
|
|
exclude(n.transientflags,tnf_processing);
|
|
{ dfa works only on normalized trees, so do not recurse into expressions, because
|
|
ResetProcessing eats a signififcant amount of time of CheckAndWarn
|
|
|
|
the following set contains (hopefully) most of the expression nodes }
|
|
if n.nodetype in [calln,inlinen,assignn,callparan,andn,addn,orn,subn,muln,divn,slashn,notn,equaln,unequaln,gtn,ltn,lten,gten,loadn,
|
|
typeconvn,vecn,subscriptn,addrn,derefn] then
|
|
result:=fen_norecurse_false
|
|
else
|
|
result:=fen_false;
|
|
end;
|
|
|
|
|
|
function ResetDFA(var n: tnode; arg: pointer): foreachnoderesult;
|
|
begin
|
|
if assigned(n.optinfo) then
|
|
begin
|
|
with n.optinfo^ do
|
|
begin
|
|
life:=nil;
|
|
def:=nil;
|
|
use:=nil;
|
|
defsum:=nil;
|
|
end;
|
|
end;
|
|
result:=fen_false;
|
|
end;
|
|
|
|
|
|
procedure TDFABuilder.CreateLifeInfo(node : tnode;map : TIndexedNodeSet);
|
|
|
|
var
|
|
changed : boolean;
|
|
|
|
procedure CreateInfo(node : tnode);
|
|
|
|
{ update life entry of a node with l, set changed if this changes
|
|
life info for the node
|
|
}
|
|
procedure updatelifeinfo(n : tnode;const l : TDFASet);
|
|
begin
|
|
if not DFASetNotEqual(l,n.optinfo^.life) then
|
|
exit;
|
|
{$ifdef DEBUG_DFA}
|
|
if not(changed) then
|
|
begin
|
|
writeln('Another DFA pass caused by: ',nodetype2str[n.nodetype],'(',n.fileinfo.line,',',n.fileinfo.column,')');
|
|
write(' Life info set was: ');PrintDFASet(Output,n.optinfo^.life);writeln;
|
|
write(' Life info set will be: ');PrintDFASet(Output,l);writeln;
|
|
end;
|
|
{$endif DEBUG_DFA}
|
|
|
|
changed:=true;
|
|
n.optinfo^.life:=l;
|
|
end;
|
|
|
|
procedure calclife(n : tnode);
|
|
var
|
|
l : TDFASet;
|
|
begin
|
|
if assigned(n.successor) then
|
|
begin
|
|
{ ensure we can access optinfo }
|
|
DFASetDiff(l,n.successor.optinfo^.life,n.optinfo^.def);
|
|
DFASetIncludeSet(l,n.optinfo^.use);
|
|
DFASetIncludeSet(l,n.optinfo^.life);
|
|
end
|
|
else
|
|
begin
|
|
l:=n.optinfo^.use;
|
|
DFASetIncludeSet(l,n.optinfo^.life);
|
|
end;
|
|
updatelifeinfo(n,l);
|
|
end;
|
|
|
|
var
|
|
dfainfo : tdfainfo;
|
|
l : TDFASet;
|
|
save: TDFASet;
|
|
lv, hv: TConstExprInt;
|
|
i : longint;
|
|
counteruse_after_loop : boolean;
|
|
begin
|
|
if node=nil then
|
|
exit;
|
|
|
|
{ ensure we've already optinfo set }
|
|
node.allocoptinfo;
|
|
|
|
if tnf_processing in node.transientflags then
|
|
exit;
|
|
include(node.transientflags,tnf_processing);
|
|
|
|
if assigned(node.successor) then
|
|
CreateInfo(node.successor);
|
|
|
|
{$ifdef EXTDEBUG_DFA}
|
|
writeln('Handling: ',nodetype2str[node.nodetype],'(',node.fileinfo.line,',',node.fileinfo.column,')');
|
|
{$endif EXTDEBUG_DFA}
|
|
{ life:=succesorlive-definition+use }
|
|
|
|
case node.nodetype of
|
|
whilerepeatn:
|
|
begin
|
|
{ analyze the loop condition }
|
|
if not(assigned(node.optinfo^.def)) and
|
|
not(assigned(node.optinfo^.use)) then
|
|
begin
|
|
dfainfo.use:=@node.optinfo^.use;
|
|
dfainfo.def:=@node.optinfo^.def;
|
|
dfainfo.map:=map;
|
|
foreachnodestatic(pm_postprocess,twhilerepeatnode(node).left,@AddDefUse,@dfainfo);
|
|
end;
|
|
|
|
{ NB: this node should typically have empty def set }
|
|
if assigned(node.successor) then
|
|
DFASetDiff(l,node.successor.optinfo^.life,node.optinfo^.def)
|
|
else if assigned(resultnode) then
|
|
DFASetDiff(l,resultnode.optinfo^.life,node.optinfo^.def)
|
|
else
|
|
l:=nil;
|
|
|
|
{ for repeat..until, node use set in included at the end of loop }
|
|
if not (lnf_testatbegin in twhilerepeatnode(node).loopflags) then
|
|
DFASetIncludeSet(l,node.optinfo^.use);
|
|
|
|
DFASetIncludeSet(l,node.optinfo^.life);
|
|
|
|
save:=node.optinfo^.life;
|
|
{ to process body correctly, we need life info in place (because
|
|
whilerepeatnode is successor of its body). }
|
|
node.optinfo^.life:=l;
|
|
|
|
{ now process the body }
|
|
CreateInfo(twhilerepeatnode(node).right);
|
|
|
|
{ restore, to prevent infinite recursion via changed flag }
|
|
node.optinfo^.life:=save;
|
|
|
|
{ for while loops, node use set is included at the beginning of loop }
|
|
l:=twhilerepeatnode(node).right.optinfo^.life;
|
|
if lnf_testatbegin in twhilerepeatnode(node).loopflags then
|
|
begin
|
|
DFASetIncludeSet(l,node.optinfo^.use);
|
|
{ ... loop body could be skipped, so include life info of the successsor node }
|
|
if assigned(node.successor) then
|
|
DFASetIncludeSet(l,node.successor.optinfo^.life);
|
|
end;
|
|
|
|
UpdateLifeInfo(node,l);
|
|
|
|
{ ... and a second iteration for fast convergence }
|
|
CreateInfo(twhilerepeatnode(node).right);
|
|
end;
|
|
|
|
forn:
|
|
begin
|
|
{
|
|
left: loopvar
|
|
right: from
|
|
t1: to
|
|
t2: body
|
|
}
|
|
node.allocoptinfo;
|
|
tfornode(node).loopiteration.allocoptinfo;
|
|
if not(assigned(node.optinfo^.def)) and
|
|
not(assigned(node.optinfo^.use)) then
|
|
begin
|
|
dfainfo.use:=@node.optinfo^.use;
|
|
dfainfo.def:=@node.optinfo^.def;
|
|
dfainfo.map:=map;
|
|
foreachnodestatic(pm_postprocess,tfornode(node).left,@AddDefUse,@dfainfo);
|
|
foreachnodestatic(pm_postprocess,tfornode(node).right,@AddDefUse,@dfainfo);
|
|
foreachnodestatic(pm_postprocess,tfornode(node).t1,@AddDefUse,@dfainfo);
|
|
end;
|
|
|
|
{ create life for the body }
|
|
CreateInfo(tfornode(node).t2);
|
|
|
|
{ is the counter living after the loop?
|
|
|
|
if left is a record element, it might not be tracked by dfa, so
|
|
optinfo might not be assigned
|
|
}
|
|
counteruse_after_loop:=assigned(tfornode(node).left.optinfo) and assigned(node.successor) and
|
|
DFASetIn(node.successor.optinfo^.life,tfornode(node).left.optinfo^.index);
|
|
|
|
if counteruse_after_loop then
|
|
begin
|
|
{ if yes, then we should warn }
|
|
{ !!!!!! }
|
|
end
|
|
else
|
|
Include(tfornode(node).loopflags,lnf_dont_mind_loopvar_on_exit);
|
|
|
|
{ first update the dummy node }
|
|
|
|
{ get the life of the loop block }
|
|
l:=copy(tfornode(node).t2.optinfo^.life);
|
|
|
|
{ take care of the sucessor }
|
|
if assigned(node.successor) then
|
|
DFASetIncludeSet(l,node.successor.optinfo^.life);
|
|
|
|
{ the counter variable is living as well inside the for loop
|
|
|
|
if left is a record element, it might not be tracked by dfa, so
|
|
optinfo might not be assigned
|
|
}
|
|
if assigned(tfornode(node).left.optinfo) then
|
|
DFASetInclude(l,tfornode(node).left.optinfo^.index);
|
|
|
|
{ force block node life info }
|
|
UpdateLifeInfo(tfornode(node).loopiteration,l);
|
|
|
|
{ now update the for node itself }
|
|
|
|
{ get the life of the loop block }
|
|
l:=copy(tfornode(node).t2.optinfo^.life);
|
|
|
|
{ take care of the sucessor as it's possible that we don't have one execution of the body }
|
|
if (not(tfornode(node).right.nodetype=ordconstn) or not(tfornode(node).t1.nodetype=ordconstn)) and
|
|
assigned(node.successor) then
|
|
DFASetIncludeSet(l,node.successor.optinfo^.life);
|
|
|
|
{
|
|
the counter variable is not living at the entry of the for node
|
|
|
|
if left is a record element, it might not be tracked by dfa, so
|
|
optinfo might not be assigned
|
|
}
|
|
if assigned(tfornode(node).left.optinfo) then
|
|
DFASetExclude(l,tfornode(node).left.optinfo^.index);
|
|
|
|
{ ... but it could be that left/right use it, so do this after
|
|
removing the def of the counter variable }
|
|
DFASetIncludeSet(l,node.optinfo^.use);
|
|
|
|
UpdateLifeInfo(node,l);
|
|
|
|
{ ... and a second iteration for fast convergence }
|
|
CreateInfo(tfornode(node).t2);
|
|
end;
|
|
|
|
temprefn,
|
|
loadn,
|
|
typeconvn,
|
|
derefn,
|
|
assignn:
|
|
begin
|
|
if not(assigned(node.optinfo^.def)) and
|
|
not(assigned(node.optinfo^.use)) then
|
|
begin
|
|
dfainfo.use:=@node.optinfo^.use;
|
|
dfainfo.def:=@node.optinfo^.def;
|
|
dfainfo.map:=map;
|
|
foreachnodestatic(pm_postprocess,node,@AddDefUse,@dfainfo);
|
|
end;
|
|
calclife(node);
|
|
end;
|
|
|
|
statementn:
|
|
begin
|
|
{ nested statement }
|
|
CreateInfo(tstatementnode(node).statement);
|
|
{ propagate info }
|
|
node.optinfo^.life:=tstatementnode(node).successor.optinfo^.life;
|
|
end;
|
|
|
|
blockn:
|
|
begin
|
|
CreateInfo(tblocknode(node).statements);
|
|
{ ensure that we don't remove life info }
|
|
l:=node.optinfo^.life;
|
|
if assigned(node.successor) then
|
|
DFASetIncludeSet(l,node.successor.optinfo^.life);
|
|
UpdateLifeInfo(node,l);
|
|
end;
|
|
|
|
ifn:
|
|
begin
|
|
{ get information from cond. expression }
|
|
if not(assigned(node.optinfo^.def)) and
|
|
not(assigned(node.optinfo^.use)) then
|
|
begin
|
|
dfainfo.use:=@node.optinfo^.use;
|
|
dfainfo.def:=@node.optinfo^.def;
|
|
dfainfo.map:=map;
|
|
foreachnodestatic(pm_postprocess,tifnode(node).left,@AddDefUse,@dfainfo);
|
|
end;
|
|
|
|
{ create life info for then and else node }
|
|
CreateInfo(tifnode(node).right);
|
|
CreateInfo(tifnode(node).t1);
|
|
|
|
{ ensure that we don't remove life info }
|
|
l:=node.optinfo^.life;
|
|
|
|
{ get life info from then branch }
|
|
if assigned(tifnode(node).right) then
|
|
DFASetIncludeSet(l,tifnode(node).right.optinfo^.life)
|
|
else if assigned(node.successor) then
|
|
DFASetIncludeSet(l,node.successor.optinfo^.life);
|
|
|
|
{ get life info from else branch }
|
|
if assigned(tifnode(node).t1) then
|
|
DFASetIncludeSet(l,tifnode(node).t1.optinfo^.life)
|
|
else if assigned(node.successor) then
|
|
DFASetIncludeSet(l,node.successor.optinfo^.life);
|
|
|
|
{ remove def info from the cond. expression }
|
|
DFASetExcludeSet(l,tifnode(node).optinfo^.def);
|
|
|
|
{ add use info from the cond. expression }
|
|
DFASetIncludeSet(l,tifnode(node).optinfo^.use);
|
|
|
|
{ finally, update the life info of the node }
|
|
UpdateLifeInfo(node,l);
|
|
end;
|
|
|
|
casen:
|
|
begin
|
|
{ get information from "case" expression }
|
|
if not(assigned(node.optinfo^.def)) and
|
|
not(assigned(node.optinfo^.use)) then
|
|
begin
|
|
dfainfo.use:=@node.optinfo^.use;
|
|
dfainfo.def:=@node.optinfo^.def;
|
|
dfainfo.map:=map;
|
|
foreachnodestatic(pm_postprocess,tcasenode(node).left,@AddDefUse,@dfainfo);
|
|
end;
|
|
|
|
{ create life info for block and else nodes }
|
|
for i:=0 to tcasenode(node).blocks.count-1 do
|
|
CreateInfo(pcaseblock(tcasenode(node).blocks[i])^.statement);
|
|
|
|
CreateInfo(tcasenode(node).elseblock);
|
|
|
|
{ ensure that we don't remove life info }
|
|
l:=node.optinfo^.life;
|
|
|
|
{ get life info from case branches }
|
|
for i:=0 to tcasenode(node).blocks.count-1 do
|
|
DFASetIncludeSet(l,pcaseblock(tcasenode(node).blocks[i])^.statement.optinfo^.life);
|
|
|
|
{ get life info from else branch or the succesor }
|
|
if assigned(tcasenode(node).elseblock) then
|
|
DFASetIncludeSet(l,tcasenode(node).elseblock.optinfo^.life)
|
|
else if assigned(node.successor) then
|
|
begin
|
|
if is_ordinal(tcasenode(node).left.resultdef) then
|
|
begin
|
|
getrange(tcasenode(node).left.resultdef,lv,hv);
|
|
if tcasenode(node).labelcoverage<(hv-lv) then
|
|
DFASetIncludeSet(l,node.successor.optinfo^.life);
|
|
end
|
|
else
|
|
DFASetIncludeSet(l,node.successor.optinfo^.life);
|
|
end;
|
|
|
|
{ add use info from the "case" expression }
|
|
DFASetIncludeSet(l,tcasenode(node).optinfo^.use);
|
|
|
|
{ finally, update the life info of the node }
|
|
UpdateLifeInfo(node,l);
|
|
end;
|
|
|
|
exitn:
|
|
begin
|
|
{ in case of inlining, an exit node can have a successor, in this case, we do not have to
|
|
use the faked resultnode }
|
|
if assigned(node.successor) then
|
|
begin
|
|
l:=node.optinfo^.life;
|
|
DFASetIncludeSet(l,node.successor.optinfo^.life);
|
|
UpdateLifeInfo(node,l);
|
|
end
|
|
else if assigned(resultnode) and (resultnode.nodetype<>nothingn) then
|
|
begin
|
|
if not(assigned(node.optinfo^.def)) and
|
|
not(assigned(node.optinfo^.use)) then
|
|
begin
|
|
if assigned(texitnode(node).left) then
|
|
begin
|
|
{ this should never happen as
|
|
texitnode.pass_typecheck converts the left node into a separate node already
|
|
|
|
node.optinfo^.def:=resultnode.optinfo^.def;
|
|
|
|
dfainfo.use:=@node.optinfo^.use;
|
|
dfainfo.def:=@node.optinfo^.def;
|
|
dfainfo.map:=map;
|
|
foreachnodestatic(pm_postprocess,texitnode(node).left,@AddDefUse,@dfainfo);
|
|
calclife(node); }
|
|
Internalerror(2020122901);
|
|
end
|
|
else
|
|
begin
|
|
{ get info from faked resultnode }
|
|
node.optinfo^.use:=resultnode.optinfo^.use;
|
|
node.optinfo^.life:=node.optinfo^.use;
|
|
changed:=true;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef JVM}
|
|
{ all other platforms except jvm translate raise nodes into call nodes during pass_1 }
|
|
raisen,
|
|
{$endif JVM}
|
|
tempcreaten,
|
|
asn,
|
|
inlinen,
|
|
calln:
|
|
begin
|
|
if not(assigned(node.optinfo^.def)) and
|
|
not(assigned(node.optinfo^.use)) then
|
|
begin
|
|
dfainfo.use:=@node.optinfo^.use;
|
|
dfainfo.def:=@node.optinfo^.def;
|
|
dfainfo.map:=map;
|
|
foreachnodestatic(pm_postprocess,node,@AddDefUse,@dfainfo);
|
|
end;
|
|
calclife(node);
|
|
end;
|
|
|
|
labeln,
|
|
tempdeleten,
|
|
nothingn,
|
|
continuen,
|
|
goton,
|
|
breakn:
|
|
begin
|
|
calclife(node);
|
|
end;
|
|
else
|
|
internalerror(2007050502);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
runs : integer;
|
|
begin
|
|
runs:=0;
|
|
repeat
|
|
inc(runs);
|
|
changed:=false;
|
|
CreateInfo(node);
|
|
foreachnodestatic(pm_postprocess,node,@ResetProcessing,nil);
|
|
{ the result node is not reached by foreachnodestatic }
|
|
exclude(resultnode.transientflags,tnf_processing);
|
|
{$ifdef DEBUG_DFA}
|
|
PrintIndexedNodeSet(output,map);
|
|
PrintDFAInfo(output,node);
|
|
{$endif DEBUG_DFA}
|
|
until not(changed);
|
|
{$ifdef DEBUG_DFA}
|
|
writeln('DFA solver iterations: ',runs);
|
|
{$endif DEBUG_DFA}
|
|
end;
|
|
|
|
|
|
{ reset all dfa info, this is required before creating dfa info
|
|
if the tree has been changed without updating dfa }
|
|
procedure TDFABuilder.resetdfainfo(node : tnode);
|
|
begin
|
|
nodemap.Free;
|
|
nodemap:=nil;
|
|
resultnode.Free;
|
|
resultnode:=nil;
|
|
foreachnodestatic(pm_postprocess,node,@ResetDFA,nil);
|
|
end;
|
|
|
|
|
|
procedure TDFABuilder.createdfainfo(node : tnode);
|
|
var
|
|
dfarec : tdfainfo;
|
|
begin
|
|
if not(assigned(nodemap)) then
|
|
nodemap:=TIndexedNodeSet.Create;
|
|
|
|
{ create a fake node using the result which will be the last node }
|
|
if not(is_void(current_procinfo.procdef.returndef)) then
|
|
begin
|
|
if current_procinfo.procdef.proctypeoption=potype_constructor then
|
|
resultnode:=load_self_node
|
|
else if (current_procinfo.procdef.proccalloption=pocall_safecall) and
|
|
(tf_safecall_exceptions in target_info.flags) then
|
|
resultnode:=load_safecallresult_node
|
|
else
|
|
resultnode:=load_result_node;
|
|
resultnode.allocoptinfo;
|
|
dfarec.use:=@resultnode.optinfo^.use;
|
|
dfarec.def:=@resultnode.optinfo^.def;
|
|
dfarec.map:=nodemap;
|
|
AddDefUse(resultnode,@dfarec);
|
|
resultnode.optinfo^.life:=resultnode.optinfo^.use;
|
|
end
|
|
else
|
|
begin
|
|
resultnode:=cnothingnode.create;
|
|
resultnode.allocoptinfo;
|
|
end;
|
|
|
|
{ add controll flow information }
|
|
SetNodeSucessors(node,resultnode);
|
|
|
|
{ now, collect life information }
|
|
CreateLifeInfo(node,nodemap);
|
|
end;
|
|
|
|
|
|
procedure TDFABuilder.redodfainfo(node: tnode);
|
|
begin
|
|
resetdfainfo(node);
|
|
createdfainfo(node);
|
|
include(current_procinfo.flags,pi_dfaavailable);
|
|
end;
|
|
|
|
|
|
destructor TDFABuilder.Destroy;
|
|
begin
|
|
Resultnode.free;
|
|
nodemap.free;
|
|
inherited destroy;
|
|
end;
|
|
|
|
type
|
|
{ helper structure to be able to pass more than one variable to the iterator function }
|
|
TSearchNodeInfo = record
|
|
nodetosearch : tnode;
|
|
{ this contains a list of all file locations where a warning was thrown already,
|
|
the same location might appear multiple times because nodes might have been copied }
|
|
warnedfilelocs : array of tfileposinfo;
|
|
end;
|
|
|
|
PSearchNodeInfo = ^TSearchNodeInfo;
|
|
|
|
{ searches for a given node n and warns if the node is found as being uninitialized. If a node is
|
|
found, searching is stopped so each call issues only one warning/hint }
|
|
function SearchNode(var n: tnode; arg: pointer): foreachnoderesult;
|
|
|
|
function WarnedForLocation(f : tfileposinfo) : boolean;
|
|
var
|
|
i : longint;
|
|
begin
|
|
result:=true;
|
|
for i:=0 to high(PSearchNodeInfo(arg)^.warnedfilelocs) do
|
|
with PSearchNodeInfo(arg)^.warnedfilelocs[i] do
|
|
begin
|
|
if (f.column=column) and (f.fileindex=fileindex) and (f.line=line) and (f.moduleindex=moduleindex) then
|
|
exit;
|
|
end;
|
|
result:=false;
|
|
end;
|
|
|
|
|
|
procedure AddFilepos(const f : tfileposinfo);
|
|
begin
|
|
Setlength(PSearchNodeInfo(arg)^.warnedfilelocs,length(PSearchNodeInfo(arg)^.warnedfilelocs)+1);
|
|
PSearchNodeInfo(arg)^.warnedfilelocs[high(PSearchNodeInfo(arg)^.warnedfilelocs)]:=f;
|
|
end;
|
|
|
|
|
|
{ Checks if the symbol is a candidate for a warning.
|
|
Emit warning/note for living locals, result and parameters, but only about the current
|
|
symtables }
|
|
function SymbolCandidateForWarningOrHint(sym : tabstractnormalvarsym) : Boolean;
|
|
begin
|
|
Result:=(((sym.owner=current_procinfo.procdef.localst) and
|
|
(current_procinfo.procdef.localst.symtablelevel=sym.owner.symtablelevel)
|
|
) or
|
|
((sym.owner=current_procinfo.procdef.parast) and
|
|
(sym.typ=paravarsym) and
|
|
(current_procinfo.procdef.parast.symtablelevel=sym.owner.symtablelevel) and
|
|
{ all parameters except out parameters are initialized by the caller }
|
|
(tparavarsym(sym).varspez=vs_out)
|
|
) or
|
|
((vo_is_funcret in sym.varoptions) and
|
|
(current_procinfo.procdef.parast.symtablelevel=sym.owner.symtablelevel)
|
|
)
|
|
) and
|
|
not(vo_is_external in sym.varoptions) and
|
|
not sym.inparentfpstruct and
|
|
not(vo_is_internal in sym.varoptions);
|
|
end;
|
|
|
|
var
|
|
varsym : tabstractnormalvarsym;
|
|
methodpointer,
|
|
hpt : tnode;
|
|
begin
|
|
result:=fen_false;
|
|
case n.nodetype of
|
|
callparan:
|
|
begin
|
|
{ do not warn about variables passed by var, just issue a hint, this
|
|
is a workaround for old code e.g. using fillchar }
|
|
if assigned(tcallparanode(n).parasym) and (tcallparanode(n).parasym.varspez in [vs_var,vs_out]) then
|
|
begin
|
|
hpt:=tcallparanode(n).left;
|
|
while assigned(hpt) and (hpt.nodetype in [subscriptn,vecn,typeconvn]) do
|
|
hpt:=tunarynode(hpt).left;
|
|
if assigned(hpt) and (hpt.nodetype=loadn) and not(WarnedForLocation(hpt.fileinfo)) and
|
|
SymbolCandidateForWarningOrHint(tabstractnormalvarsym(tloadnode(hpt).symtableentry)) and
|
|
PSearchNodeInfo(arg)^.nodetosearch.isequal(hpt) then
|
|
begin
|
|
{ issue only a hint for var, when encountering the node passed as out, we need only to stop searching }
|
|
if tcallparanode(n).parasym.varspez=vs_var then
|
|
UninitializedVariableMessage(hpt.fileinfo,false,
|
|
tloadnode(hpt).symtable.symtabletype=localsymtable,
|
|
is_managed_type(tloadnode(hpt).resultdef),
|
|
tloadnode(hpt).symtableentry.RealName);
|
|
AddFilepos(hpt.fileinfo);
|
|
result:=fen_norecurse_true;
|
|
end
|
|
end;
|
|
end;
|
|
orn,
|
|
andn:
|
|
begin
|
|
{ take care of short boolean evaluation: if the expression to be search is found in left,
|
|
we do not need to search right }
|
|
if foreachnodestatic(pm_postprocess,taddnode(n).left,@optdfa.SearchNode,arg) or
|
|
foreachnodestatic(pm_postprocess,taddnode(n).right,@optdfa.SearchNode,arg) then
|
|
result:=fen_norecurse_true
|
|
else
|
|
result:=fen_norecurse_false;
|
|
end;
|
|
calln:
|
|
begin
|
|
methodpointer:=tcallnode(n).methodpointer;
|
|
if assigned(methodpointer) and (methodpointer.nodetype<>typen) then
|
|
begin
|
|
{ Remove all postfix operators }
|
|
hpt:=methodpointer;
|
|
while assigned(hpt) and (hpt.nodetype in [subscriptn,vecn]) do
|
|
hpt:=tunarynode(hpt).left;
|
|
|
|
{ skip (absolute and other simple) type conversions -- only now,
|
|
because the checks above have to take type conversions into
|
|
e.g. class reference types account }
|
|
hpt:=actualtargetnode(@hpt)^;
|
|
|
|
{ R.Init then R will be initialized by the constructor,
|
|
Also allow it for simple loads }
|
|
if (tcallnode(n).procdefinition.proctypeoption=potype_constructor) or
|
|
(PSearchNodeInfo(arg)^.nodetosearch.isequal(hpt) and
|
|
(((methodpointer.resultdef.typ=objectdef) and
|
|
not(oo_has_virtual in tobjectdef(methodpointer.resultdef).objectoptions)) or
|
|
(methodpointer.resultdef.typ=recorddef)
|
|
)
|
|
) then
|
|
begin
|
|
{ don't warn about the method pointer }
|
|
AddFilepos(hpt.fileinfo);
|
|
|
|
if not(foreachnodestatic(pm_postprocess,tcallnode(n).left,@optdfa.SearchNode,arg)) then
|
|
foreachnodestatic(pm_postprocess,tcallnode(n).right,@optdfa.SearchNode,arg);
|
|
result:=fen_norecurse_true
|
|
end;
|
|
end;
|
|
end;
|
|
loadn:
|
|
begin
|
|
if (tloadnode(n).symtableentry.typ in [localvarsym,paravarsym,staticvarsym]) and
|
|
PSearchNodeInfo(arg)^.nodetosearch.isequal(n) and ((nf_modify in n.flags) or not(nf_write in n.flags)) then
|
|
begin
|
|
varsym:=tabstractnormalvarsym(tloadnode(n).symtableentry);
|
|
|
|
if assigned(varsym.owner) and SymbolCandidateForWarningOrHint(varsym) then
|
|
begin
|
|
if (vo_is_funcret in varsym.varoptions) and not(WarnedForLocation(n.fileinfo)) then
|
|
begin
|
|
if is_managed_type(varsym.vardef) then
|
|
MessagePos(n.fileinfo,sym_w_managed_function_result_uninitialized)
|
|
else
|
|
MessagePos(n.fileinfo,sym_w_function_result_uninitialized);
|
|
AddFilepos(n.fileinfo);
|
|
result:=fen_norecurse_true;
|
|
end
|
|
else
|
|
begin
|
|
{ typed consts are initialized, further, warn only once per location }
|
|
if not (vo_is_typed_const in varsym.varoptions) and not(WarnedForLocation(n.fileinfo)) then
|
|
begin
|
|
UninitializedVariableMessage(n.fileinfo,true,varsym.typ=localvarsym,is_managed_type(varsym.vardef),varsym.realname);
|
|
AddFilepos(n.fileinfo);
|
|
result:=fen_norecurse_true;
|
|
end;
|
|
end;
|
|
end
|
|
{$ifdef dummy}
|
|
{ if a the variable we are looking for is passed as a var parameter, we stop searching }
|
|
else if assigned(varsym.owner) and
|
|
(varsym.owner=current_procinfo.procdef.parast) and
|
|
(varsym.typ=paravarsym) and
|
|
(current_procinfo.procdef.parast.symtablelevel=varsym.owner.symtablelevel) and
|
|
(tparavarsym(varsym).varspez=vs_var) then
|
|
result:=fen_norecurse_true;
|
|
{$endif dummy}
|
|
end;
|
|
end;
|
|
else
|
|
;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure CheckAndWarn(code : tnode;nodetosearch : tnode);
|
|
|
|
var
|
|
SearchNodeInfo : TSearchNodeInfo;
|
|
|
|
function DoCheck(node : tnode) : boolean;
|
|
var
|
|
i : longint;
|
|
touchesnode : Boolean;
|
|
|
|
procedure MaybeDoCheck(n : tnode);inline;
|
|
begin
|
|
Result:=Result or DoCheck(n);
|
|
end;
|
|
|
|
procedure MaybeSearchIn(n : tnode);
|
|
begin
|
|
if touchesnode then
|
|
Result:=Result or foreachnodestatic(pm_postprocess,n,@SearchNode,@SearchNodeInfo);
|
|
end;
|
|
|
|
begin
|
|
result:=false;
|
|
|
|
if node=nil then
|
|
exit;
|
|
|
|
if tnf_processing in node.transientflags then
|
|
exit;
|
|
include(node.transientflags,tnf_processing);
|
|
|
|
if not(assigned(node.optinfo)) or not(DFASetIn(node.optinfo^.life,nodetosearch.optinfo^.index)) then
|
|
exit;
|
|
|
|
{ we do not need this info always, so try to safe some time here, CheckAndWarn
|
|
takes a lot of time anyways }
|
|
if not(node.nodetype in [statementn,blockn]) then
|
|
touchesnode:=DFASetIn(node.optinfo^.use,nodetosearch.optinfo^.index) or
|
|
DFASetIn(node.optinfo^.def,nodetosearch.optinfo^.index)
|
|
else
|
|
touchesnode:=false;
|
|
|
|
case node.nodetype of
|
|
whilerepeatn:
|
|
begin
|
|
MaybeSearchIn(twhilerepeatnode(node).left);
|
|
MaybeDoCheck(twhilerepeatnode(node).right);
|
|
end;
|
|
|
|
forn:
|
|
begin
|
|
MaybeSearchIn(tfornode(node).right);
|
|
MaybeSearchIn(tfornode(node).t1);
|
|
MaybeDoCheck(tfornode(node).t2);
|
|
end;
|
|
|
|
statementn:
|
|
MaybeDoCheck(tstatementnode(node).statement);
|
|
|
|
blockn:
|
|
MaybeDoCheck(tblocknode(node).statements);
|
|
|
|
ifn:
|
|
begin
|
|
MaybeSearchIn(tifnode(node).left);
|
|
MaybeDoCheck(tifnode(node).right);
|
|
MaybeDoCheck(tifnode(node).t1);
|
|
end;
|
|
|
|
casen:
|
|
begin
|
|
MaybeSearchIn(tcasenode(node).left);
|
|
for i:=0 to tcasenode(node).blocks.count-1 do
|
|
MaybeDoCheck(pcaseblock(tcasenode(node).blocks[i])^.statement);
|
|
|
|
MaybeDoCheck(tcasenode(node).elseblock);
|
|
end;
|
|
|
|
{ we are aware of the following nodes so if new node types are added to the compiler
|
|
and pop up in the search, the ie below kicks in as a reminder }
|
|
exitn:
|
|
begin
|
|
MaybeSearchIn(texitnode(node).left);
|
|
{ exit uses the resultnode implicitly, so searching for a matching node is
|
|
useless, if we reach the exit node and found the living node not in left, then
|
|
it can be only the resultnode
|
|
|
|
successor might be assigned in case of an inlined exit node, in this case we do not warn about an unassigned
|
|
result as this had happened already when the routine has been compiled }
|
|
if not(assigned(node.successor)) and not(Result) and not(is_void(current_procinfo.procdef.returndef)) and
|
|
not(assigned(texitnode(node).resultexpr)) and
|
|
{ don't warn about constructors }
|
|
not(current_procinfo.procdef.proctypeoption in [potype_class_constructor,potype_constructor]) then
|
|
begin
|
|
if is_managed_type(current_procinfo.procdef.returndef) then
|
|
MessagePos(node.fileinfo,sym_w_managed_function_result_uninitialized)
|
|
else
|
|
MessagePos(node.fileinfo,sym_w_function_result_uninitialized);
|
|
|
|
Setlength(SearchNodeInfo.warnedfilelocs,length(SearchNodeInfo.warnedfilelocs)+1);
|
|
SearchNodeInfo.warnedfilelocs[high(SearchNodeInfo.warnedfilelocs)]:=node.fileinfo;
|
|
end
|
|
end;
|
|
{ could be the implicitly generated load node for the result }
|
|
{$ifdef JVM}
|
|
{ all other platforms except jvm translate raise nodes into call nodes during pass_1 }
|
|
raisen,
|
|
{$endif JVM}
|
|
labeln,
|
|
loadn,
|
|
assignn,
|
|
calln,
|
|
temprefn,
|
|
typeconvn,
|
|
inlinen,
|
|
tempcreaten,
|
|
tempdeleten:
|
|
MaybeSearchIn(node);
|
|
nothingn,
|
|
continuen,
|
|
goton,
|
|
breakn:
|
|
;
|
|
else
|
|
internalerror(2013111301);
|
|
end;
|
|
|
|
{ if already a warning has been issued, then stop }
|
|
if Result then
|
|
exit;
|
|
|
|
if assigned(node.successor) then
|
|
MaybeDoCheck(node.successor);
|
|
end;
|
|
|
|
begin
|
|
SearchNodeInfo.nodetosearch:=nodetosearch;
|
|
DoCheck(code);
|
|
foreachnodestatic(pm_postprocess,code,@ResetProcessing,nil);
|
|
end;
|
|
|
|
|
|
end.
|