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