tcpas2jsanalyzer.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. unit TCPas2JSAnalyzer;
  2. {$mode ObjFPC}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils, fpcunit, testregistry, StrUtils, TCModules, PasTree,
  6. PScanner, PasResolver, PasUseAnalyzer, PasResolveEval, Pas2jsUseAnalyzer;
  7. type
  8. { TCustomTestPas2jsAnalyzer }
  9. TCustomTestPas2jsAnalyzer = class(TCustomTestModule)
  10. private
  11. FAnalyzer: TPas2JSAnalyzer;
  12. FPAMessages: TFPList; // list of TPAMessage
  13. FPAGoodMessages: TFPList;
  14. FProcAnalyzer: TPas2JSAnalyzer;
  15. function GetPAMessages(Index: integer): TPAMessage;
  16. procedure OnAnalyzerMessage(Sender: TObject; Msg: TPAMessage);
  17. protected
  18. procedure SetUp; override;
  19. procedure TearDown; override;
  20. procedure ParseModule; override;
  21. procedure AnalyzeModule; virtual;
  22. procedure AnalyzeProgram; virtual;
  23. procedure AnalyzeUnit; virtual;
  24. procedure AnalyzeWholeProgram; virtual;
  25. procedure CheckUsedMarkers; virtual;
  26. procedure CheckUseAnalyzerHint(MsgType: TMessageType; MsgNumber: integer;
  27. const MsgText: string); virtual;
  28. procedure CheckUseAnalyzerUnexpectedHints; virtual;
  29. procedure CheckUnitUsed(const aFilename: string; Used: boolean); virtual;
  30. procedure CheckScopeReferences(const ScopeName: string;
  31. const RefNames: array of string);
  32. public
  33. property Analyzer: TPas2JSAnalyzer read FAnalyzer;
  34. property ProcAnalyzer: TPas2JSAnalyzer read FProcAnalyzer;
  35. function PAMessageCount: integer;
  36. property PAMessages[Index: integer]: TPAMessage read GetPAMessages;
  37. end;
  38. { TTestPas2jsAnalyzer }
  39. TTestPas2jsAnalyzer = class(TCustomTestPas2jsAnalyzer)
  40. Published
  41. procedure TestM_ProgramLocalVar;
  42. procedure TestM_PassRecordToJSValue;
  43. end;
  44. implementation
  45. { TTestPas2jsAnalyzer }
  46. procedure TTestPas2jsAnalyzer.TestM_ProgramLocalVar;
  47. begin
  48. StartProgram(false);
  49. Add([
  50. 'procedure {#DoIt_used}DoIt;',
  51. 'var {#l_notused}l: longint;',
  52. 'begin',
  53. 'end;',
  54. 'begin',
  55. ' DoIt;',
  56. 'end.']);
  57. AnalyzeProgram;
  58. end;
  59. procedure TTestPas2jsAnalyzer.TestM_PassRecordToJSValue;
  60. begin
  61. StartProgram(false);
  62. Add([
  63. 'type',
  64. ' {#trec_used}TRec = record',
  65. ' {#x_used}x: word;',
  66. ' end;',
  67. ' {#tbig_used}TBig = record',
  68. ' {#r_used}r: TRec;',
  69. ' end;',
  70. ' {#tnope_used}TNope = record',
  71. ' {#a_notused}a: word;',
  72. ' {#b_used}b: word;',
  73. ' end;',
  74. 'procedure DoIt(v: JSValue);',
  75. 'begin',
  76. 'end;',
  77. 'var big: TBig;',
  78. ' n: TNope;',
  79. 'begin',
  80. ' DoIt(big);',
  81. ' DoIt(n.b);',
  82. 'end.']);
  83. AnalyzeProgram;
  84. end;
  85. { TCustomTestPas2jsAnalyzer }
  86. function TCustomTestPas2jsAnalyzer.GetPAMessages(Index: integer): TPAMessage;
  87. begin
  88. Result:=TPAMessage(FPAMessages[Index]);
  89. end;
  90. procedure TCustomTestPas2jsAnalyzer.OnAnalyzerMessage(Sender: TObject;
  91. Msg: TPAMessage);
  92. begin
  93. Msg.AddRef;
  94. FPAMessages.Add(Msg);
  95. end;
  96. procedure TCustomTestPas2jsAnalyzer.SetUp;
  97. begin
  98. inherited SetUp;
  99. FPAMessages:=TFPList.Create;
  100. FPAGoodMessages:=TFPList.Create;
  101. FAnalyzer:=TPas2JSAnalyzer.Create;
  102. FAnalyzer.Resolver:=ResolverEngine;
  103. Analyzer.OnMessage:=@OnAnalyzerMessage;
  104. end;
  105. procedure TCustomTestPas2jsAnalyzer.TearDown;
  106. var
  107. i: Integer;
  108. begin
  109. FreeAndNil(FPAGoodMessages);
  110. for i:=0 to FPAMessages.Count-1 do
  111. TPAMessage(FPAMessages[i]).Release;
  112. FreeAndNil(FPAMessages);
  113. FreeAndNil(FAnalyzer);
  114. FreeAndNil(FProcAnalyzer);
  115. inherited TearDown;
  116. end;
  117. procedure TCustomTestPas2jsAnalyzer.ParseModule;
  118. begin
  119. inherited ParseModule;
  120. if SkipTests then exit;
  121. CheckReferenceDirectives;
  122. end;
  123. procedure TCustomTestPas2jsAnalyzer.AnalyzeModule;
  124. begin
  125. Analyzer.AnalyzeModule(Module);
  126. Analyzer.EmitModuleHints(Module);
  127. CheckUsedMarkers;
  128. end;
  129. procedure TCustomTestPas2jsAnalyzer.AnalyzeProgram;
  130. begin
  131. ParseProgram;
  132. AnalyzeModule;
  133. end;
  134. procedure TCustomTestPas2jsAnalyzer.AnalyzeUnit;
  135. begin
  136. ParseUnit;
  137. AnalyzeModule;
  138. end;
  139. procedure TCustomTestPas2jsAnalyzer.AnalyzeWholeProgram;
  140. begin
  141. ParseProgram;
  142. Analyzer.AnalyzeWholeProgram(Module as TPasProgram);
  143. CheckUsedMarkers;
  144. end;
  145. procedure TCustomTestPas2jsAnalyzer.CheckUsedMarkers;
  146. type
  147. TUsed = (
  148. uUsed,
  149. uNotUsed,
  150. uTypeInfo,
  151. uNoTypeinfo
  152. );
  153. var
  154. aMarker: PSrcMarker;
  155. p: SizeInt;
  156. Postfix: String;
  157. Elements: TFPList;
  158. i: Integer;
  159. El, FoundEl: TPasElement;
  160. ExpectedUsed: TUsed;
  161. begin
  162. aMarker:=FirstSrcMarker;
  163. while aMarker<>nil do
  164. begin
  165. {$IFDEF VerbosePasAnalyzer}
  166. writeln('TCustomTestPas2jsAnalyzer.CheckUsedMarkers ',aMarker^.Identifier,' Line=',aMarker^.Row,' StartCol=',aMarker^.StartCol,' EndCol=',aMarker^.EndCol);
  167. {$ENDIF}
  168. p:=RPos('_',aMarker^.Identifier);
  169. if p>1 then
  170. begin
  171. Postfix:=copy(aMarker^.Identifier,p+1);
  172. if Postfix='used' then
  173. ExpectedUsed:=uUsed
  174. else if Postfix='notused' then
  175. ExpectedUsed:=uNotUsed
  176. else if Postfix='typeinfo' then
  177. ExpectedUsed:=uTypeInfo
  178. else if Postfix='notypeinfo' then
  179. ExpectedUsed:=uNoTypeInfo
  180. else
  181. RaiseErrorAtSrcMarker('TCustomTestPas2jsAnalyzer.CheckUsedMarkers unknown postfix "'+Postfix+'"',aMarker);
  182. Elements:=FindElementsAt(aMarker);
  183. try
  184. FoundEl:=nil;
  185. for i:=0 to Elements.Count-1 do
  186. begin
  187. El:=TPasElement(Elements[i]);
  188. {$IFDEF VerbosePasAnalyzer}
  189. writeln('TCustomTestPas2jsAnalyzer.CheckUsedMarkers ',aMarker^.Identifier,' ',i,'/',Elements.Count,' El=',GetObjName(El),' ',GetObjName(El.CustomData));
  190. {$ENDIF}
  191. case ExpectedUsed of
  192. uUsed,uNotUsed:
  193. if Analyzer.IsUsed(El) then
  194. begin
  195. FoundEl:=El;
  196. break;
  197. end;
  198. uTypeInfo,uNoTypeinfo:
  199. if Analyzer.IsTypeInfoUsed(El) then
  200. begin
  201. FoundEl:=El;
  202. break;
  203. end;
  204. end;
  205. end;
  206. if FoundEl<>nil then
  207. case ExpectedUsed of
  208. uNotUsed:
  209. RaiseErrorAtSrcMarker('expected element to be *not* used, but it is marked',aMarker);
  210. uNoTypeinfo:
  211. RaiseErrorAtSrcMarker('expected element to have *no* typeinfo, but it is marked',aMarker);
  212. end
  213. else
  214. case ExpectedUsed of
  215. uUsed:
  216. RaiseErrorAtSrcMarker('expected element to be used, but it is not marked',aMarker);
  217. uTypeInfo:
  218. RaiseErrorAtSrcMarker('expected element to have typeinfo, but it is not marked',aMarker);
  219. end;
  220. finally
  221. Elements.Free;
  222. end;
  223. end;
  224. aMarker:=aMarker^.Next;
  225. end;
  226. end;
  227. procedure TCustomTestPas2jsAnalyzer.CheckUseAnalyzerHint(MsgType: TMessageType;
  228. MsgNumber: integer; const MsgText: string);
  229. var
  230. i: Integer;
  231. Msg: TPAMessage;
  232. s: string;
  233. begin
  234. i:=PAMessageCount-1;
  235. while i>=0 do
  236. begin
  237. Msg:=PAMessages[i];
  238. if (Msg.MsgNumber=MsgNumber) then
  239. begin
  240. if (Msg.MsgType=MsgType) and (Msg.MsgText=MsgText) then
  241. begin
  242. FPAGoodMessages.Add(Msg);
  243. exit;
  244. end;
  245. end;
  246. dec(i);
  247. end;
  248. // mismatch
  249. writeln('TCustomTestPas2jsAnalyzer.CheckHasHint: ');
  250. for i:=0 to PAMessageCount-1 do
  251. begin
  252. Msg:=PAMessages[i];
  253. writeln(' ',i,'/',PAMessageCount,': [',Msg.Id,'] ',Msg.MsgType,': (',Msg.MsgNumber,') {',Msg.MsgText,'}');
  254. end;
  255. s:='';
  256. str(MsgType,s);
  257. Fail('Analyzer Message not found: '+s+': ('+IntToStr(MsgNumber)+') {'+MsgText+'}');
  258. end;
  259. procedure TCustomTestPas2jsAnalyzer.CheckUseAnalyzerUnexpectedHints;
  260. var
  261. i: Integer;
  262. Msg: TPAMessage;
  263. s: String;
  264. begin
  265. for i:=0 to PAMessageCount-1 do
  266. begin
  267. Msg:=PAMessages[i];
  268. if FPAGoodMessages.IndexOf(Msg)>=0 then continue;
  269. s:='';
  270. str(Msg.MsgType,s);
  271. Fail('Unexpected analyzer message found ['+IntToStr(Msg.Id)+'] '+s+': ('+IntToStr(Msg.MsgNumber)+') {'+Msg.MsgText+'}');
  272. end;
  273. end;
  274. procedure TCustomTestPas2jsAnalyzer.CheckUnitUsed(const aFilename: string;
  275. Used: boolean);
  276. var
  277. aResolver: TTestEnginePasResolver;
  278. PAEl: TPAElement;
  279. begin
  280. aResolver:=FindModuleWithFilename(aFilename);
  281. AssertNotNull('unit not found "'+aFilename+'"',aResolver);
  282. AssertNotNull('unit module not found "'+aFilename+'"',aResolver.Module);
  283. PAEl:=Analyzer.FindElement(aResolver.Module);
  284. if PAEl<>nil then
  285. begin
  286. // unit is used
  287. if not Used then
  288. Fail('expected unit "'+aFilename+'" not used, but it is used');
  289. end
  290. else
  291. begin
  292. // unit is not used
  293. if Used then
  294. Fail('expected unit "'+aFilename+'" used, but it is not used');
  295. end;
  296. end;
  297. procedure TCustomTestPas2jsAnalyzer.CheckScopeReferences(
  298. const ScopeName: string; const RefNames: array of string);
  299. type
  300. TEntry = record
  301. Name: string;
  302. Access: TPSRefAccess;
  303. end;
  304. var
  305. Entries: array of TEntry;
  306. procedure CheckRefs(ScopeRefs: TPasScopeReferences; const Prefix: string);
  307. procedure DumpRefsAndFail(Refs: TFPList; const Msg: string);
  308. var
  309. i: Integer;
  310. Ref: TPasScopeReference;
  311. begin
  312. {$IFDEF VerbosePasAnalyzer}
  313. if Refs.Count=0 then
  314. writeln('DumpRefsAndFail ',Prefix,' NO REFS');
  315. {$ENDIF}
  316. for i:=0 to Refs.Count-1 do
  317. begin
  318. Ref:=TPasScopeReference(Refs[i]);
  319. if Ref=nil then break;
  320. {$IFDEF VerbosePasAnalyzer}
  321. writeln('DumpRefsAndFail ',Prefix,' ',i,' ',GetObjName(Ref.Element),' ',Ref.Access);
  322. {$ENDIF}
  323. end;
  324. Fail(Prefix+': '+Msg);
  325. end;
  326. var
  327. Refs: TFPList;
  328. j, i: Integer;
  329. o: TObject;
  330. Ref: TPasScopeReference;
  331. begin
  332. if ScopeRefs=nil then
  333. Refs:=TFPList.Create
  334. else
  335. Refs:=ScopeRefs.GetList;
  336. try
  337. // check that Refs only contains TPasProcScopeReference
  338. for i:=0 to Refs.Count-1 do
  339. begin
  340. o:=TObject(Refs[i]);
  341. if not (o is TPasScopeReference) then
  342. Fail(Prefix+': Refs['+IntToStr(i)+'] '+GetObjName(o));
  343. end;
  344. // check that all Entries are referenced
  345. for i:=0 to length(Entries)-1 do
  346. begin
  347. j:=Refs.Count-1;
  348. while (j>=0)
  349. and (CompareText(Entries[i].Name,TPasScopeReference(Refs[j]).Element.Name)<>0) do
  350. dec(j);
  351. if j<0 then
  352. DumpRefsAndFail(Refs,'Missing reference "'+Entries[i].Name+'"');
  353. Ref:=TPasScopeReference(Refs[j]);
  354. if (Entries[i].Access<>psraNone) and (Ref.Access<>Entries[i].Access) then
  355. DumpRefsAndFail(Refs,'Wrong reference access "'+Entries[i].Name+'",'
  356. +' expected '+dbgs(Entries[i].Access)+', but got '+dbgs(Ref.Access));
  357. end;
  358. // check that no other references are in Refs
  359. for i:=0 to Refs.Count-1 do
  360. begin
  361. Ref:=TPasScopeReference(Refs[i]);
  362. j:=length(Entries)-1;
  363. while (j>=0)
  364. and (CompareText(Ref.Element.Name,Entries[j].Name)<>0) do
  365. dec(j);
  366. if j<0 then
  367. DumpRefsAndFail(Refs,'Unneeded reference "'+GetObjName(Ref.Element)+'"');
  368. end;
  369. finally
  370. Refs.Free;
  371. end;
  372. end;
  373. function FindProc(Section: TPasSection): boolean;
  374. var
  375. i: Integer;
  376. El: TPasElement;
  377. Proc: TPasProcedure;
  378. Scope: TPasProcedureScope;
  379. begin
  380. for i:=0 to Section.Declarations.Count-1 do
  381. begin
  382. El:=TPasElement(Section.Declarations[i]);
  383. if CompareText(El.Name,ScopeName)<>0 then continue;
  384. if not (El is TPasProcedure) then
  385. Fail('El is not proc '+GetObjName(El));
  386. Proc:=TPasProcedure(El);
  387. Scope:=Proc.CustomData as TPasProcedureScope;
  388. if Scope.DeclarationProc<>nil then continue;
  389. // check references created by AnalyzeModule
  390. CheckRefs(Scope.References,'AnalyzeModule');
  391. exit(true);
  392. end;
  393. Result:=false;
  394. end;
  395. procedure CheckInitialFinalization(El: TPasImplBlock);
  396. var
  397. Scope: TPasInitialFinalizationScope;
  398. begin
  399. Scope:=El.CustomData as TPasInitialFinalizationScope;
  400. CheckRefs(Scope.References,'AnalyzeModule');
  401. end;
  402. var
  403. i: Integer;
  404. begin
  405. Entries:=nil;
  406. SetLength(Entries,High(RefNames)-low(RefNames)+1);
  407. for i:=low(RefNames) to high(RefNames) do
  408. begin
  409. Entries[i].Name:=RefNames[i];
  410. Entries[i].Access:=psraNone;
  411. end;
  412. if Module is TPasProgram then
  413. begin
  414. if CompareText(ScopeName,'begin')=0 then
  415. begin
  416. // check begin-block references created by AnalyzeModule
  417. CheckInitialFinalization(Module.InitializationSection);
  418. exit;
  419. end
  420. else if FindProc(TPasProgram(Module).ProgramSection) then
  421. exit;
  422. end
  423. else if Module is TPasLibrary then
  424. begin
  425. if CompareText(ScopeName,'begin')=0 then
  426. begin
  427. // check begin-block references created by AnalyzeModule
  428. CheckInitialFinalization(Module.InitializationSection);
  429. exit;
  430. end
  431. else if FindProc(TPasLibrary(Module).LibrarySection) then
  432. exit;
  433. end
  434. else if Module.ClassType=TPasModule then
  435. begin
  436. if CompareText(ScopeName,'initialization')=0 then
  437. begin
  438. // check initialization references created by AnalyzeModule
  439. CheckInitialFinalization(Module.InitializationSection);
  440. exit;
  441. end
  442. else if CompareText(ScopeName,'finalization')=0 then
  443. begin
  444. // check finalization references created by AnalyzeModule
  445. CheckInitialFinalization(Module.FinalizationSection);
  446. exit;
  447. end
  448. else if FindProc(Module.InterfaceSection) then
  449. exit
  450. else if FindProc(Module.ImplementationSection) then
  451. exit;
  452. end;
  453. Fail('missing proc '+ScopeName);
  454. end;
  455. function TCustomTestPas2jsAnalyzer.PAMessageCount: integer;
  456. begin
  457. Result:=FPAMessages.Count;
  458. end;
  459. Initialization
  460. RegisterTests([TTestPas2jsAnalyzer]);
  461. end.