1(* $Id: CheckUses.Mod,v 1.25 2004/05/08 13:34:46 mva Exp $ *) 2MODULE OOC:IR:CheckUses; 3(* Warn about uninitialized variables and unreachable code, 4 Copyright (C) 2003, 2004 Michael van Acken, Stewart Greenhill 5 6 This file is part of OOC. 7 8 OOC is free software; you can redistribute it and/or modify it 9 under the terms of the GNU General Public License as published by 10 the Free Software Foundation; either version 2 of the License, or 11 (at your option) any later version. 12 13 OOC is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 15 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 16 License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with OOC. If not, write to the Free Software Foundation, 59 20 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 21*) 22 23IMPORT 24 Object, Object:Boxed, ADT:Dictionary, DictInt := ADT:Dictionary:IntValue, 25 ADT:ArrayList, Config, Msg, 26 OOC:Config:Pragmas, OOC:Config:StdPragmas, 27 Sym := OOC:SymbolTable, TR := OOC:SymbolTable:TypeRules, 28 OOC:SymbolTable:Predef, 29 OOC:Error, OOC:Scanner:SymList, OOC:IR, OOC:IR:VisitAll; 30 31 32(* Some notes on the dataflow analysis of this module: 33 34 @itemize @bullet 35 @item 36 It tracks reads and writes to nonlocal variables and variable parameters. 37 38 @item 39 Calls to and between nested procedures are taken into account. 40 41 @item 42 For read and write operations on tracked variables, it is precise down to 43 the level of statements and expressions. That is, it can distinguish ``read 44 before write'' and ``write before read'' situations. 45 46 @item 47 A structured variable, like a records and an array, is treated like an 48 atomic variable. The first assignment to one of its members is assumed to 49 define the whole value, with the effect that all following reads are assumed 50 to be valid. 51 52 @item 53 The algorithm distinguishes situations where no, all, or some paths leading 54 to a read have a defined value. 55 56 @item 57 For heap objects, only writes are tracked. The information is used to 58 set @ofield{Sym.ProcDecl.nonlocalWrites}. 59 @end itemize *) 60 61CONST 62 undefined = 0; 63 maybeUndefined = 1; 64 defined = 2; 65 66TYPE 67 Def = POINTER TO DefDesc; 68 DefDesc = RECORD 69 (Object.ObjectDesc) 70 class: DictInt.Dictionary; (* of Sym.VarDecl or Sym.Type *) 71 (* Set of variables that haven been seen at a given point in the 72 statement sequence. Each variables is classified as either 73 @oconst{undefined}, @oconst{maybeUndefined}, or @oconst{defined}. *) 74 unreachable: BOOLEAN; 75 (* If TRUE, then there is no path of control leading to this point. *) 76 END; 77 78CONST 79 doesNotRead = 0; 80 doesRead = 1; 81 (* At least one path through the procedure reads the value of the variable 82 from before the procedure call. *) 83 84 doesNotWrite = 0; 85 mayWrite = 1; 86 (* Some, but not all, paths through the procedure write to the variable. *) 87 doesWrite = 2; 88 (* All paths through the procedure write to the variable. *) 89 90 scaleR = 4; 91 92TYPE 93 ProcData = POINTER TO ProcDataDesc; 94 ProcDataDesc = RECORD 95 (Object.ObjectDesc) 96 callers: ArrayList.ArrayList; (* of Sym.ProcDecl *) 97 (* List of known callers of this procedure. *) 98 inout: DictInt.Dictionary; (* of Sym.VarDecl or Sym.Type *) 99 (* Maps a variable to `w+scaleR*r', where `r' and `w' are one of the 100 `xxxRead' and `xxxWrite' flags above. Only nonlocal variables and 101 variable parameters appear here. If a variable is not in the 102 dictionary, then this is equivalent to `doesNotRead' and `doesNotWrite'. *) 103 inWorklist: BOOLEAN; 104 updatedInOut: BOOLEAN; 105 END; 106 107CONST 108 read = 0; 109 write = 1; 110 writeMaybe = 2; 111 passCollectWrites = 0; 112 passAnalyzeCalls = 1; 113 passEmitWarnings = 2; 114 115TYPE 116 Symbol = SymList.Symbol; 117 Visitor = POINTER TO VisitorDesc; 118 VisitorDesc = RECORD 119 (VisitAll.VisitorDesc) 120 errList: Error.List; 121 pragmaHistory: Pragmas.History; 122 pass: SHORTINT; 123 knownProcs: Dictionary.Dictionary; (* of Sym.ProcDecl *) 124 125 procExit: Def; 126 localProc: Sym.ProcDecl; 127 localProcData: ProcData; 128 129 def: Def; 130 mode: SHORTINT; (* either `read', `write', or `writeMaybe' *) 131 END; 132 VisitorCalls = POINTER TO VisitorCallsDesc; 133 VisitorCallsDesc = RECORD 134 (VisitAll.VisitorDesc) 135 localProc: Sym.ProcDecl; 136 knownProcs: Dictionary.Dictionary; (* of Sym.ProcDecl *) 137 END; 138 139CONST 140 undefinedVar = 1; 141 maybeUndefinedVar = 2; 142 unreachableCode = 3; 143 endOfFunction = 4; 144 deadTypeTest = 5; 145 calleeUndefinedvar = 6; 146 147VAR 148 any: Sym.Type; 149 150TYPE 151 ErrorContext = POINTER TO ErrorContextDesc; 152 ErrorContextDesc = RECORD (* stateless *) 153 (Error.ContextDesc) 154 END; 155 156VAR 157 checkUsesContext: ErrorContext; 158 159PROCEDURE (context: ErrorContext) GetTemplate* (msg: Error.Msg; VAR templ: Error.LString); 160 VAR 161 t: ARRAY 128 OF Error.LChar; 162 BEGIN 163 CASE msg. code OF 164 | undefinedVar: 165 t := Error.warningPrefix+"Undefined variable" 166 | maybeUndefinedVar: 167 t := Error.warningPrefix+"Variable may be undefined" 168 | unreachableCode: 169 t := Error.warningPrefix+"Unreachable code" 170 | endOfFunction: 171 t := Error.warningPrefix+"Control may reach end of function procedure" 172 | deadTypeTest: 173 t := Error.warningPrefix+"Type test made unreachable by preceeding guard" 174 | calleeUndefinedvar: 175 t := Error.warningPrefix+"Callee may read undefined variable `${name}'"; 176 END; 177 context. BaseTemplate (msg, t, templ) 178 END GetTemplate; 179 180 181 182PROCEDURE (v: VisitorCalls) VisitCall* (call: IR.Call); 183 VAR 184 obj: Object.Object; 185 procDecl: Sym.Declaration; 186 BEGIN 187 v.VisitCall^(call); 188 189 IF (call.design IS IR.ProcedureRef) & 190 v.knownProcs.HasKey(call.design(IR.ProcedureRef).decl) THEN 191 procDecl := call.design(IR.ProcedureRef).decl; 192 obj := v.knownProcs.Get(procDecl); 193 obj(ProcData).callers.Append(v.localProc); 194 END; 195 END VisitCall; 196 197PROCEDURE KnownProcs(module: IR.Module): Dictionary.Dictionary; 198(* Create set with all procedures local to the module. These are the 199 procedures that are considered for further analysis in the rest of this 200 module. *) 201 VAR 202 i: LONGINT; 203 knownProcs: Dictionary.Dictionary; 204 v: VisitorCalls; 205 pd: ProcData; 206 BEGIN 207 knownProcs := Dictionary.New(); 208 209 FOR i := 0 TO LEN (module.procList^)-1 DO 210 NEW(pd); 211 pd.callers := ArrayList.New(4); 212 pd.inout := DictInt.New(); 213 pd.inWorklist := TRUE; 214 pd.updatedInOut := FALSE; 215 knownProcs.Set(module.procList[i].decl, pd); 216 END; 217 218 NEW(v); 219 VisitAll.InitVisitor(v); 220 v.knownProcs := knownProcs; 221 FOR i := 0 TO LEN (module.procList^)-1 DO 222 v.localProc := module.procList[i].decl; 223 module.procList[i].Accept(v); 224 END; 225 226 RETURN knownProcs; 227 END KnownProcs; 228 229 230 231PROCEDURE NewDef(): Def; 232 VAR 233 def: Def; 234 BEGIN 235 NEW(def); 236 def.class := DictInt.New(); 237 def.unreachable := FALSE; 238 RETURN def; 239 END NewDef; 240 241PROCEDURE (def: Def) Copy(): Def; 242 VAR 243 def2: Def; 244 BEGIN 245 NEW(def2); 246 def2.class := def.class.Copy(); 247 def2.unreachable := def.unreachable; 248 RETURN def2; 249 END Copy; 250 251PROCEDURE (def: Def) GetClass(var: Sym.Item): LONGINT; 252 BEGIN 253 IF def.class.HasKey(var) THEN 254 RETURN def.class.Get(var); 255 ELSE 256 RETURN undefined; 257 END; 258 END GetClass; 259 260PROCEDURE (def: Def) SetClass(var: Sym.Item; class: LONGINT); 261 BEGIN 262 def.class.Set(var, class); 263 END SetClass; 264 265PROCEDURE (def: Def) Merge(def2: Def); 266 VAR 267 k: Object.ObjectArrayPtr; 268 v: Sym.Item; 269 i: LONGINT; 270 271 PROCEDURE Merge(a, b: LONGINT): LONGINT; 272 BEGIN 273 IF (a = b) & (a # maybeUndefined) THEN 274 RETURN a; 275 ELSE 276 RETURN maybeUndefined; 277 END; 278 END Merge; 279 280 BEGIN 281 IF def.unreachable THEN 282 def.class := def2.class.Copy(); 283 def.unreachable := def2.unreachable; 284 ELSIF ~def2.unreachable THEN (* & ~def.unreachable *) 285 k := def.class.Keys(); 286 FOR i := 0 TO LEN(k^)-1 DO 287 v := k[i](Sym.Item); 288 def.SetClass(v, Merge(def.GetClass(v), def2.GetClass(v))); 289 END; 290 291 k := def2.class.Keys(); 292 FOR i := 0 TO LEN(k^)-1 DO 293 v := k[i](Sym.Item); 294 def.SetClass(v, Merge(def.GetClass(v), def2.GetClass(v))); 295 END; 296 (* ELSE: ~def.unreachable & def2.unreachable *) 297 END; 298 END Merge; 299 300PROCEDURE (def: Def) Unreachable(); 301 BEGIN 302 def.class.Clear(); 303 def.unreachable := TRUE; 304 END Unreachable; 305 306PROCEDURE NewUnreachableDef(): Def; 307 VAR 308 def: Def; 309 BEGIN 310 def := NewDef(); 311 def.Unreachable(); 312 RETURN def; 313 END NewUnreachableDef; 314 315 316PROCEDURE WarnSymE(errList: Error.List; pragmaHistory: Pragmas.History; 317 sym: Symbol; code: Error.Code): Error.Msg; 318 VAR 319 e: Error.Msg; 320 value: Config.Variable; 321 BEGIN 322 value := pragmaHistory.GetValue(StdPragmas.warnings.name, sym.pos); 323 IF value(Config.BooleanVar).boolean THEN 324 e := Error.New(checkUsesContext, code); 325 e.SetIntAttrib("pos", sym.pos); 326 e.SetIntAttrib("line", sym.line); 327 e.SetIntAttrib("column", sym.column); 328 errList.Append (e); 329 RETURN e; 330 ELSE 331 RETURN NIL; 332 END; 333 END WarnSymE; 334 335PROCEDURE WarnSym(errList: Error.List; pragmaHistory: Pragmas.History; 336 sym: Symbol; code: Error.Code); 337 VAR 338 e: Error.Msg; 339 BEGIN 340 e := WarnSymE(errList, pragmaHistory, sym, code); 341 END WarnSym; 342 343PROCEDURE WarnSymV(v: Visitor; sym: Symbol; code: Error.Code; 344 varDecl: Sym.VarDecl); 345 VAR 346 e: Error.Msg; 347 BEGIN 348 e := WarnSymE(v.errList, v.pragmaHistory, sym, code); 349 IF (e # NIL) & (varDecl # NIL) THEN 350 e.SetStringAttrib("name", Msg.GetStringPtr(varDecl.name.str^)); 351 END; 352 END WarnSymV; 353 354 355PROCEDURE NonlocalVar(localProc: Sym.ProcDecl; varDecl: Sym.VarDecl): BOOLEAN; 356(* TRUE iff `varDecl' is a VAR parameter or a variable outside of 357 `localProc'. *) 358 VAR 359 proc: Sym.ProcDecl; 360 BEGIN 361 IF varDecl.isVarParam THEN 362 RETURN TRUE; 363 ELSE 364 proc := varDecl.Procedure(); 365 RETURN (proc # localProc); (* proc=NIL for global variables *) 366 END; 367 END NonlocalVar; 368 369PROCEDURE (pd: ProcData) SetReadFlag(varDecl: Sym.VarDecl; flag: LONGINT); 370 VAR 371 oldFlag, newFlag: LONGINT; 372 BEGIN 373 IF pd.inout.HasKey(varDecl) THEN 374 oldFlag := pd.inout.Get(varDecl); 375 ELSE 376 oldFlag := doesNotWrite + doesNotRead*scaleR; 377 END; 378 newFlag := (oldFlag MOD scaleR)+flag*scaleR; 379 380 IF (newFlag # oldFlag) THEN 381 ASSERT(oldFlag < newFlag); 382 pd.inout.Set(varDecl, newFlag); 383 pd.updatedInOut := TRUE; 384 END; 385 END SetReadFlag; 386 387PROCEDURE (pd: ProcData) SetWriteFlag(item: Sym.Item; flag: LONGINT); 388 VAR 389 oldFlag, newFlag: LONGINT; 390 BEGIN 391 IF pd.inout.HasKey(item) THEN 392 oldFlag := pd.inout.Get(item); 393 ELSE 394 oldFlag := doesNotWrite + doesNotRead*scaleR; 395 END; 396 newFlag := (oldFlag DIV scaleR)*scaleR + flag; 397 398 IF (newFlag # oldFlag) THEN 399 ASSERT(oldFlag > newFlag); 400 pd.inout.Set(item, newFlag); 401 pd.updatedInOut := TRUE; 402 END; 403 END SetWriteFlag; 404 405 406PROCEDURE EmitVarWarning(v: Visitor; varDecl: Sym.VarDecl; 407 sym: Symbol; contextCall: BOOLEAN); 408 VAR 409 class: LONGINT; 410 411 PROCEDURE LocalVar(varDecl: Sym.VarDecl): BOOLEAN; 412 BEGIN 413 RETURN ~varDecl.isParameter & 414 (varDecl.Procedure() = v.localProc); 415 END LocalVar; 416 417 BEGIN 418 IF LocalVar(varDecl) THEN 419 IF (v.mode = read) THEN 420 class := v.def.GetClass(varDecl); 421 CASE class OF 422 | undefined: 423 IF contextCall THEN 424 WarnSymV(v, sym, calleeUndefinedvar, varDecl); 425 ELSE 426 WarnSymV(v, sym, undefinedVar, varDecl); 427 END; 428 | maybeUndefined: 429 IF contextCall THEN 430 WarnSymV(v, sym, calleeUndefinedvar, varDecl); 431 ELSE 432 WarnSymV(v, sym, maybeUndefinedVar, varDecl); 433 END; 434 | defined: 435 (* ignore *) 436 END; 437 ELSIF (v.mode = write) THEN 438 v.def.SetClass(varDecl, defined); 439 ELSIF (v.def.GetClass(varDecl) # defined) THEN (* v.mode=writeMaybe *) 440 v.def.SetClass(varDecl, maybeUndefined); 441 END; 442 END; 443 END EmitVarWarning; 444 445PROCEDURE VisitVarDecl(v: Visitor; varDecl: Sym.VarDecl; 446 sym: Symbol; contextCall: BOOLEAN); 447 VAR 448 pb: Object.Object; 449 class: LONGINT; 450 BEGIN 451 CASE v.pass OF 452 | passCollectWrites: 453 IF (v.mode >= write) & NonlocalVar(v.localProc, varDecl) THEN 454 pb := v.knownProcs.Get(v.localProc); 455 pb(ProcData).inout.Set(varDecl, doesWrite); 456 END; 457 458 | passAnalyzeCalls: 459 IF NonlocalVar(v.localProc, varDecl) THEN 460 IF (v.mode = read) THEN 461 class := v.def.GetClass(varDecl); 462 IF (class # defined) THEN 463 v.localProcData.SetReadFlag(varDecl, doesRead); 464 END; 465 ELSIF (v.mode = write) THEN 466 v.def.SetClass(varDecl, defined); 467 ELSIF (v.def.GetClass(varDecl) # defined) THEN (* v.mode=writeMaybe *) 468 v.def.SetClass(varDecl, maybeUndefined); 469 END; 470 END; 471 472 | passEmitWarnings: 473 EmitVarWarning(v, varDecl, sym, contextCall); 474 END; 475 END VisitVarDecl; 476 477PROCEDURE (v: Visitor) VisitVar* (var: IR.Var); 478 BEGIN 479 VisitVarDecl(v, var.decl(Sym.VarDecl), var.sym, FALSE); 480 END VisitVar; 481 482PROCEDURE (v: Visitor) VisitDeref* (deref: IR.Deref); 483 VAR 484 oldMode: SHORTINT; 485 pb: Object.Object; 486 BEGIN 487 CASE v.pass OF 488 | passCollectWrites: 489 IF (v.mode >= write) THEN 490 pb := v.knownProcs.Get(v.localProc); 491 pb(ProcData).inout.Set(deref.type, doesWrite); 492 END; 493 494 | passAnalyzeCalls, passEmitWarnings: 495 IF (v.mode = read) THEN 496 deref.pointer.Accept(v); 497 ELSE 498 v.def.SetClass(deref.type, defined); 499 oldMode := v.mode; 500 v.mode := read; 501 deref.pointer.Accept(v); 502 v.mode := oldMode; 503 END; 504 END; 505 END VisitDeref; 506 507PROCEDURE (v: Visitor) VisitIndex* (index: IR.Index); 508 VAR 509 oldMode: SHORTINT; 510 BEGIN 511 oldMode := v.mode; 512 index.array.Accept (v); 513 v.mode := read; 514 index.index.Accept (v); 515 v.mode := oldMode; 516 END VisitIndex; 517 518PROCEDURE (v: Visitor) VisitAdr* (adr: IR.Adr); 519 BEGIN 520 (* ignore, this is neither a read nor a write *) 521 END VisitAdr; 522 523PROCEDURE (v: Visitor) VisitCall* (call: IR.Call); 524 VAR 525 i, class: LONGINT; 526 oldMode: SHORTINT; 527 inout, writeMaybeDict: DictInt.Dictionary; 528 obj: Object.Object; 529 design: IR.Expression; 530 readList, writeList: ArrayList.ArrayList; 531 k: Object.ObjectArrayPtr; 532 callee: Sym.ProcDecl; 533 varDecl: Sym.VarDecl; 534 type: Sym.Type; 535 BEGIN 536 oldMode := v.mode; 537 call.design.Accept(v); 538 539 CASE v.pass OF 540 | passCollectWrites: 541 IF ~(call.design IS IR.ProcedureRef) OR 542 call.design(IR.ProcedureRef).decl(Sym.ProcDecl).nonlocalWrites THEN 543 v.localProcData.inout.Set(any, doesWrite); (* assume worst *) 544 END; 545 546 FOR i := 0 TO LEN(call.arguments^)-1 DO 547 IF (call.formalPars[i] # NIL) & call.formalPars[i].isVarParam THEN 548 IF ~(call.arguments[i] IS IR.Const) THEN (* avoid NIL to VAR param *) 549 v.mode := write; 550 call.arguments[i](IR.Adr).design.Accept(v); 551 END; 552 ELSE 553 v.mode := read; 554 call.arguments[i].Accept(v); 555 END; 556 END; 557 558 | passAnalyzeCalls, passEmitWarnings: 559 IF (call.design IS IR.ProcedureRef) & 560 v.knownProcs.HasKey(call.design(IR.ProcedureRef).decl) THEN 561 callee := call.design(IR.ProcedureRef).decl(Sym.ProcDecl); 562 obj := v.knownProcs.Get(call.design(IR.ProcedureRef).decl); 563 inout := obj(ProcData).inout; 564 ELSE 565 IF ~(call.design IS IR.ProcedureRef) OR 566 call.design(IR.ProcedureRef).decl(Sym.ProcDecl).nonlocalWrites THEN 567 v.def.SetClass(any, defined); (* can write anything *) 568 END; 569 570 callee := NIL; 571 inout := NIL; 572 END; 573 574 readList := ArrayList.New(8); 575 writeList := ArrayList.New(8); 576 writeMaybeDict := DictInt.New(); 577 FOR i := 0 TO LEN(call.arguments^)-1 DO 578 IF (call.formalPars[i] = NIL) THEN 579 (* ignore *) 580 ELSIF call.formalPars[i].isVarParam THEN 581 IF ~(call.arguments[i] IS IR.Const) THEN (* avoid NIL to VAR param *) 582 design := call.arguments[i](IR.Adr).design; 583 IF (inout = NIL) THEN (* no info on caller, assume write *) 584 writeList.Append(design); 585 ELSIF inout.HasKey(call.formalPars[i]) THEN 586 class := inout.Get(call.formalPars[i]); 587 CASE class DIV scaleR OF 588 | doesRead: 589 readList.Append(design); 590 | doesNotRead: 591 (* ignore *) 592 END; 593 594 CASE class MOD scaleR OF 595 | doesWrite: 596 writeList.Append(design); 597 | mayWrite: 598 writeMaybeDict.Set(design, 0); 599 writeList.Append(design); 600 | doesNotWrite: 601 (* ignore *) 602 END; 603 END; 604 END; 605 ELSE 606 readList.Append(call.arguments[i]); 607 END; 608 END; 609 610 FOR i := 0 TO readList.size-1 DO 611 v.mode := read; 612 readList.array[i](IR.Expression).Accept(v); 613 END; 614 615 IF (inout # NIL) THEN 616 k := inout.Keys(); 617 FOR i := 0 TO LEN(k^)-1 DO 618 IF (k[i] IS Sym.VarDecl) THEN 619 varDecl := k[i](Sym.VarDecl); 620 IF (varDecl.Procedure() # callee) THEN 621 class := inout.Get(varDecl); 622 623 CASE class DIV scaleR OF 624 | doesRead: 625 v.mode := read; 626 VisitVarDecl(v, varDecl, call.sym, TRUE); 627 | doesNotRead: 628 (* ignore *) 629 END; 630 631 CASE class MOD scaleR OF 632 | doesWrite: 633 v.mode := write; 634 VisitVarDecl(v, varDecl, call.sym, TRUE); 635 | mayWrite: 636 v.mode := writeMaybe; 637 VisitVarDecl(v, varDecl, call.sym, TRUE); 638 | doesNotWrite: 639 (* ignore *) 640 END; 641 END; 642 ELSE (* k[i] IS Sym.Type *) 643 v.def.SetClass(k[i](Sym.Type), defined); 644 END; 645 END; 646 END; 647 648 FOR i := 0 TO writeList.size-1 DO 649 IF writeMaybeDict.HasKey(writeList.array[i]) THEN 650 v.mode := writeMaybe; 651 ELSE 652 v.mode := write; 653 END; 654 writeList.array[i](IR.Expression).Accept(v); 655 END; 656 END; 657 658 v.mode := oldMode; 659 type := call.design.type.Deparam(); 660 IF type(Sym.FormalPars).noReturn THEN 661 v.def.Unreachable(); 662 END; 663 END VisitCall; 664 665 666PROCEDURE TransitiveWriteClosure(knownProcs: Dictionary.Dictionary; 667 pd: ProcData); 668 VAR 669 i, j: LONGINT; 670 k: Object.ObjectArrayPtr; 671 pdCaller: ProcData; 672 obj: Object.Object; 673 callerDecl: Sym.ProcDecl; 674 BEGIN 675 k := pd.inout.Keys(); 676 FOR i := 0 TO LEN(k^)-1 DO 677 FOR j := 0 TO pd.callers.size-1 DO 678 callerDecl := pd.callers.array[j](Sym.ProcDecl); 679 obj := knownProcs.Get(callerDecl); 680 pdCaller := obj(ProcData); 681 IF ~pdCaller.inout.HasKey(k[i]) & 682 ((k[i] IS Sym.Type) OR 683 NonlocalVar(callerDecl, k[i](Sym.VarDecl))) THEN 684 pdCaller.inout.Set(k[i], doesWrite); 685 TransitiveWriteClosure(knownProcs, pdCaller); 686 END; 687 END; 688 END; 689 END TransitiveWriteClosure; 690 691PROCEDURE CheckUses*(module: IR.Module; moduleDecl: Sym.Module; 692 pragmaHistory: Pragmas.History; errList: Error.List); 693 VAR 694 i, inWorklist: LONGINT; 695 def: Def; 696 proc: IR.Procedure; 697 v: Visitor; 698 knownProcs, loopExits: Dictionary.Dictionary; 699 pd: Object.Object; 700 701 PROCEDURE CheckStatmSeq(def: Def; statmSeq: IR.StatementSeq); 702 VAR 703 i: LONGINT; 704 705 PROCEDURE CheckExpr(expr: IR.Expression); 706 BEGIN 707 v.def := def; 708 v.mode := read; 709 expr.Accept(v); 710 END CheckExpr; 711 712 PROCEDURE CheckDesign(design: IR.Expression; mode: SHORTINT); 713 BEGIN 714 v.def := def; 715 v.mode := mode; 716 design.Accept(v); 717 END CheckDesign; 718 719 PROCEDURE CheckStatm(statm: IR.Statement); 720 VAR 721 i: LONGINT; 722 defIn, def2: Def; 723 obj: Object.Object; 724 expr: IR.Expression; 725 726 PROCEDURE CheckWith(defIn: Def; with: IR.WithStatm; 727 prevGuards: ArrayList.ArrayList); 728 VAR 729 def2, defOld: Def; 730 localTest, test: IR.TypeTest; 731 BEGIN 732 localTest := with.guard(IR.TypeTest); 733 FOR i := 0 TO prevGuards.size-1 DO 734 test := prevGuards.array[i](IR.TypeTest); 735 IF (test.expr(IR.Var).decl = localTest.expr(IR.Var).decl) & 736 TR.IsExtensionOf(localTest.referenceType, 737 test.referenceType) & 738 (v.pass = passEmitWarnings) THEN 739 WarnSym(errList, pragmaHistory, localTest.sym, deadTypeTest); 740 END; 741 END; 742 743 defOld := def; 744 def := defIn; 745 CheckExpr(with.guard); 746 def := defOld; 747 748 def2 := defIn.Copy(); 749 CheckStatmSeq(def2, with.pathTrue); 750 def.Merge(def2); 751 752 IF (with.pathFalse # NIL) THEN 753 IF (LEN(with.pathFalse^) = 1) & 754 (with.pathFalse[0] IS IR.WithStatm) THEN 755 prevGuards.Append(with.guard(IR.TypeTest)); 756 CheckWith(defIn, with.pathFalse[0](IR.WithStatm), prevGuards); 757 ELSE 758 def2 := defIn.Copy(); 759 CheckStatmSeq(def2, with.pathFalse); 760 def.Merge(def2); 761 END; 762 END; 763 END CheckWith; 764 765 BEGIN 766 WITH statm: IR.Assert DO 767 IF (statm.predicate = NIL) OR 768 ((statm.predicate IS IR.Const) & 769 Boxed.false.Equals(statm.predicate(IR.Const).value)) THEN 770 def.Unreachable(); 771 ELSE 772 CheckExpr(statm.predicate); 773 END; 774 775 | statm: IR.AssignOp DO 776 CheckExpr(statm.value); 777 CheckExpr(statm.variable); 778 CheckDesign(statm.variable, write); 779 780 | statm: IR.Assignment DO 781 CheckExpr(statm.value); 782 CheckDesign(statm.variable, write); 783 784 | statm: IR.Call DO 785 CheckExpr(statm); 786 787 | statm: IR.Copy DO 788 CheckExpr(statm.source); 789 CheckDesign(statm.dest, write); 790 791 | statm: IR.CopyParameter DO 792 (* ignore *) 793 794 | statm: IR.CopyString DO 795 CheckExpr(statm.source); 796 CheckDesign(statm.dest, write); 797 798 | statm: IR.ForStatm DO 799 CheckExpr(statm.start); 800 CheckExpr(statm.end); 801 CheckDesign(statm.var, write); 802 (* `step' is a constant and cannot be undefined *) 803 CheckStatmSeq(def, statm.body); 804 805 | statm: IR.IterateArrayStatm DO 806 CheckExpr(statm.range); 807 CheckDesign(statm.var, write); 808 CheckStatmSeq(def, statm.body); 809 810 | statm: IR.IterateObjectStatm DO 811 CheckExpr(statm.iteratorFactory); 812 CheckExpr(statm.stepperCall); 813 CheckDesign(statm.var, write); 814 CheckStatmSeq(def, statm.body); 815 816 | statm: IR.IfStatm DO 817 CheckExpr(statm.guard); 818 def2 := def.Copy(); 819 CheckStatmSeq(def, statm.pathTrue); 820 CheckStatmSeq(def2, statm.pathFalse); 821 def.Merge(def2); 822 823 | statm: IR.CaseStatm DO 824 CheckExpr(statm.select); 825 defIn := def.Copy(); 826 def.Unreachable(); 827 FOR i := 0 TO LEN(statm.caseList^)-1 DO 828 def2 := defIn.Copy(); 829 CheckStatmSeq(def2, statm.caseList[i].statmSeq); 830 def.Merge(def2); 831 END; 832 IF (statm.default # NIL) THEN 833 def2 := defIn.Copy(); 834 CheckStatmSeq(def2, statm.default); 835 def.Merge(def2); 836 END; 837 838 | statm: IR.LoopStatm DO 839 defIn := NewUnreachableDef(); 840 loopExits.Set(statm, defIn); 841 CheckStatmSeq(def, statm.body); 842 def^ := defIn^; 843 844 | statm: IR.Exit DO 845 obj := loopExits.Get(statm.loop); 846 obj(Def).Merge(def); 847 def.Unreachable(); 848 849 | statm: IR.MoveBlock DO 850 IF (v.pass = passCollectWrites) THEN 851 v.localProcData.inout.Set(any, defined); (* assume worst *) 852 END; 853 854 IF (statm.source IS IR.Adr) THEN 855 CheckExpr(statm.source(IR.Adr).design); 856 ELSE 857 CheckExpr(statm.source); 858 END; 859 CheckExpr(statm.size); 860 861 expr := statm.dest; 862 IF (expr IS IR.TypeConv) THEN 863 expr := expr(IR.TypeConv).expr; 864 END; 865 IF (expr IS IR.Adr) THEN 866 CheckDesign(expr(IR.Adr).design, write); 867 ELSE 868 v.def.SetClass(any, defined); 869 CheckExpr(statm.dest); 870 END; 871 872 | statm: IR.Raise DO 873 CheckExpr(statm.exception); 874 def.Unreachable(); 875 876 | statm: IR.RepeatStatm DO 877 CheckStatmSeq(def, statm.body); 878 CheckExpr(statm.exitCondition); 879 880 | statm: IR.Return DO 881 IF (statm.result # NIL) THEN 882 CheckExpr(statm.result); 883 END; 884 v.procExit.Merge(def); 885 def.Unreachable(); 886 887 | statm: IR.TryStatm DO 888 defIn := def.Copy(); 889 CheckStatmSeq(def, statm.statmSeq); 890 FOR i := 0 TO LEN(statm.catchList^)-1 DO 891 def2 := defIn.Copy(); 892 CheckStatmSeq(def2, statm.catchList[i].statmSeq); 893 def.Merge(def2); 894 END; 895 896 | statm: IR.WhileStatm DO 897 CheckExpr(statm.guard); 898 CheckStatmSeq(def, statm.body); 899 900 | statm: IR.WithStatm DO 901 defIn := def.Copy(); 902 def.Unreachable(); 903 CheckWith(defIn, statm, ArrayList.New(4)); 904 END; 905 END CheckStatm; 906 907 BEGIN 908 IF (statmSeq # NIL) THEN 909 i := 0; 910 WHILE (i # LEN(statmSeq^)) & ~def.unreachable DO 911 CheckStatm(statmSeq[i]); 912 INC(i); 913 END; 914 IF (i # LEN(statmSeq^)) & (v.pass = passEmitWarnings) THEN 915 WarnSym(errList, pragmaHistory, statmSeq[i].sym, unreachableCode); 916 END; 917 END; 918 END CheckStatmSeq; 919 920 PROCEDURE CheckedFunction(procDecl: Sym.ProcDecl): BOOLEAN; 921 BEGIN 922 RETURN (procDecl.formalPars.resultType # NIL) & 923 ~procDecl.isAbstract; 924 END CheckedFunction; 925 926 PROCEDURE CallersToWorklist(callers: ArrayList.ArrayList); 927 VAR 928 i: LONGINT; 929 pd: Object.Object; 930 BEGIN 931 FOR i := 0 TO callers.size-1 DO 932 pd := knownProcs.Get(callers.array[i]); 933 pd(ProcData).inWorklist := TRUE; 934 END; 935 END CallersToWorklist; 936 937 PROCEDURE SetWriteFlags(localProc: Sym.ProcDecl; pd: ProcData; def: Def; 938 VAR nonlocalWrites: BOOLEAN); 939 VAR 940 k: Object.ObjectArrayPtr; 941 i: LONGINT; 942 item: Object.Object; 943 BEGIN 944 nonlocalWrites := FALSE; 945 946 k := pd.inout.Keys(); 947 FOR i := 0 TO LEN(k^)-1 DO 948 IF ~def.class.HasKey(k[i]) THEN (* is undefined *) 949 pd.SetWriteFlag(k[i](Sym.Item), doesNotWrite); 950 END; 951 END; 952 953 k := def.class.Keys(); 954 FOR i := 0 TO LEN(k^)-1 DO 955 item := k[i]; 956 WITH item: Sym.VarDecl DO 957 IF NonlocalVar(localProc, item) THEN 958 CASE def.class.Get(item) OF 959 | undefined: 960 pd.SetWriteFlag(item, doesNotWrite); 961 | maybeUndefined: 962 nonlocalWrites := TRUE; 963 pd.SetWriteFlag(item, mayWrite); 964 | defined: 965 nonlocalWrites := TRUE; 966 pd.SetWriteFlag(item, doesWrite); 967 END; 968 END; 969 | item: Sym.Type DO 970 nonlocalWrites := TRUE; 971 IF (item # any) THEN 972 (* For item=any (aka Predef.void), the assertion oldFlag>newFlag in 973 SetWriteFlag() may fail. I don't know if it the right action to 974 simply skip it. Given that `any' seems to be used as a target 975 that is never read, and therefore might be complete redundant, 976 this change should do no harm. *) 977 pd.SetWriteFlag(item, doesWrite); 978 END; 979 END; 980 END; 981 982 IF def.unreachable & (* no path to end of proc *) 983 ~localProc.formalPars.noReturn & (* not marked as NO_RETURN *) 984 (~localProc.IsTypeBound() OR (* normal procedure *) 985 localProc.notRedefined) THEN 986 localProc.formalPars.noReturn := TRUE; 987 pd.updatedInOut := TRUE; 988 END; 989 END SetWriteFlags; 990 991(* PROCEDURE DumpInOut(); 992 VAR 993 i, j: LONGINT; 994 procDecl: Sym.ProcDecl; 995 pd: Object.Object; 996 varDecl: Sym.VarDecl; 997 inout: DictInt.Dictionary; 998 k: Object.ObjectArrayPtr; 999 BEGIN 1000 Log.Ln; 1001 1002 k := knownProcs.Keys(); 1003 FOR i := 0 TO knownProcs.Size()-1 DO 1004 pd := knownProcs.Get(k[i]); 1005 Log.String("Callee", k[i](Sym.ProcDecl).name.str^); 1006 FOR j := 0 TO pd(ProcData).callers.size-1 DO 1007 Log.String(" caller", pd(ProcData).callers.array[j](Sym.ProcDecl).name.str^); 1008 END; 1009 END; 1010 1011 FOR i := 0 TO LEN (module.procList^)-1 DO 1012 procDecl := module.procList[i].decl; 1013 pd := knownProcs.Get(procDecl); 1014 inout := pd(ProcData).inout; 1015 Log.String("PROCEDURE", procDecl.name.str^); 1016 1017 k := inout.Keys(); 1018 FOR j := 0 TO LEN(k^)-1 DO 1019 varDecl := k[j](Sym.VarDecl); 1020 Log.String("var", varDecl.name.str^); 1021 CASE inout.Get(varDecl) DIV scaleR OF 1022 | doesRead: Err.String(" read: yes"); 1023 | doesNotRead: Err.String(" read: no"); 1024 END; 1025 Err.Ln; 1026 CASE inout.Get(varDecl) MOD scaleR OF 1027 | doesWrite: Err.String(" write: yes"); 1028 | doesNotWrite: Err.String(" write: no"); 1029 | mayWrite: Err.String(" write: maybe"); 1030 END; 1031 Err.Ln; 1032 END; 1033 END; 1034 END DumpInOut;*) 1035 1036 BEGIN 1037 IF (moduleDecl.class # Sym.mcStandard) THEN 1038 RETURN; (* nothing to check *) 1039 END; 1040 1041 knownProcs := KnownProcs(module); 1042 1043 NEW(v); 1044 VisitAll.InitVisitor(v); 1045 v.errList := errList; 1046 v.pragmaHistory := pragmaHistory; 1047 v.def := NIL; 1048 v.knownProcs := knownProcs; 1049 loopExits := Dictionary.New(); 1050 1051 (* first pass: assume that every assignment and every designator passed 1052 to a VAR parameter means that the variable in question is a `doesWrite' 1053 classification; propagate this classification to all callers *) 1054 v.pass := passCollectWrites; 1055 FOR i := 0 TO LEN (module.procList^)-1 DO 1056 proc := module.procList[i]; 1057 v.localProc := proc.decl; 1058 pd := knownProcs.Get(v.localProc); 1059 v.localProcData := pd(ProcData); 1060 v.procExit := NewUnreachableDef(); 1061 1062 CheckStatmSeq(NewDef(), proc.statmSeq); 1063 TransitiveWriteClosure(knownProcs, pd(ProcData)); 1064 END; 1065 1066 (* second pass: fine tune write classifications by reducing them to 1067 `mayWrite' or `noWrite' where appropriate; add read classifications *) 1068 v.pass := passAnalyzeCalls; 1069 REPEAT 1070 inWorklist := 0; 1071 FOR i := 0 TO LEN (module.procList^)-1 DO 1072 proc := module.procList[i]; 1073 pd := knownProcs.Get(proc.decl); 1074 IF pd(ProcData).inWorklist THEN 1075 v.localProc := proc.decl; 1076 v.localProcData := pd(ProcData); 1077 v.localProcData.updatedInOut := FALSE; 1078 v.procExit := NewUnreachableDef(); 1079 1080 def := NewDef(); 1081 CheckStatmSeq(def, proc.statmSeq); 1082 v.procExit.Merge(def); 1083 SetWriteFlags(v.localProc, v.localProcData, v.procExit, 1084 proc.decl.nonlocalWrites); 1085 1086 IF v.localProcData.updatedInOut THEN 1087 CallersToWorklist(pd(ProcData).callers); 1088 INC(inWorklist); 1089 END; 1090 END; 1091 END; 1092 UNTIL (inWorklist = 0); 1093 (*DumpInOut();*) 1094 1095 (* final pass: emit warnings *) 1096 v.pass := passEmitWarnings; 1097 FOR i := 0 TO LEN (module.procList^)-1 DO 1098 proc := module.procList[i]; 1099 v.localProc := proc.decl; 1100 v.procExit := NewUnreachableDef(); 1101 IF (proc.endOfProc # NIL) THEN 1102 def := NewDef(); 1103 CheckStatmSeq(def, proc.statmSeq); 1104 IF ~def.unreachable & CheckedFunction(proc.decl) THEN 1105 WarnSym(errList, pragmaHistory, proc.endOfProc, endOfFunction); 1106 END; 1107 END; 1108 END; 1109 END CheckUses; 1110 1111BEGIN 1112 NEW (checkUsesContext); 1113 Error.InitContext (checkUsesContext, "OOC:IR:CheckUses"); 1114 any := Predef.GetType(Predef.void); 1115END OOC:IR:CheckUses. 1116