]> andersk Git - splint.git/blame - src/usymtab_interface.c
Cleaned up flags to generate manual help.
[splint.git] / src / usymtab_interface.c
CommitLineData
616915dd 1/*
11db3170 2** Splint - annotation-assisted static program checker
77d37419 3** Copyright (C) 1994-2002 University of Virginia,
616915dd 4** Massachusetts Institute of Technology
5**
6** This program is free software; you can redistribute it and/or modify it
7** under the terms of the GNU General Public License as published by the
8** Free Software Foundation; either version 2 of the License, or (at your
9** option) any later version.
10**
11** This program is distributed in the hope that it will be useful, but
12** WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14** General Public License for more details.
15**
16** The GNU General Public License is available from http://www.gnu.org/ or
17** the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
18** MA 02111-1307, USA.
19**
20** For information on lclint: lclint-request@cs.virginia.edu
21** To report a bug: lclint-bug@cs.virginia.edu
11db3170 22** For more information: http://www.splint.org
616915dd 23*/
24/*
25** usymtab_interface.c
26**
27** Grammar interface to symtab.
28**
11db3170 29** The Splint parser will build symbol tables for abstract types and
616915dd 30** function declarations.
31**
32*/
33
34# include "lclintMacros.nf"
35# include "llbasic.h"
36# include "gram.h"
37# include "lclscan.h"
38# include "lclsyntable.h"
39# include "lslparse.h"
40# include "usymtab_interface.h"
41# include "structNames.h"
42
43static void
44 declareFcnAux (fcnNode p_f, /*@only@*/ qtype p_qt, ctype p_ct, typeId p_tn,
45 bool p_priv, bool p_spec);
46
47static uentryList paramNodeList_toUentryList (paramNodeList p_p);
48static /*@observer@*/ cstring getVarName (/*@null@*/ typeExpr p_x);
49static qtype convertLclTypeSpecNode (/*@null@*/ lclTypeSpecNode p_n);
50static ctype convertTypeExpr (ctype p_c, /*@null@*/ typeExpr p_x);
51static ctype convertCTypeExpr (ctype p_c, /*@null@*/ typeExpr p_x);
52static /*@exposed@*/ sRef fixTermNode (termNode p_n, fcnNode p_f, uentryList p_cl);
53static sRefSet fixModifies (fcnNode p_f, uentryList p_cl);
54
55static uentryList
56 convertuentryList (stDeclNodeList x)
57{
58 uentryList fl = uentryList_new ();
59
60
61 stDeclNodeList_elements (x, i)
62 {
63 declaratorNodeList d = i->declarators;
64 qtype q = convertLclTypeSpecNode (i->lcltypespec);
65
66 declaratorNodeList_elements (d, j)
67 {
68 idDecl id;
69
70 qtype_setType (q, convertTypeExpr (qtype_getType (q), j->type));
71 id = idDecl_create (cstring_copy (getVarName (j->type)), qtype_copy (q));
72 fl = uentryList_add (fl, uentry_makeIdVariable (id));
73 idDecl_free (id);
74 } end_declaratorNodeList_elements;
75
76 qtype_free (q);
77 } end_stDeclNodeList_elements;
78
79
80 return (fl);
81}
82
83static uentryList
84 convert_uentryList (paramNodeList x)
85{
86 uentryList p = uentryList_undefined;
87 bool first_one = TRUE;
88
89
90 paramNodeList_elements (x, i)
91 {
92 if (i != (paramNode) 0)
93 {
94 if (paramNode_isElipsis (i))
95 {
96 first_one = FALSE;
97 p = uentryList_add (p, uentry_makeElipsisMarker ());
98 }
99 else
100 {
101 qtype q = convertLclTypeSpecNode (i->type);
102 typeExpr t = i->paramdecl;
103
104 qtype_setType (q, convertTypeExpr (qtype_getType (q), t));
105
106 /* note: has to be like this to hack around void ???? still */
107
108 if (first_one)
109 {
110 if (ctype_isVoid (qtype_getType (q)))
111 {
112 llassert (uentryList_isUndefined (p));
113 qtype_free (q);
114 return (p);
115 }
116
117 first_one = FALSE;
118 }
119
120 /*
121 ** don't do qualifiers here, will get errors later
122 */
123
124 p = uentryList_add (p, uentry_makeUnnamedVariable (qtype_getType (q)));
125 qtype_free (q);
126 }
127 }
128 else
129 {
130 llbug (cstring_makeLiteral ("convertuentryList: null paramNode"));
131 }
132 } end_paramNodeList_elements;
133
134 if (first_one)
135 {
136 llassert (uentryList_isUndefined (p));
137
138 p = uentryList_makeMissingParams ();
139 }
140
141 return p;
142}
143
144/*
145** convertTypeExpr
146**
147** modify c with pointer, array, function
148**
149** (based on printTypeExpr2 from abstract.c)
150**
151*/
152
153static ctype
154convertTypeExpr (ctype c, typeExpr x)
155{
156 if (x == (typeExpr) 0)
157 {
158 return c;
159 }
160
161 switch (x->kind)
162 {
163 case TEXPR_BASE:
164 return (c);
165 case TEXPR_PTR:
166 return (convertTypeExpr (ctype_makePointer (c), x->content.pointer));
167 case TEXPR_ARRAY:
168 return (convertTypeExpr (ctype_makeArray (c), x->content.array.elementtype));
169 case TEXPR_FCN:
170 {
171 ctype rv = convertTypeExpr (c, x->content.function.returntype);
172 uentryList p = paramNodeList_toUentryList (x->content.function.args);
173
174 if (x->content.function.returntype != NULL
175 && x->content.function.returntype->wrapped == 1
176 && ctype_isPointer (rv))
177 {
178 rv = ctype_baseArrayPtr (rv);
179 }
180
181 return (ctype_makeParamsFunction (rv, p));
182 }
183 default:
184 {
185 llfatalbug (message ("convertTypeExpr: unknown typeExprKind: %d",
186 (int) x->kind));
187 }
188 }
189
190 BADEXIT;
191}
192
193static
194ctype convertCTypeExpr (ctype c, typeExpr x)
195{
196 if (x == (typeExpr) 0)
197 {
198 return c;
199 }
200
201 switch (x->kind)
202 {
203 case TEXPR_BASE: return (c);
204 case TEXPR_PTR: return (convertCTypeExpr (ctype_makePointer (c),
205 x->content.pointer));
206 case TEXPR_ARRAY: return (convertCTypeExpr (ctype_makeArray (c),
207 x->content.array.elementtype));
208 case TEXPR_FCN:
209 {
210 ctype rv = convertCTypeExpr (c, x->content.function.returntype);
211 uentryList p = convert_uentryList (x->content.function.args);
212
213 return (ctype_makeParamsFunction (rv, p));
214 }
215 default:
216 {
217 llfatalbug (message ("convertCTypeExpr: unknown typeExprKind: %d", (int) x->kind));
218 }
219 }
220 BADEXIT;
221}
222
223/*
224** convertLclTypeSpecNode
225**
226** LclTypeSpecNode --> ctype
227** this is the base type only!
228*/
229
230/*
231** convertLeaves
232**
233** for now, assume only last leaf is relevant.
234** this should be a safe assumption in general???
235*/
236
237static ctype
238 convertLeaves (ltokenList f)
239{
240 ctype c = ctype_unknown;
241
242 ltokenList_reset (f);
243
244 ltokenList_elements (f, current)
245 {
246 switch (ltoken_getCode (current))
247 {
248 case LLT_TYPEDEF_NAME:
249 {
250 cstring tn = ltoken_getRawString (current);
251
252 if (usymtab_existsTypeEither (tn))
253 {
254 c = ctype_combine (uentry_getAbstractType
255 (usymtab_lookupEither (tn)), c);
256 }
257 else if (cstring_equalLit (tn, "bool"))
258 {
259 /*
260 ** Bogus...keep consistent with old lcl builtin.
261 */
262 c = ctype_bool;
263 }
264 else
265 {
266 fileloc loc = fileloc_fromTok (current);
267
268 voptgenerror (FLG_UNRECOG,
269 message ("Unrecognized type: %s", tn), loc);
270 fileloc_free (loc);
271
272 usymtab_supEntry
273 (uentry_makeDatatype
274 (tn, ctype_unknown, MAYBE, NO, fileloc_getBuiltin ()));
275
276 }
277 /*@switchbreak@*/ break;
278 }
279 case LLT_CHAR:
280 c = ctype_combine (ctype_char, c);
281 /*@switchbreak@*/ break;
282
283 case LLT_DOUBLE:
284 c = ctype_combine (ctype_double, c);
285 /*@switchbreak@*/ break;
286 case LLT_FLOAT:
287 c = ctype_combine (ctype_float, c);
288 /*@switchbreak@*/ break;
289 case LLT_CONST:
290 case LLT_VOLATILE:
291 /*@switchbreak@*/ break;
292 case LLT_INT:
293 c = ctype_combine (ctype_int, c);
294 /*@switchbreak@*/ break;
295 case LLT_LONG:
296 c = ctype_combine (c, ctype_lint);
297 /*@switchbreak@*/ break;
298 case LLT_SHORT:
299 c = ctype_combine (c, ctype_sint);
300 /*@switchbreak@*/ break;
301 case LLT_SIGNED:
302 c = ctype_combine (c, ctype_int);
303 /*@switchbreak@*/ break;
304 case LLT_UNSIGNED:
305 c = ctype_combine (c, ctype_uint);
306 /*@switchbreak@*/ break;
307 case LLT_UNKNOWN:
308 c = ctype_combine (ctype_unknown, c);
309 /*@switchbreak@*/ break;
310 case LLT_VOID:
311 c = ctype_combine (ctype_void, c);
312 /*@switchbreak@*/ break;
313 case LLT_ENUM:
314 llcontbug (cstring_makeLiteral ("convertLeaves: enum"));
315 c = ctype_int;
316 /*@switchbreak@*/ break;
317 default:
318 llfatalbug (message ("convertLeaves: bad token: %q",
319 ltoken_unparseCodeName (current)));
320 }
321 } end_ltokenList_elements;
322
323 return c;
324}
325
326static enumNameList
327 convertEnumList (ltokenList enums)
328{
329 enumNameList el = enumNameList_new ();
330
331 if (ltokenList_isDefined (enums))
332 {
333 ltokenList_elements (enums, i)
334 {
335 enumNameList_addh
336 (el, enumName_create (cstring_copy (ltoken_unparse (i))));
337 } end_ltokenList_elements;
338 }
339
340 return el;
341}
342
343static /*@only@*/ qtype
344 convertLclTypeSpecNode (/*@null@*/ lclTypeSpecNode n)
345{
346
347 if (n != (lclTypeSpecNode) 0)
348 {
349 qtype result;
350
351 switch (n->kind)
352 {
353 case LTS_CONJ:
354 {
355 qtype c1 = convertLclTypeSpecNode (n->content.conj->a);
356 qtype c2 = convertLclTypeSpecNode (n->content.conj->b);
357
358 /*
359 ** Is it explicit?
360 */
361
362 if (fileloc_isLib (g_currentloc)
363 || fileloc_isStandardLibrary (g_currentloc))
364 {
365 result = qtype_mergeImplicitAlt (c1, c2);
366 }
367 else
368 {
369 result = qtype_mergeAlt (c1, c2);
370 }
371
372 break;
373 }
374 case LTS_TYPE:
375 llassert (n->content.type != NULL);
376 result = qtype_create (convertLeaves (n->content.type->ctypes));
377 break;
378 case LTS_STRUCTUNION:
379 {
380 strOrUnionNode sn;
381 cstring cn = cstring_undefined;
382
383 sn = n->content.structorunion;
384
385 llassert (sn != (strOrUnionNode) 0);
386
387 if (!ltoken_isUndefined (sn->opttagid))
388 {
389 cn = cstring_copy (ltoken_getRawString (sn->opttagid));
390 }
391 else
392 {
393 cn = fakeTag ();
394 }
395
396 switch (sn->kind)
397 {
398 case SU_STRUCT:
399 if (usymtab_existsStructTag (cn))
400 {
401
402 result = qtype_create (uentry_getAbstractType
403 (usymtab_lookupStructTag (cn)));
404 cstring_free (cn);
405 }
406 else
407 {
408 uentryList fl = convertuentryList (sn->structdecls);
409 ctype ct;
410
411 ct = ctype_createStruct (cstring_copy (cn), fl);
412
413 /*
414 ** If it was a forward declaration, this could add it to
415 ** the table. Need to check if it exists again...
416 */
417
418 if (usymtab_existsStructTag (cn))
419 {
420 result = qtype_create (uentry_getAbstractType
421 (usymtab_lookupStructTag (cn)));
422 }
423 else
424 {
425 fileloc loc = fileloc_fromTok (n->content.structorunion->tok);
426 uentry ue = uentry_makeStructTag (cn, ct, loc);
427
428 result = qtype_create (usymtab_supTypeEntry (ue));
429 }
430
431 cstring_free (cn);
432 }
433 /*@switchbreak@*/ break;
434 case SU_UNION:
435 if (usymtab_existsUnionTag (cn))
436 {
437
438 result = qtype_create (uentry_getAbstractType
439 (usymtab_lookupUnionTag (cn)));
440 cstring_free (cn);
441 }
442 else
443 {
444 uentryList fl;
445 ctype ct;
446
447 fl = convertuentryList (sn->structdecls);
448 ct = ctype_createUnion (cstring_copy (cn), fl);
449
450 /*
451 ** If it was a forward declaration, this could add it to
452 ** the table. Need to check if it exists again...
453 */
454
455
456
457 if (usymtab_existsUnionTag (cn))
458 {
459
460 result = qtype_create (uentry_getAbstractType
461 (usymtab_lookupUnionTag (cn)));
462 }
463 else
464 {
465 fileloc loc = fileloc_fromTok (n->content.structorunion->tok);
466 uentry ue = uentry_makeUnionTag (cn, ct, loc);
467
468 result = qtype_create (usymtab_supTypeEntry (ue));
469 }
470
471 cstring_free (cn);
472 }
473 /*@switchbreak@*/ break;
474 BADDEFAULT
475 }
476 break;
477 }
478 case LTS_ENUM:
479 {
480 enumSpecNode e = n->content.enumspec;
481 enumNameList el;
482 cstring ename;
483 bool first = TRUE;
484 ctype ta;
485 ctype cet;
486
487 llassert (e != NULL);
488 el = convertEnumList (e->enums);
489
490 if (!ltoken_isUndefined (e->opttagid)) /* named enumerator */
491 {
492 ename = cstring_copy (ltoken_getRawString (e->opttagid));
493 }
494 else
495 {
496 ename = fakeTag ();
497 }
498
499 cet = ctype_createEnum (ename, el);
500
501 if (usymtab_existsEnumTag (ename))
502 {
503 ta = uentry_getAbstractType (usymtab_lookupEnumTag (ename));
504 }
505 else
506 {
507 fileloc loc = fileloc_fromTok (e->tok);
508 uentry ue = uentry_makeEnumTag (ename, cet, loc);
509
510 ta = usymtab_supTypeEntry (ue);
511 }
512
513 enumNameList_elements (el, en)
514 {
515 uentry ue;
516 fileloc loc;
517
518 if (first)
519 {
520 ltokenList_reset (e->enums);
521 first = FALSE;
522 }
523 else
524 {
525 ltokenList_advance (e->enums);
526 }
527
528 loc = fileloc_fromTok (ltokenList_current (e->enums));
529 ue = uentry_makeSpecEnumConstant (en, cet, loc);
530
531 /*
532 ** Can't check name here, might not have
533 ** type yet. Will check in .lh file?
534 */
535
536 ue = usymtab_supGlobalEntryReturn (ue);
537
538 if (context_inLCLLib ())
539 {
540 uentry_setDefined (ue, loc);
541 }
542 } end_enumNameList_elements;
543
544 result = qtype_create (ta);
545 }
546 break;
547 default:
548 {
549 llfatalbug (message ("convertLclTypeSpecNode: unknown lclTypeSpec kind: %d",
550 (int) n->kind));
551 }
552 }
553
554 result = qtype_addQualList (result, n->quals);
555
556 if (n->pointers > 0)
557 {
558 qtype_adjustPointers (n->pointers, result);
559 }
560
561 return result;
562 }
563 else
564 {
565 llcontbug (cstring_makeLiteral ("convertLclTypeSpecNode: null"));
566 return qtype_unknown ();
567 }
568 BADEXIT;
569}
570
571static /*@only@*/ multiVal
572 literalValue (ctype ct, ltoken lit)
573{
574 cstring text = cstring_fromChars (lsymbol_toChars (ltoken_getText (lit)));
575 char first;
576
577 if (cstring_length (text) > 0)
578 {
579 first = cstring_firstChar (text);
580 }
581 else
582 {
583 return multiVal_unknown ();
584 }
585
586
587 if /*@-usedef@*/ (first == '\"') /*@=usedef@*/
588 {
589 int len = cstring_length (text) - 2;
590 char *val = mstring_create (len);
591
592 llassert (cstring_lastChar (text) == '\"');
593 strncpy (val, cstring_toCharsSafe (text) + 1, size_fromInt (len));
594 return (multiVal_makeString (cstring_fromCharsO (val)));
595 }
596
597 if (ctype_isDirectInt (ct) || ctype_isPointer (ct))
598 {
599 long val = 0;
600
601 if (sscanf (cstring_toCharsSafe (text), "%ld", &val) == 1)
602 {
603 return multiVal_makeInt (val);
604 }
605 }
606
607 return multiVal_unknown ();
608}
609
610
611/*
612** declareConstant
613**
614** unfortunately, because the abstract types are different, this
615** cannot be easily subsumed into declareVar.
616*/
617
618void
619doDeclareConstant (constDeclarationNode c, bool priv)
620{
621 lclTypeSpecNode t;
622 ctype ctx;
623 qtype qt;
624
625 if (c == (constDeclarationNode) 0)
626 {
627 return;
628 }
629
630 t = c->type;
631 qt = convertLclTypeSpecNode (t);
632
633 ctx = qtype_getType (qt);
634
635 initDeclNodeList_elements (c->decls, i)
636 {
637 ctype ct = convertTypeExpr (ctx, i->declarator->type);
638 cstring s = getVarName (i->declarator->type);
639
640 if (ctype_isFunction (ct))
641 {
642 fcnNode fcn = fcnNode_fromDeclarator (lclTypeSpecNode_copy (t),
643 declaratorNode_copy (i->declarator));
644
645 /* FALSE == unspecified function, only a declaration */
646
647 doDeclareFcn (fcn, typeId_invalid, priv, FALSE);
648 fcnNode_free (fcn);
649 }
650 else
651 {
652 uentry ue;
653 fileloc loc = fileloc_fromTok (i->declarator->id);
654
655 if (i->value != (termNode)0 &&
656 i->value->kind == TRM_LITERAL)
657 {
5e211f69 658 ue = uentry_makeConstantValue (s, ct, loc, priv, literalValue (ct, i->value->literal));
616915dd 659 }
660 else
661 {
5e211f69 662 ue = uentry_makeConstantValue (s, ct, loc, priv, multiVal_unknown ());
616915dd 663 }
5e211f69 664
665 uentry_reflectQualifiers (ue, qtype_getQuals (qt));
616915dd 666
667 if (context_inLCLLib () && !priv)
668 {
669 uentry_setDefined (ue, loc);
670 }
671
672 usymtab_supGlobalEntry (ue);
673 }
674 } end_initDeclNodeList_elements;
675
676 qtype_free (qt);
677}
678
679static cstring
680getVarName (/*@null@*/ typeExpr x)
681{
682 cstring s = cstring_undefined;
683
684 if (x != (typeExpr) 0)
685 {
686 switch (x->kind)
687 {
688 case TEXPR_BASE:
689 s = ltoken_getRawString (x->content.base);
690 break;
691 case TEXPR_PTR:
692 s = getVarName (x->content.pointer);
693 break;
694 case TEXPR_ARRAY:
695 s = getVarName (x->content.array.elementtype);
696 break;
697 case TEXPR_FCN:
698 s = getVarName (x->content.function.returntype);
699 break;
700 default:
701 llfatalbug (message ("getVarName: unknown typeExprKind: %d", (int) x->kind));
702 }
703 }
704
705 return s;
706}
707
708void
709doDeclareVar (varDeclarationNode v, bool priv)
710{
711 lclTypeSpecNode t;
712 qtype c;
713
714 if (v == (varDeclarationNode) 0)
715 {
716 return;
717 }
718
719 t = v->type;
720 c = convertLclTypeSpecNode (t);
721
722 initDeclNodeList_elements (v->decls, i)
723 {
724 ctype ct = convertTypeExpr (qtype_getType (c), i->declarator->type);
725 cstring s = getVarName (i->declarator->type);
726
727 qtype_setType (c, ct);
728
729 if (ctype_isFunction (ct))
730 {
731 fcnNode fcn;
732
733
734 fcn = fcnNode_fromDeclarator (lclTypeSpecNode_copy (t),
735 declaratorNode_copy (i->declarator));
736
737 /* FALSE == unspecified function, only a declaration */
738 declareFcnAux (fcn, qtype_unknown (), ct,
739 typeId_invalid, priv, FALSE);
740 fcnNode_free (fcn);
741 }
742 else
743 {
744 fileloc loc = fileloc_fromTok (i->declarator->id);
745 uentry le = uentry_makeVariable (s, ct, loc, priv);
746
747 uentry_reflectQualifiers (le, qtype_getQuals (c));
748
749 if (uentry_isCheckedUnknown (le))
750 {
751 if (context_getFlag (FLG_IMPCHECKEDSTRICTSPECGLOBALS))
752 {
753 uentry_setCheckedStrict (le);
754 }
755 else if (context_getFlag (FLG_IMPCHECKEDSPECGLOBALS))
756 {
757 uentry_setChecked (le);
758 }
759 else if (context_getFlag (FLG_IMPCHECKMODSPECGLOBALS))
760 {
761 uentry_setCheckMod (le);
762 }
763 else
764 {
765 ; /* okay */
766 }
767 }
768
769 if (context_inLCLLib () && !priv)
770 {
771 uentry_setDefined (le, loc);
772 }
773
774 if (initDeclNode_isRedeclaration (i))
775 {
776 usymtab_replaceEntry (le);
777 }
778 else
779 {
780 le = usymtab_supEntrySrefReturn (le);
781 }
782 }
783 } end_initDeclNodeList_elements;
784
785 qtype_free (c);
786}
787
788static globSet
789processGlob (/*@returned@*/ globSet globs, varDeclarationNode v)
790{
791 if (v == (varDeclarationNode) 0)
792 {
793 return globs;
794 }
795
796 if (v->isSpecial)
797 {
798 globs = globSet_insert (globs, v->sref);
799 }
800 else
801 {
802 lclTypeSpecNode t = v->type;
803 qtype qt = convertLclTypeSpecNode (t);
804 ctype c = qtype_getType (qt);
805 cstring s;
806
807 initDeclNodeList_elements (v->decls, i)
808 {
809 ctype ct;
810 uentry ue;
811 qualList quals = qtype_getQuals (qt);
812
813 s = getVarName (i->declarator->type);
814 ue = usymtab_lookupGlobSafe (s);
815
816 if (uentry_isInvalid (ue))
817 {
818 ; /* error already reported */
819 }
820 else
821 {
822 if (uentry_isPriv (ue))
823 {
824 globs = globSet_insert (globs, sRef_makeSpecState ());
825 }
826 else
827 {
828 uentry ce = uentry_copy (ue);
829 ctype lt = uentry_getType (ce);
830 fileloc loc = fileloc_fromTok (i->declarator->id);
831
832 ct = convertTypeExpr (c, i->declarator->type);
833
834 if (!ctype_match (lt, ct))
835 {
836 (void) gentypeerror
837 (lt, exprNode_undefined,
838 ct, exprNode_undefined,
839 message ("Global type mismatch %s (%t, %t)",
840 s, lt, ct),
841 loc);
842 }
843
844 uentry_reflectQualifiers (ce, quals);
845 globs = globSet_insert (globs,
846 sRef_copy (uentry_getSref (ce)));
847 fileloc_free (loc);
848 uentry_free (ce);
849 }
850 }
851 } end_initDeclNodeList_elements;
852
853 qtype_free (qt);
854 }
855
856 return globs;
857}
858
859static void
860declareAbstractType (abstractNode n, bool priv)
861{
862 cstring tn;
863 fileloc loc;
864 uentry ue;
865 usymId uid;
866 abstBodyNode ab;
867
868 if (n == (abstractNode) 0)
869 {
870 return;
871 }
872
873
874 tn = ltoken_getRawString (n->name);
875
876 loc = fileloc_fromTok (n->tok);
877
878 ue = uentry_makeDatatypeAux (tn, ctype_unknown,
879 ynm_fromBool (n->isMutable), YES, loc, priv);
880
881 if (n->isRefCounted)
882 {
883 uentry_setRefCounted (ue);
884 }
885
886 if (context_inLCLLib () && !priv)
887 {
888 uentry_setDefined (ue, loc);
889 }
890
891 uid = usymtab_supAbstractTypeEntry (ue, context_inLCLLib() && !priv);
892
893
894 if (!priv && (ab = n->body) != (abstBodyNode) 0)
895 {
896 fcnNodeList ops = ab->fcns;
897
898 if (!fcnNodeList_isEmpty (ops))
899 {
900 fcnNodeList_elements (ops, i)
901 {
902 if (i->typespec == (lclTypeSpecNode) 0)
903 {
904 cstring fname = ltoken_getRawString (i->name);
905
906 if (usymtab_exists (fname))
907 {
908 uentry e = usymtab_lookup (fname);
909 fileloc floc = fileloc_fromTok (i->declarator->id);
910
911 if (uentry_isForward (e))
912 {
913 usymtab_supEntry
914 (uentry_makeTypeListFunction
915 (fname, typeIdSet_insert (uentry_accessType (e), uid),
916 floc));
917 }
918 else
919 {
920 usymtab_supEntry
921 (uentry_makeSpecFunction
922 (fname, uentry_getType (e),
923 typeIdSet_insert (uentry_accessType (e), uid),
924 globSet_undefined,
925 sRefSet_undefined,
926 floc));
927
928 if (context_inLCLLib ())
929 {
930 llbuglit ("Jolly jeepers Wilma, it ain't dead after all!");
931 }
932 }
933 }
934 else
935 {
936 usymtab_supEntry
937 (uentry_makeForwardFunction (fname, uid, loc));
938 }
939 }
940 else
941 {
942 declareFcn (i, uid);
943 }
944 } end_fcnNodeList_elements;
945 }
946 }
947}
948
949static void
950 declareExposedType (exposedNode n, bool priv)
951{
952 usymId uid;
953 qtype c;
954 cstring s;
955
956
957 if (n == (exposedNode) 0)
958 {
959 return;
960 }
961
962 c = convertLclTypeSpecNode (n->type);
963
964 declaratorInvNodeList_elements (n->decls, i)
965 {
966 ctype realType = convertTypeExpr (qtype_getType (c), i->declarator->type);
967 fileloc loc = fileloc_fromTok (i->declarator->id);
968 uentry ue;
969
970 s = getVarName (i->declarator->type);
971
972 ue = uentry_makeDatatypeAux (s, realType, MAYBE, NO, loc, priv);
973
974 uentry_reflectQualifiers (ue, qtype_getQuals (c));
975
976 if (context_inLCLLib () && !priv)
977 {
978 uentry_setDefined (ue, loc);
979 }
980
981 uid = usymtab_supExposedTypeEntry (ue, context_inLCLLib () && !priv);
982 } end_declaratorInvNodeList_elements;
983
984 qtype_free (c);
985}
986
987/*
988** ah...remember ye old days...
989**
990** wow...same thing in THREE symbol tables! talk about space efficiency
991** (or as Joe Theory once said, its only a constant factor)
992*/
993
994void
995doDeclareType (typeNode t, bool priv)
996{
997
998 if (t != (typeNode) 0)
999 {
1000 switch (t->kind)
1001 {
1002 case TK_ABSTRACT:
1003 declareAbstractType (t->content.abstract, priv);
1004 break;
1005
1006 case TK_EXPOSED:
1007 declareExposedType (t->content.exposed, priv);
1008 break;
1009
1010 case TK_UNION:
1011 default:
1012 {
1013 llfatalbug (message ("declareType ERROR: unknown kind: %q",
1014 cstring_fromCharsO (FormatInt ((int)t->kind))));
1015 }
1016 }
1017 }
1018
1019}
1020
1021extern void
1022declareIter (iterNode iter)
1023{
1024 fileloc loc = fileloc_fromTok (iter->name);
1025 uentry ue =
1026 uentry_makeIter (ltoken_unparse (iter->name),
1027 ctype_makeFunction
1028 (ctype_void,
1029 paramNodeList_toUentryList (iter->params)),
1030 fileloc_copy (loc));
1031
1032 usymtab_supEntry (ue);
1033 usymtab_supEntry
1034 (uentry_makeEndIter (ltoken_unparse (iter->name), loc));
1035}
1036
1037/*
1038** declareFcn
1039*/
1040
1041static void
1042declareFcnAux (fcnNode f, /*@only@*/ qtype qt, ctype ct,
1043 typeId tn, bool priv, bool spec)
1044{
1045 globalList globals;
1046 typeIdSet acct;
1047 sRefSet sl = sRefSet_undefined;
1048 globSet globlist = globSet_undefined;
1049 cstring s = getVarName (f->declarator->type);
1050 fileloc loc = fileloc_fromTok (f->declarator->id);
1051 uentryList args;
1052
1053 /*
1054 ** type conversion generates args
1055 */
1056
1057 if (ctype_isFunction (ct))
1058 {
1059 args = ctype_argsFunction (ct);
1060 }
1061 else
1062 {
1063 llcontbug (message ("Not function: %s", ctype_unparse (ct)));
1064 args = uentryList_undefined;
1065 }
1066
1067
1068 fileloc_setColumnUndefined (loc);
1069
1070 if (spec)
1071 {
1072 globals = f->globals;
1073
28bf4b0b 1074 sl = fixModifies (f, args);
616915dd 1075
1076 /*
1077 ** Bind let declarations in modifies list
1078 */
1079
1080 varDeclarationNodeList_elements (globals, glob)
1081 {
1082 globlist = processGlob (globlist, glob);
1083 } end_varDeclarationNodeList_elements;
1084
1085
1086 if (f->checks != (lclPredicateNode) 0)
1087 /* push stderr on globalList */
1088 /* modifies *stderr^ */
1089 {
1090 uentry ue;
1091
1092 if (!(usymtab_existsVar (cstring_makeLiteralTemp ("stderr"))))
1093 {
1094 ctype tfile;
1095
1096 llmsglit ("Global stderr implied by checks clause, "
1097 "not declared in initializations.");
1098
1099 tfile = usymtab_lookupType (cstring_makeLiteralTemp ("FILE"));
1100
1101 if (ctype_isUndefined (tfile))
1102 {
1103 llmsglit ("FILE datatype implied by checks clause not defined.");
1104 tfile = ctype_unknown;
1105 }
1106
1107 usymtab_supGlobalEntry
1108 (uentry_makeVariable (cstring_makeLiteralTemp ("stderr"),
1109 tfile, fileloc_getBuiltin (), FALSE));
1110 }
1111
1112 ue = usymtab_lookupGlob (cstring_makeLiteralTemp ("stderr"));
1113
28bf4b0b 1114 globlist = globSet_insert (globlist, sRef_copy (uentry_getSref (ue)));
616915dd 1115 sl = sRefSet_insert (sl, sRef_buildPointer (uentry_getSref (ue)));
616915dd 1116 }
1117 }
1118
1119 if (usymId_isInvalid (tn))
1120 {
1121 acct = context_fileAccessTypes ();
1122 }
1123 else
1124 {
1125 acct = typeIdSet_single (tn);
1126 }
28bf4b0b 1127
616915dd 1128 if (usymtab_exists (s))
1129 {
1130 uentry l = usymtab_lookup (s);
1131 uentry ue;
1132
1133 if (uentry_isForward (l) || (fileloc_isLib (uentry_whereSpecified (l))))
1134 {
1135 typeIdSet accessType;
1136
1137 if (uentry_isFunction (l))
1138 {
1139 accessType = typeIdSet_union (uentry_accessType (l),
1140 context_fileAccessTypes ());
1141 }
1142 else
1143 {
1144 accessType = context_fileAccessTypes ();
1145 }
1146
1147 if (spec)
1148 {
1149 ue = uentry_makeSpecFunction (s, ct, accessType, globlist, sl, loc);
1150 }
1151 else
1152 {
1153 sRefSet_free (sl);
1154 globSet_free (globlist);
1155
1156 ue = uentry_makeUnspecFunction (s, ct, accessType, loc);
1157 }
1158
1159 uentry_reflectQualifiers (ue, qtype_getQuals (qt));
616915dd 1160 usymtab_supEntry (ue);
1161 }
1162 else
1163 {
1164 /*
1165 ** error reported by symtable already
1166 **
1167 ** llgenerror (message ("Function redeclared: %s (previous declaration: %s)", s,
1168 ** fileloc_unparse (uentry_whereSpecified (l))),
1169 ** loc);
1170 */
1171
616915dd 1172 fileloc_free (loc);
1173 sRefSet_free (sl);
1174 globSet_free (globlist);
1175 }
1176 }
1177 else
1178 {
1179 uentry le;
1180
1181 if (spec)
1182 {
1183 if (priv)
1184 {
1185 le = uentry_makePrivFunction2 (s, ct, acct, globlist, sl, loc);
1186 }
1187 else
1188 {
1189 le = uentry_makeSpecFunction (s, ct, acct, globlist, sl, loc);
1190 }
1191 }
1192 else
1193 {
1194 le = uentry_makeUnspecFunction (s, ct, acct, loc);
1195
1196 sRefSet_free (sl);
1197 globSet_free (globlist);
1198 }
1199
1200 if (context_inLCLLib () && !priv)
1201 {
1202 uentry_setDefined (le, loc);
1203 }
1204
1205 uentry_reflectQualifiers (le, qtype_getQuals (qt));
1206
28bf4b0b 1207 if (qual_isUnknown (f->special)) {
1208 ;
1209 } else if (qual_isPrintfLike (f->special)) {
1210 uentry_setPrintfLike (le);
1211 } else if (qual_isScanfLike (f->special)) {
1212 uentry_setScanfLike (le);
1213 } else if (qual_isMessageLike (f->special)) {
1214 uentry_setMessageLike (le);
1215 } else {
1216 BADBRANCH;
1217 }
616915dd 1218
1219 usymtab_supEntry (le);
1220 }
1221
1222 qtype_free (qt);
1223}
1224
1225extern void
1226doDeclareFcn (fcnNode f, typeId tn, bool priv, bool spec)
1227{
1228 qtype qt = convertLclTypeSpecNode (f->typespec);
1229 ctype ct = convertTypeExpr (qtype_getType (qt), f->declarator->type);
1230
28bf4b0b 1231 declareFcnAux (f, qt, ct, tn, priv, spec);
616915dd 1232}
1233
1234/*
1235** is s is an argument to f, return its arg no.
1236** otherwise, return 0
1237*/
1238
1239static int
1240getParamNo (cstring s, fcnNode f)
1241{
1242 /* gasp, maybe should do run-time checks here */
1243 paramNodeList params;
1244 typeExpr fd = f->declarator->type;
1245
1246 /* is this a bug in the LCL grammar? */
1247
1248 while (fd != NULL && (fd->kind == TEXPR_PTR || fd->kind == TEXPR_ARRAY))
1249 {
1250 if (fd->kind == TEXPR_PTR)
1251 {
1252 fd = fd->content.pointer;
1253 }
1254 else
1255 {
1256 /*@-null@*/ fd = fd->content.array.elementtype; /*@=null@*/
1257
1258 /*
1259 ** This is a bug in checking, that I should eventually fix.
1260 ** Need some way of deleting the guard from the true branch,
1261 ** but adding it back in the false branch...
1262 */
1263 }
1264 }
1265
1266 llassert (fd != NULL);
1267
1268 if (fd->kind != TEXPR_FCN)
1269 {
1270 llfatalbug (message ("getParamNo: not a function: %q (%d)",
1271 typeExpr_unparse (fd), (int) fd->kind));
1272 }
1273
1274 params = fd->content.function.args;
1275
1276 if (paramNodeList_empty (params))
1277 {
1278 return -1;
1279 }
1280 else
1281 {
1282 int pno = 0;
1283
1284 paramNodeList_elements (params, i)
1285 {
1286 if (i->paramdecl != (typeExpr) 0) /* handle (void) */
1287 {
1288 if (cstring_equal (s, getVarName (i->paramdecl)))
1289 {
1290 return pno;
1291 }
1292 }
1293 pno++;
1294 } end_paramNodeList_elements;
1295 return -1;
1296 }
1297}
1298
1299static /*@null@*/ /*@observer@*/ termNode
1300getLetDecl (cstring s, fcnNode f)
1301{
1302 letDeclNodeList x = f->lets;
1303
1304 letDeclNodeList_elements (x, i)
1305 {
1306 if (cstring_equal (s, ltoken_getRawString (i->varid)))
1307 {
1308 if (i->sortspec != NULL)
1309 {
1310 llbuglit ("getLetDecl: cannot return sort!");
1311 }
1312 else
1313 { /* is a termNode */
1314 return i->term;
1315 }
1316 }
1317 } end_letDeclNodeList_elements;
1318
1319 return (termNode) 0;
1320}
1321
1322/*
1323** processTermNode --- based on printTermNode2
1324*/
1325
1326static /*@exposed@*/ sRef
1327 processTermNode (/*@null@*/ opFormNode op, termNodeList args,
1328 fcnNode f, uentryList cl)
1329{
1330 if (op != (opFormNode) 0)
1331 {
1332 switch (op->kind)
1333 {
1334 case OPF_IF:
1335 llcontbuglit ("processTermNode: OPF_IF: not handled");
1336 break;
1337 case OPF_ANYOP:
1338 llcontbuglit ("processTermNode: OPF_ANYOP: not handled");
1339 break;
1340 case OPF_MANYOP:
1341 {
1342 int size = termNodeList_size (args);
1343
1344 if (size == 1
1345 && (cstring_equalLit (ltoken_getRawString (op->content.anyop), "'") ||
1346 cstring_equalLit (ltoken_getRawString (op->content.anyop), "^")))
1347 {
1348 return (fixTermNode (termNodeList_head (args), f, cl));
1349 }
1350 else
1351 {
1352 ;
1353 }
1354 break;
1355 }
1356 case OPF_ANYOPM:
1357 {
1358 int size = termNodeList_size (args);
1359
1360 if (size == 1
1361 && (cstring_equalLit (ltoken_getRawString (op->content.anyop), "*")))
1362 {
1363 sRef ft;
1364 sRef res;
1365
1366 ft = fixTermNode (termNodeList_head (args), f, cl);
1367 res = sRef_buildPointer (ft);
1368 return (res);
1369 }
1370 else
1371 {
1372 ;
1373 }
1374 break;
1375 }
1376 case OPF_MANYOPM:
1377 llcontbuglit ("OPF_MANYOPM: not handled\n");
1378 break;
1379 case OPF_MIDDLE:
1380 llcontbuglit ("OPF_MIDDLE: not handled\n");
1381 break;
1382 case OPF_MMIDDLE:
1383 llcontbuglit ("OPF_MMIDDLE: not handled\n");
1384 break;
1385 case OPF_MIDDLEM:
1386 llcontbuglit ("OPF_MIDDLEM: not handled\n");
1387 break;
1388 case OPF_MMIDDLEM:
1389 llcontbuglit ("OPF_MMIDDLEM: not handled\n");
1390 break;
1391 case OPF_BMIDDLE:
1392 if (op->content.middle == 1)
1393 llbug (message ("array fetch: [%q]",
1394 termNodeList_unparse (args)));
1395 else
1396 llcontbuglit ("OPF_BMIDDLE: bad\n");
1397 break;
1398
1399 case OPF_BMMIDDLE:
1400 if (op->content.middle <= 1)
1401 {
1402 sRef arr = fixTermNode (termNodeList_head (args), f, cl);
1403 sRef ret;
1404
1405 if (op->content.middle == 1)
1406 {
1407 termNode t = (termNodeList_reset (args),
1408 termNodeList_advance (args),
1409 termNodeList_current (args));
1410
1411 if (t->kind == TRM_LITERAL)
1412 {
1413 int i;
1414
1415 if (sscanf
1416 (cstring_toCharsSafe
1417 (ltoken_getRawString (t->literal)),
1418 "%d", &i) == 1)
1419 {
1420 ret = sRef_buildArrayFetchKnown (arr, i);
1421 }
1422 else
1423 {
1424 ret = sRef_buildArrayFetch (arr);
1425 }
1426
1427 return (ret);
1428 }
1429 }
1430
1431 /* unknown index */
1432
1433 ret = sRef_buildArrayFetch (arr);
1434
1435 return (ret);
1436 }
1437 else
1438 {
1439 llcontbug (message ("op->content.middle = %d",
1440 op->content.middle));
1441 break;
1442 }
1443
1444 case OPF_BMIDDLEM:
1445 llcontbuglit ("OPF_BMIDDLEM not handled");
1446 break;
1447
1448 case OPF_BMMIDDLEM:
1449 llcontbuglit ("OPF_BMMIDDLEM not handled");
1450 break;
1451
1452 case OPF_SELECT:
1453 llcontbug (message ("select: .%s",
1454 ltoken_getRawString (op->content.id)));
1455 break;
1456
1457 case OPF_MAP:
1458 llcontbug (message ("map: .%s",
1459 ltoken_getRawString (op->content.id)));
1460 break;
1461
1462 case OPF_MSELECT:
1463 {
1464 sRef rec = fixTermNode (termNodeList_head (args), f, cl);
1465 sRef ret;
1466 ctype ct = ctype_realType (sRef_deriveType (rec, cl));
1467 cstring fieldname = ltoken_getRawString (op->content.id);
1468
1469 ct = ctype_realType (ct);
1470
1471 /*
1472 ** does it correspond to a typedef struct field
1473 **
1474 ** (kind of kludgey, but there is no direct way to
1475 ** tell if it is an lsl operator instead)
1476 */
1477
1478 if (ctype_isStructorUnion (ct) &&
1479 uentry_isValid
1480 (uentryList_lookupField (ctype_getFields (ct), fieldname)))
1481 {
1482 cstring fname = cstring_copy (fieldname);
1483
1484 ret = sRef_buildField (rec, fname);
1485 cstring_markOwned (fname);
1486 }
1487 else
1488 {
1489 ret = sRef_undefined;
1490 }
1491
1492 return ret;
1493 }
1494 case OPF_MMAP:
1495 {
1496 sRef rec = fixTermNode (termNodeList_head (args), f, cl);
1497 sRef ret = sRef_undefined;
1498 ctype ct = ctype_realType (sRef_deriveType (rec, cl));
1499 cstring fieldname = ltoken_getRawString (op->content.id);
1500
1501 /*
1502 ** does it correspond to a typedef struct field
1503 */
1504
1505 if (ctype_isPointer (ct))
1506 {
1507 ctype ctb = ctype_realType (ctype_baseArrayPtr (ct));
1508
1509 if (ctype_isStructorUnion (ctb) &&
1510 uentry_isValid (uentryList_lookupField
1511 (ctype_getFields (ctb), fieldname)))
1512 {
1513 cstring fname = cstring_copy (fieldname);
1514
1515 ret = sRef_buildArrow (rec, fname);
1516 cstring_markOwned (fname);
1517 }
1518 }
1519
1520 return ret;
1521 }
1522 }
1523 }
1524
1525 return sRef_undefined;
1526}
1527
1528/*
1529** fixModifies
1530**
1531** o replace anything in modifies that is bound with let with value
1532** o replace spec variables with internal state
1533** o replace paramaters with paramno identifiers
1534** o replace globals with their usymid's
1535** o make everything sRefs
1536*/
1537
1538static /*@exposed@*/ sRef fixTermNode (termNode n, fcnNode f, uentryList cl)
1539{
1540 if (n != (termNode) 0)
1541 {
1542 switch (n->kind)
1543 {
1544 case TRM_LITERAL:
1545 break;
1546 case TRM_CONST:
1547 case TRM_VAR:
1548 case TRM_ZEROARY:
1549 {
1550 cstring s = ltoken_getRawString (n->literal);
1551 termNode tl = getLetDecl (s, f);
1552
1553 if (tl != (termNode) 0)
1554 {
1555 return (fixTermNode (tl, f, cl));
1556 }
1557 else
1558 {
1559 int i = getParamNo (s, f);
1560
1561 if (i < 0)
1562 {
1563 usymId usym = usymtab_getId (s);
1564
1565 if (usymId_isInvalid (usym))
1566 {
1567 if (usymtab_existsEither (s))
1568 {
1569 return sRef_makeSpecState ();
1570 }
1571 else
1572 {
1573 llcontbuglit ("Invalid symbol in modifies list");
1574 return sRef_undefined;
1575 }
1576 }
1577 else
6970c11b 1578 return (sRef_makeGlobal (usym, ctype_unknown, stateInfo_currentLoc ()));
616915dd 1579 }
1580
1581 else
1582 {
6970c11b 1583 sRef p = sRef_makeParam (i, ctype_unknown, stateInfo_currentLoc ());
1584 return (p);
616915dd 1585 }
1586 }
1587 }
1588 case TRM_APPLICATION:
1589 {
1590 nameNode nn = n->name;
1591
1592 if (nn != (nameNode) 0)
1593 {
1594 if (nn->isOpId)
1595 {
1596 /* must we handle n->given ? skip for now */
1597
1598 llfatalbug
1599 (message ("fixTermNode: expect non-empty nameNode: "
1600 "TRM_APPLICATION: %q",
1601 nameNode_unparse (nn)));
1602 }
1603 else
1604 {
1605 sRef sr;
1606
1607 sr = processTermNode (nn->content.opform, n->args, f, cl);
1608 return (sr);
1609 }
1610 }
1611
1612 return sRef_undefined;
1613 }
1614 case TRM_UNCHANGEDALL:
1615 case TRM_UNCHANGEDOTHERS:
1616 case TRM_SIZEOF:
1617 case TRM_QUANTIFIER:
1618 return sRef_undefined;
1619 }
1620 }
1621
1622 return sRef_undefined;
1623}
1624
1625static
1626/*@only@*/ sRefSet fixModifies (fcnNode f, uentryList cl)
1627{
1628 static bool shownWarning = FALSE;
1629 modifyNode m = f->modify;
1630 sRefSet sl = sRefSet_new ();
1631
1632 if (m != (modifyNode) 0)
1633 {
1634 if (m->hasStoreRefList)
1635 {
1636 storeRefNodeList srefs = m->list;
1637
1638 storeRefNodeList_elements (srefs, i)
1639 {
1640 if (storeRefNode_isObj (i) || storeRefNode_isType (i))
1641 {
1642 if (!shownWarning)
1643 {
1644 fileloc loc = fileloc_fromTok (f->name);
1645
1646 llmsg (message
1647 ("%q: Warning: object and type modifications "
11db3170 1648 "not understood by Splint",
616915dd 1649 fileloc_unparse (loc)));
1650 fileloc_free (loc);
1651 shownWarning = TRUE;
1652 }
1653 }
1654 else if (storeRefNode_isSpecial (i))
1655 {
1656 sl = sRefSet_insert (sl, i->content.ref);
1657 }
1658 else if (storeRefNode_isTerm (i))
1659 {
1660 sRef s = fixTermNode (i->content.term, f, cl);
1661
1662 if (sRef_isKnown (s))
1663 {
1664 sl = sRefSet_insert (sl, s);
1665 }
1666 }
1667 else
1668 {
1669 BADEXIT;
1670 }
1671 } end_storeRefNodeList_elements;
1672
1673 }
1674 }
1675
1676 return sl;
1677}
1678
1679static /*@only@*/ cstring
1680paramNode_name (paramNode x)
1681{
1682 return (typeExpr_name (x->paramdecl));
1683}
1684
1685static /*@only@*/ uentry
1686paramNode_toUentry (paramNode p)
1687{
1688 if (p != (paramNode) 0)
1689 {
1690 if (p->kind == PELIPSIS)
1691 {
1692 return uentry_makeElipsisMarker ();
1693 }
1694 else
1695 {
1696 qtype ct = convertLclTypeSpecNode (p->type);
1697 ctype cr = convertTypeExpr (qtype_getType (ct), p->paramdecl);
1698 cstring pname = (p->paramdecl == (typeExpr)0) ? cstring_undefined
1699 : paramNode_name (p);
6970c11b 1700 uentry ue = uentry_makeVariableParam (pname, cr, g_currentloc);
616915dd 1701
1702 uentry_reflectQualifiers (ue, qtype_getQuals (ct));
1703 qtype_free (ct);
1704 return (ue);
1705 }
1706 }
1707 else
1708 {
1709 llcontbuglit ("paramNode_toUentry: NULL");
1710 return uentry_undefined;
1711 }
1712 BADEXIT;
1713}
1714
1715static uentryList
1716 paramNodeList_toUentryList (paramNodeList p)
1717{
1718 uentryList cl = uentryList_new ();
1719
1720 if (paramNodeList_isNull (p)) return (cl);
1721
1722 paramNodeList_elements (p, current)
1723 {
1724 cl = uentryList_add (cl, paramNode_toUentry (current));
1725 } end_paramNodeList_elements;
1726
1727 return cl;
1728}
1729
1730
This page took 0.291282 seconds and 5 git commands to generate.