tcpas2jsanalyzer.pas 14 KB

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