fpc/compiler/parser.pas
svenbarth 88af293155 Fix for Mantis #22160
The cause of the internal error was the following:
We have a generic in an unit ("A") which uses another unit ("B") in the implementation section and this other unit uses unit A in the interface section. Now the generic is specialized in the interface section of B. This leads to the problem that in unit A when it tries to load the globalsymtable of unit B that globalsymtable will be Nil, because parsing of the interface section is not yet finished. Thus the change in pgenutil.pas, specialization_init: if the unit is still "in_interface" the localsymtable needs to be used instead of the globalsymtable.

This doesn't necessarily lead to a compiling test though, as there is the following possibility:
Unit A contains a generic class/record (with methods) and uses unit B in the implementation section. This unit B also contains a generic class/record (with methods) and uses unit A in the implementation section. Both units contain a specialization of the other unit's generic outside of it's own generics (such that generate_specialization is fully triggered). Let's assume compilation starts with unit A and we reach the uses of unit B. Now compilation switches to unit B and completes as unit A is already registered and in compilation. The problem now is that the generic in unit A still contains unresolved forward declarations as the implementation section of A was not yet parsed which will lead to "forward declaration not solved" errors (Note: Delphi compiles this).

The solution to this is the following: if a generic is specialized from another unit which is not in state ms_compiled then the unit of the specialization needs to wait for the unit of the generic. So the specialization's unit adds itself into a list of waiting units of the generic's unit. Now inside "proc_unit" we need to check whether this module is waiting for other modules and if so avoid "finishing" the unit (which means generating the methods of the specialization, generating assembler code and ultimately freeing the scanner and PPU). Now when the generic's unit finishes we need to check whether other modules are waiting for it and finish them (of course it's a bit more complicated in reality, but that pretty much sums it up).

+ globstat.pas: Added an unit which handles the saving and restoring of the global state which was originally inside "parser.pas, compile" so that Don't Repeat Yourself (DRY) is respected.
* fmodule.pas, tmodule: 
  + add fields to keep track of the units the module is waiting for and which modules are waiting for the module
  + add field for the saved global state (raw pointer to avoid circles)
  + add field for the state which is needed to finish the unit (raw pointer to avoid circles)
  + move the code which was used in "parser.pas, compile" after a module was successfully compiled to the new virtual method "end_of_parsing"
+ fppu.pas, tppumodule.end_of_parsing:
  free the ppufile here
* pgenutil.pas:
  + add new procedure "maybe_add_waiting_unit" which adds the specialization's unit to the waiting list of the generic if that unit is not yet compiled
  * generate_specialization: call the new function when we add a new (true) specialization
  * specialization_init: instead of not adding implementation units at all check whether the unit is still parsing the interface section and add the localsymtable in that case
* pmodules.pas:
  * change "proc_unit" to a function which returns "true" if the unit was already finished (no need to wait for other units)
  + move the code from "proc_unit" from "generate_specialization_procs" on to a new procedure "finish_unit" which
  * this procedure is either called immediately in "proc_unit" if the unit does not need to wait for other units or from "finish_unit" itself if a unit that is waiting for the given unit does no longer wait for another module (special care is taken in proc_unit to avoid circles)
* parser.pas, compile:
  * correctly handle the case if an unit is not finished
  * use the new global state functionality from globstat.pas
  * pay special attention when calling "set_current_module" (see comment at that call)

+ add tests from 22160
+ add test for above mentioned "diamond" case

git-svn-id: trunk@22452 -
2012-09-25 09:45:25 +00:00

448 lines
14 KiB
ObjectPascal

{
Copyright (c) 1998-2002 by Florian Klaempfl
This unit does the parsing process
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.
****************************************************************************
}
unit parser;
{$i fpcdefs.inc}
interface
{$ifdef PREPROCWRITE}
procedure preprocess(const filename:string);
{$endif PREPROCWRITE}
procedure compile(const filename:string);
procedure initparser;
procedure doneparser;
implementation
uses
{$IFNDEF USE_FAKE_SYSUTILS}
sysutils,
{$ELSE}
fksysutl,
{$ENDIF}
cutils,cclasses,
globtype,version,tokens,systems,globals,verbose,switches,globstat,
symbase,symtable,symdef,symsym,
finput,fmodule,fppu,
aasmbase,aasmtai,aasmdata,
cgbase,
script,gendef,
comphook,
scanner,scandir,
pbase,ptype,psystem,pmodules,psub,ncgrtti,htypechk,
cresstr,cpuinfo,procinfo;
procedure initparser;
begin
{ Current compiled module/proc }
set_current_module(nil);
current_module:=nil;
current_asmdata:=nil;
current_procinfo:=nil;
current_structdef:=nil;
current_genericdef:=nil;
current_specializedef:=nil;
loaded_units:=TLinkedList.Create;
usedunits:=TLinkedList.Create;
unloaded_units:=TLinkedList.Create;
{ global switches }
current_settings.globalswitches:=init_settings.globalswitches;
current_settings.sourcecodepage:=init_settings.sourcecodepage;
{ initialize scanner }
InitScanner;
InitScannerDirectives;
{ scanner }
c:=#0;
pattern:='';
orgpattern:='';
cstringpattern:='';
current_scanner:=nil;
switchesstatestackpos:=0;
{ register all nodes and tais }
registernodes;
registertais;
{ memory sizes }
if stacksize=0 then
stacksize:=target_info.stacksize;
{ RTTI writer }
RTTIWriter:=TRTTIWriter.Create;
{ open assembler response }
if cs_link_on_target in current_settings.globalswitches then
GenerateAsmRes(outputexedir+ChangeFileExt(inputfilename,'_ppas'))
else
GenerateAsmRes(outputexedir+'ppas');
{ open deffile }
DefFile:=TDefFile.Create(outputexedir+ChangeFileExt(inputfilename,target_info.defext));
{ list of generated .o files, so the linker can remove them }
SmartLinkOFiles:=TCmdStrList.Create;
{ codegen }
if paraprintnodetree<>0 then
printnode_reset;
{ target specific stuff }
case target_info.system of
system_powerpc_amiga:
include(supported_calling_conventions,pocall_syscall);
system_powerpc_morphos:
include(supported_calling_conventions,pocall_syscall);
system_m68k_amiga:
include(supported_calling_conventions,pocall_syscall);
end;
end;
procedure doneparser;
begin
{ Reset current compiling info, so destroy routines can't
reference the data that might already be destroyed }
set_current_module(nil);
current_module:=nil;
current_procinfo:=nil;
current_asmdata:=nil;
current_structdef:=nil;
current_genericdef:=nil;
current_specializedef:=nil;
{ unload units }
if assigned(loaded_units) then
begin
loaded_units.free;
loaded_units:=nil;
end;
if assigned(usedunits) then
begin
usedunits.free;
usedunits:=nil;
end;
if assigned(unloaded_units) then
begin
unloaded_units.free;
unloaded_units:=nil;
end;
{ if there was an error in the scanner, the scanner is
still assinged }
if assigned(current_scanner) then
begin
current_scanner.free;
current_scanner:=nil;
end;
{ close scanner }
DoneScanner;
RTTIWriter.free;
{ close ppas,deffile }
asmres.free;
deffile.free;
{ free list of .o files }
SmartLinkOFiles.Free;
end;
{$ifdef PREPROCWRITE}
procedure preprocess(const filename:string);
var
i : longint;
begin
new(preprocfile,init('pre'));
{ initialize a module }
set_current_module(new(pmodule,init(filename,false)));
macrosymtablestack:= initialmacrosymtable;
current_module.localmacrosymtable:= tmacrosymtable.create(false);
current_module.localmacrosymtable.next:= initialmacrosymtable;
macrosymtablestack:= current_module.localmacrosymtable;
main_module:=current_module;
{ startup scanner, and save in current_module }
current_scanner:=new(pscannerfile,Init(filename));
current_module.scanner:=current_scanner;
{ loop until EOF is found }
repeat
current_scanner^.readtoken(true);
preprocfile^.AddSpace;
case token of
_ID :
begin
preprocfile^.Add(orgpattern);
end;
_REALNUMBER,
_INTCONST :
preprocfile^.Add(pattern);
_CSTRING :
begin
i:=0;
while (i<length(cstringpattern)) do
begin
inc(i);
if cstringpattern[i]='''' then
begin
insert('''',cstringpattern,i);
inc(i);
end;
end;
preprocfile^.Add(''''+cstringpattern+'''');
end;
_CCHAR :
begin
case pattern[1] of
#39 :
pattern:='''''''';
#0..#31,
#128..#255 :
begin
str(ord(pattern[1]),pattern);
pattern:='#'+pattern;
end;
else
pattern:=''''+pattern[1]+'''';
end;
preprocfile^.Add(pattern);
end;
_EOF :
break;
else
preprocfile^.Add(tokeninfo^[token].str)
end;
until false;
{ free scanner }
dispose(current_scanner,done);
current_scanner:=nil;
{ close }
dispose(preprocfile,done);
end;
{$endif PREPROCWRITE}
{*****************************************************************************
Compile a source file
*****************************************************************************}
procedure compile(const filename:string);
var
olddata : pglobalstate;
hp,hp2 : tmodule;
finished : boolean;
begin
{ parsing a procedure or declaration should be finished }
if assigned(current_procinfo) then
internalerror(200811121);
if assigned(current_structdef) then
internalerror(200811122);
inc(compile_level);
parser_current_file:=filename;
{ Uses heap memory instead of placing everything on the
stack. This is needed because compile() can be called
recursively }
new(olddata);
{ handle the postponed case first }
flushpendingswitchesstate;
save_global_state(olddata^,false);
{ reset parser, a previous fatal error could have left these variables in an unreliable state, this is
important for the IDE }
afterassignment:=false;
in_args:=false;
named_args_allowed:=false;
got_addrn:=false;
getprocvardef:=nil;
allow_array_constructor:=false;
{ show info }
Message1(parser_i_compiling,filename);
{ reset symtable }
symtablestack:=tdefawaresymtablestack.create;
macrosymtablestack:=TSymtablestack.create;
systemunit:=nil;
current_settings.defproccall:=init_settings.defproccall;
current_exceptblock:=0;
exceptblockcounter:=0;
current_settings.maxfpuregisters:=-1;
current_settings.pmessage:=nil;
{ reset the unit or create a new program }
{ a unit compiled at command line must be inside the loaded_unit list }
if (compile_level=1) then
begin
if assigned(current_module) then
internalerror(200501158);
set_current_module(tppumodule.create(nil,'',filename,false));
addloadedunit(current_module);
main_module:=current_module;
current_module.state:=ms_compile;
end;
if not(assigned(current_module) and
(current_module.state in [ms_compile,ms_second_compile])) then
internalerror(200212281);
{ Load current state from the init values }
current_settings:=init_settings;
{ load current asmdata from current_module }
current_asmdata:=TAsmData(current_module.asmdata);
{ startup scanner and load the first file }
current_scanner:=tscannerfile.Create(filename);
current_scanner.firstfile;
current_module.scanner:=current_scanner;
{ init macros before anything in the file is parsed.}
current_module.localmacrosymtable:= tmacrosymtable.create(false);
macrosymtablestack.push(initialmacrosymtable);
macrosymtablestack.push(current_module.localmacrosymtable);
{ read the first token }
current_scanner.readtoken(false);
{ this is set to false if a unit needs to wait for other units }
finished:=true;
{ If the compile level > 1 we get a nice "unit expected" error
message if we are trying to use a program as unit.}
try
try
if (token=_UNIT) or (compile_level>1) then
begin
current_module.is_unit:=true;
finished:=proc_unit;
end
else if (token=_ID) and (idtoken=_PACKAGE) then
begin
current_module.IsPackage:=true;
proc_package;
end
else
proc_program(token=_LIBRARY);
except
on ECompilerAbort do
raise;
on Exception do
begin
{ Increase errorcounter to prevent some
checks during cleanup }
inc(status.errorcount);
raise;
end;
end;
{ the program or the unit at the command line should not need to wait
for other units }
if (compile_level=1) and not finished then
internalerror(2012091901);
finally
if assigned(current_module) then
begin
if finished then
current_module.end_of_parsing
else
begin
{ these are saved in the unit's state and thus can be set to
Nil again as would be done by tmodule.end_of_parsing }
macrosymtablestack:=nil;
symtablestack:=nil;
if current_scanner=current_module.scanner then
current_scanner:=nil;
end;
end;
if (compile_level=1) and
(status.errorcount=0) then
{ Write Browser Collections }
do_extractsymbolinfo;
restore_global_state(olddata^,false);
{ Restore all locally modified warning messages }
RestoreLocalVerbosity(current_settings.pmessage);
current_exceptblock:=0;
exceptblockcounter:=0;
{ Shut down things when the last file is compiled succesfull }
if (compile_level=1) and
(status.errorcount=0) then
begin
parser_current_file:='';
{ Close script }
if (not AsmRes.Empty) then
begin
Message1(exec_i_closing_script,AsmRes.Fn);
AsmRes.WriteToDisk;
end;
end;
{ free now what we did not free earlier in
proc_program PM }
if (compile_level=1) and needsymbolinfo then
begin
hp:=tmodule(loaded_units.first);
while assigned(hp) do
begin
hp2:=tmodule(hp.next);
if (hp<>current_module) then
begin
loaded_units.remove(hp);
hp.free;
end;
hp:=hp2;
end;
{ free also unneeded units we didn't free before }
unloaded_units.Clear;
end;
dec(compile_level);
{ If used units are compiled current_module is already the same as
the stored module. Now if the unit is not finished its scanner is
not yet freed and thus set_current_module would reopen the scanned
file which will result in pointing to the wrong position in the
file. In the normal case current_scanner and current_module.scanner
would be Nil, thus nothing bad would happen }
if olddata^.old_current_module<>current_module then
set_current_module(olddata^.old_current_module);
FreeLocalVerbosity(current_settings.pmessage);
dispose(olddata);
end;
end;
end.