1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2012 Jean Privat <jean@pryen.org>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # A program that collects various data about nit programs and libraries
19 # Collected datas are :
21 # * number of classes (interface, class, enum, extern, abstract)
22 # * number of class definitions and refinments
23 # * number of properties
24 # * number of used types and runtime classes
28 import rapid_type_analysis
30 # A counter counts occurence of things
31 # Use this instead of a HashMap[E, Int]
33 # Total number of counted occurences
36 private var map
= new HashMap[E
, Int]
38 # The number of counted occurences of `e'
42 if map
.has_key
(e
) then return map
[e
]
46 # Count one more occurence of `e'
49 self.map
[e
] = self[e
] + 1
53 # Return an array of elements sorted by occurences
56 var res
= map
.keys
.to_a
57 res
.sort
!cmp a
, b
= map
[a
] <=> map
[b
]
62 # The job of this visitor is to resolve all types found
63 class ATypeCounterVisitor
65 var modelbuilder
: ModelBuilder
66 var nclassdef
: AClassdef
68 var typecount
: Counter[MType]
70 # Get a new visitor on a classef to add type count in `typecount'.
71 init(modelbuilder
: ModelBuilder, nclassdef
: AClassdef, typecount
: Counter[MType])
73 self.modelbuilder
= modelbuilder
74 self.nclassdef
= nclassdef
75 self.typecount
= typecount
81 var mtype
= modelbuilder
.resolve_mtype
(self.nclassdef
, n
)
83 self.typecount
.inc
(mtype
)
92 var modelbuilder
: ModelBuilder
93 var nclassdef
: AClassdef
97 # Get a new visitor on a classef to add type count in `typecount'.
98 init(modelbuilder
: ModelBuilder, nclassdef
: AClassdef, news
: Set[MClass])
100 self.modelbuilder
= modelbuilder
101 self.nclassdef
= nclassdef
107 if n
isa ANewExpr then
108 var mtype
= modelbuilder
.resolve_mtype
(self.nclassdef
, n
.n_type
)
109 if mtype
!= null then
110 assert mtype
isa MClassType
111 self.news
.add
(mtype
.mclass
)
121 var implicits
: Int = 0
125 if n
isa ASelfExpr then
127 if n
isa AImplicitSelfExpr then
137 var modelbuilder
: ModelBuilder
138 var nclassdef
: AClassdef
140 var total_sends
: Int = 0
141 var nullable_sends
: Int = 0
142 var buggy_sends
: Int = 0
144 # Get a new visitor on a classef to add type count in `typecount'.
145 init(modelbuilder
: ModelBuilder, nclassdef
: AClassdef)
147 self.modelbuilder
= modelbuilder
148 self.nclassdef
= nclassdef
154 if n
isa ASendExpr then
155 self.total_sends
+= 1
156 var t
= n
.n_expr
.mtype
158 self.buggy_sends
+= 1
161 t
= t
.anchor_to
(self.nclassdef
.mclassdef
.mmodule
, self.nclassdef
.mclassdef
.bound_mtype
)
162 if t
isa MNullableType then
163 self.nullable_sends
+= 1
164 else if t
isa MClassType then
167 n
.debug
("Problem: strange receiver type found: {t} ({t.class_name})")
173 # Visit all `nmodules' of a modelbuilder and compute statistics on the usage of explicit static types
174 fun count_ntypes
(modelbuilder
: ModelBuilder)
176 # Count each occurence of a specific static type
177 var typecount
= new Counter[MType]
179 # Visit all the source code to collect data
180 for nmodule
in modelbuilder
.nmodules
do
181 for nclassdef
in nmodule
.n_classdefs
do
182 var visitor
= new ATypeCounterVisitor(modelbuilder
, nclassdef
, typecount
)
183 visitor
.enter_visit
(nclassdef
)
188 print
"--- Statistics of the explitic static types ---"
189 print
"Total number of explicit static types: {typecount.total}"
190 if typecount
.total
== 0 then return
192 # types sorted by usage
193 var types
= typecount
.sort
195 # Display most used types (ie the last of `types')
196 print
"Most used types: "
198 if types
.length
< min
then min
= types
.length
200 var t
= types
[types
.length-i-1
]
201 print
" {t}: {typecount[t]}"
204 # Compute the distribution of type usage
205 print
"Distribution of type usage:"
210 if typecount
[t
] > limit
then
211 print
" <={limit}: {count} ({div(count*100,types.length)}% of types; {div(sum*100,typecount.total)}% of usage)"
214 while typecount
[t
] > limit
do limit
= limit
* 2
219 print
" <={limit}: {count} ({div(count*100,types.length)}% of types; {div(sum*100,typecount.total)}% of usage)"
222 fun visit_news
(modelbuilder
: ModelBuilder, mainmodule
: MModule)
224 print
"--- Dead classes ---"
225 # Count each occurence of a specific static type
226 var news
= new HashSet[MClass]
228 # Visit all the source code to collect data
229 for nmodule
in modelbuilder
.nmodules
do
230 for nclassdef
in nmodule
.n_classdefs
do
231 var visitor
= new ANewVisitor(modelbuilder
, nclassdef
, news
)
232 visitor
.enter_visit
(nclassdef
)
236 for c
in modelbuilder
.model
.mclasses
do
237 if c
.kind
== concrete_kind
and not news
.has
(c
) then
238 print
"{c.full_name}"
242 var hier
= mainmodule
.flatten_mclass_hierarchy
244 if c
.kind
!= abstract_kind
and not (c
.kind
== concrete_kind
and not news
.has
(c
)) then continue
246 for sup
in hier
[c
].greaters
do
247 for cd
in sup
.mclassdefs
do
248 for p
in cd
.intro_mproperties
do
249 if p
isa MAttribute then
250 continue label classes
255 print
"no attributes: {c.full_name}"
260 fun visit_self
(modelbuilder
: ModelBuilder)
262 print
"--- Explicit vs. Implicit Self ---"
263 # Visit all the source code to collect data
264 var visitor
= new ASelfVisitor
265 for nmodule
in modelbuilder
.nmodules
do
266 for nclassdef
in nmodule
.n_classdefs
do
267 visitor
.enter_visit
(nclassdef
)
270 print
"Total number of self: {visitor.total}"
271 print
"Total number of implicit self: {visitor.implicits} ({div(visitor.implicits*100,visitor.total)}%)"
274 fun visit_nullable_sends
(modelbuilder
: ModelBuilder)
276 print
"--- Sends on Nullable Reciever ---"
278 var nullable_sends
= 0
281 # Visit all the source code to collect data
282 for nmodule
in modelbuilder
.nmodules
do
283 for nclassdef
in nmodule
.n_classdefs
do
284 var visitor
= new NullableSends(modelbuilder
, nclassdef
)
285 visitor
.enter_visit
(nclassdef
)
286 total_sends
+= visitor
.total_sends
287 nullable_sends
+= visitor
.nullable_sends
288 buggy_sends
+= visitor
.buggy_sends
291 print
"Total number of sends: {total_sends}"
292 print
"Number of sends on a nullable receiver: {nullable_sends} ({div(nullable_sends*100,total_sends)}%)"
293 print
"Number of buggy sends (cannot determine the type of the receiver): {buggy_sends} ({div(buggy_sends*100,total_sends)}%)"
296 # Compute general statistics on a model
297 fun compute_statistics
(model
: Model)
299 print
"--- Statistics of the model ---"
300 var nbmod
= model
.mmodules
.length
301 print
"Number of modules: {nbmod}"
305 var nbcla
= model
.mclasses
.length
306 var nbcladef
= model
.mclassdef_hierarchy
.length
307 print
"Number of classes: {nbcla}"
309 # determine the distribution of:
310 # * class kinds (interface, abstract class, etc.)
311 # * refinex classes (vs. unrefined ones)
312 var kinds
= new Counter[MClassKind]
314 for c
in model
.mclasses
do
316 if c
.mclassdefs
.length
> 1 then
320 for k
in kinds
.sort
do
322 print
" Number of {k} kind: {v} ({div(v*100,nbcla)}%)"
328 print
"Nomber of class definitions: {nbcladef}"
329 print
"Number of refined classes: {refined} ({div(refined*100,nbcla)}%)"
330 print
"Average number of class refinments by classes: {div(nbcladef-nbcla,nbcla)}"
331 print
"Average number of class refinments by refined classes: {div(nbcladef-nbcla,refined)}"
335 var nbprop
= model
.mproperties
.length
336 print
"Number of properties: {model.mproperties.length}"
339 # Compute class tables for the classes of the program main
340 fun compute_tables
(main
: MModule)
342 var model
= main
.model
344 var nc
= 0 # Number of runtime classes
345 var nl
= 0 # Number of usages of class definitions (a class definition can be used more than once)
346 var nhp
= 0 # Number of usages of properties (a property can be used more than once)
347 var npas
= 0 # Number of usages of properties without lookup (easy easy case, easier that CHA)
349 # Collect the full class hierarchy
350 var hier
= main
.flatten_mclass_hierarchy
352 # Skip classes without direct instances
353 if c
.kind
== interface_kind
or c
.kind
== abstract_kind
then continue
357 # Now, we need to collect all properties defined/inherited/imported
358 # So, visit all definitions of all super-classes
359 for sup
in hier
[c
].greaters
do
360 for cd
in sup
.mclassdefs
do
363 # Now, search properties introduced
364 for p
in cd
.intro_mproperties
do
367 # Select property definition
368 if p
.mpropdefs
.length
== 1 then
371 var sels
= p
.lookup_definitions
(main
, c
.mclassdefs
.first
.bound_mtype
)
372 if sels
.length
> 1 then
373 print
"conflict for {p.full_name} in class {c.full_name}: {sels.join(", ")}"
374 else if sels
.is_empty
then
375 print
"ERROR: no property for {p.full_name} in class {c.full_name}!"
383 print
"--- Construction of tables ---"
384 print
"Number of runtime classes: {nc} (excluding interfaces and abstract classes)"
385 print
"Average number of composing class definition by runtime class: {div(nl,nc)}"
386 print
"Total size of tables (classes and instances): {nhp} (not including stuff like info for subtyping or call-next-method)"
387 print
"Average size of table by runtime class: {div(nhp,nc)}"
388 print
"Values never redefined: {npas} ({div(npas*100,nhp)}%)"
391 # Helper function to display n/d and handle division by 0
392 fun div
(n
: Int, d
: Int): String
394 if d
== 0 then return "na"
395 return ((100*n
/d
).to_f
/100.0).to_precision
(2)
398 # Create a dot file representing the module hierarchy of a model.
399 # Importation relation is represented with arrow
400 # Nesting relation is represented with nested boxes
401 fun generate_module_hierarchy
(model
: Model)
404 buf
.append
("digraph \{\n")
405 buf
.append
("node [shape=box];\n")
406 buf
.append
("rankdir=BT;\n")
407 for mmodule
in model
.mmodules
do
408 if mmodule
.direct_owner
== null then
409 generate_module_hierarchy_for
(mmodule
, buf
)
412 for mmodule
in model
.mmodules
do
413 for s
in mmodule
.in_importation
.direct_greaters
do
414 buf
.append
("\"{mmodule}\
" -> \"{s}\
";\n")
418 var f
= new OFStream.open
("module_hierarchy.dot")
423 # Helper function for `generate_module_hierarchy'.
424 # Create graphviz nodes for the module and recusrively for its nested modules
425 private fun generate_module_hierarchy_for
(mmodule
: MModule, buf
: Buffer)
427 if mmodule
.in_nesting
.direct_greaters
.is_empty
then
428 buf
.append
("\"{mmodule.name}\
";\n")
430 buf
.append
("subgraph \"cluster_
{mmodule.name}\
" \{label=\"\
"\n")
431 buf
.append
("\"{mmodule.name}\
";\n")
432 for s
in mmodule
.in_nesting
.direct_greaters
do
433 generate_module_hierarchy_for
(s
, buf
)
439 # Create a dot file representing the class hierarchy of a model.
440 fun generate_class_hierarchy
(mmodule
: MModule)
443 buf
.append
("digraph \{\n")
444 buf
.append
("node [shape=box];\n")
445 buf
.append
("rankdir=BT;\n")
446 var hierarchy
= mmodule
.flatten_mclass_hierarchy
447 for mclass
in hierarchy
do
448 buf
.append
("\"{mclass}\
" [label=\"{mclass}\
"];\n")
449 for s
in hierarchy
[mclass
].direct_greaters
do
450 buf
.append
("\"{mclass}\
" -> \"{s}\
";\n")
454 var f
= new OFStream.open
("class_hierarchy.dot")
459 # Create a dot file representing the classdef hierarchy of a model.
460 # For a simple user of the model, the classdef hierarchy is not really usefull, it is more an internal thing.
461 fun generate_classdef_hierarchy
(model
: Model)
464 buf
.append
("digraph \{\n")
465 buf
.append
("node [shape=box];\n")
466 buf
.append
("rankdir=BT;\n")
467 for mmodule
in model
.mmodules
do
468 for mclassdef
in mmodule
.mclassdefs
do
469 buf
.append
("\"{mclassdef} {mclassdef.bound_mtype}\
" [label=\"{mclassdef.mmodule}\\n
{mclassdef.bound_mtype}\
"];\n")
470 for s
in mclassdef
.in_hierarchy
.direct_greaters
do
471 buf
.append
("\"{mclassdef} {mclassdef.bound_mtype}\
" -> \"{s} {s.bound_mtype}\
";\n")
476 var f
= new OFStream.open
("classdef_hierarchy.dot")
481 fun generate_model_hyperdoc
(model
: Model)
484 buf
.append
("<html>\n<body>\n")
485 buf
.append
("<h1>Model</h1>\n")
487 buf
.append
("<h2>Modules</h2>\n")
488 for mmodule
in model
.mmodules
do
489 buf
.append
("<h3 id='module-{mmodule}'>{mmodule}</h3>\n")
491 buf
.append
("<dt>direct owner</dt>\n")
492 var own
= mmodule
.direct_owner
493 if own
!= null then buf
.append
("<dd>{linkto(own)}</dd>\n")
494 buf
.append
("<dt>nested</dt>\n")
495 for x
in mmodule
.in_nesting
.direct_greaters
do
496 buf
.append
("<dd>{linkto(x)}</dd>\n")
498 buf
.append
("<dt>direct import</dt>\n")
499 for x
in mmodule
.in_importation
.direct_greaters
do
500 buf
.append
("<dd>{linkto(x)}</dd>\n")
502 buf
.append
("<dt>direct clients</dt>\n")
503 for x
in mmodule
.in_importation
.direct_smallers
do
504 buf
.append
("<dd>{linkto(x)}</dd>\n")
506 buf
.append
("<dt>introduced classes</dt>\n")
507 for x
in mmodule
.mclassdefs
do
508 if not x
.is_intro
then continue
509 buf
.append
("<dd>{linkto(x.mclass)} by {linkto(x)}</dd>\n")
511 buf
.append
("<dt>refined classes</dt>\n")
512 for x
in mmodule
.mclassdefs
do
513 if x
.is_intro
then continue
514 buf
.append
("<dd>{linkto(x.mclass)} by {linkto(x)}</dd>\n")
516 buf
.append
("</dl>\n")
518 buf
.append
("<h2>Classes</h2>\n")
519 for mclass
in model
.mclasses
do
520 buf
.append
("<h3 id='class-{mclass}'>{mclass}</h3>\n")
522 buf
.append
("<dt>module of introduction</dt>\n")
523 buf
.append
("<dd>{linkto(mclass.intro_mmodule)}</dd>\n")
524 buf
.append
("<dt>class definitions</dt>\n")
525 for x
in mclass
.mclassdefs
do
526 buf
.append
("<dd>{linkto(x)} in {linkto(x.mmodule)}</dd>\n")
528 buf
.append
("</dl>\n")
530 buf
.append
("<h2>Class Definitions</h2>\n")
531 for mclass
in model
.mclasses
do
532 for mclassdef
in mclass
.mclassdefs
do
533 buf
.append
("<h3 id='classdef-{mclassdef}'>{mclassdef}</h3>\n")
535 buf
.append
("<dt>module</dt>\n")
536 buf
.append
("<dd>{linkto(mclassdef.mmodule)}</dd>\n")
537 buf
.append
("<dt>class</dt>\n")
538 buf
.append
("<dd>{linkto(mclassdef.mclass)}</dd>\n")
539 buf
.append
("<dt>direct refinements</dt>\n")
540 for x
in mclassdef
.in_hierarchy
.direct_greaters
do
541 if x
.mclass
!= mclass
then continue
542 buf
.append
("<dd>{linkto(x)} in {linkto(x.mmodule)}</dd>\n")
544 buf
.append
("<dt>direct refinemees</dt>\n")
545 for x
in mclassdef
.in_hierarchy
.direct_smallers
do
546 if x
.mclass
!= mclass
then continue
547 buf
.append
("<dd>{linkto(x)} in {linkto(x.mmodule)}</dd>\n")
549 buf
.append
("<dt>direct superclasses</dt>\n")
550 for x
in mclassdef
.supertypes
do
551 buf
.append
("<dd>{linkto(x.mclass)} by {x}</dd>\n")
553 buf
.append
("<dt>introduced properties</dt>\n")
554 for x
in mclassdef
.mpropdefs
do
555 if not x
.is_intro
then continue
556 buf
.append
("<dd>{linkto(x.mproperty)} by {linkto(x)}</dd>\n")
558 buf
.append
("<dt>redefined properties</dt>\n")
559 for x
in mclassdef
.mpropdefs
do
560 if x
.is_intro
then continue
561 buf
.append
("<dd>{linkto(x.mproperty)} by {linkto(x)}</dd>\n")
563 buf
.append
("</dl>\n")
566 buf
.append
("<h2>Properties</h2>\n")
567 for mprop
in model
.mproperties
do
568 buf
.append
("<h3 id='property-{mprop}'>{mprop}</h3>\n")
570 buf
.append
("<dt>module of introdcution</dt>\n")
571 buf
.append
("<dd>{linkto(mprop.intro_mclassdef.mmodule)}</dd>\n")
572 buf
.append
("<dt>class of introduction</dt>\n")
573 buf
.append
("<dd>{linkto(mprop.intro_mclassdef.mclass)}</dd>\n")
574 buf
.append
("<dt>class definition of introduction</dt>\n")
575 buf
.append
("<dd>{linkto(mprop.intro_mclassdef)}</dd>\n")
576 buf
.append
("<dt>property definitions</dt>\n")
577 for x
in mprop
.mpropdefs
do
578 buf
.append
("<dd>{linkto(x)} in {linkto(x.mclassdef)}</dd>\n")
580 buf
.append
("</dl>\n")
582 buf
.append
("<h2>Property Definitions</h2>\n")
583 for mprop
in model
.mproperties
do
584 for mpropdef
in mprop
.mpropdefs
do
585 buf
.append
("<h3 id='propdef-{mpropdef}'>{mpropdef}</h3>\n")
587 buf
.append
("<dt>module</dt>\n")
588 buf
.append
("<dd>{linkto(mpropdef.mclassdef.mmodule)}</dd>\n")
589 buf
.append
("<dt>class</dt>\n")
590 buf
.append
("<dd>{linkto(mpropdef.mclassdef.mclass)}</dd>\n")
591 buf
.append
("<dt>class definition</dt>\n")
592 buf
.append
("<dd>{linkto(mpropdef.mclassdef)}</dd>\n")
593 buf
.append
("<dt>super definitions</dt>\n")
594 for x
in mpropdef
.mproperty
.lookup_super_definitions
(mpropdef
.mclassdef
.mmodule
, mpropdef
.mclassdef
.bound_mtype
) do
595 buf
.append
("<dd>{linkto(x)} in {linkto(x.mclassdef)}</dd>\n")
599 buf
.append
("</body></html>\n")
600 var f
= new OFStream.open
("model.html")
605 fun linkto
(o
: Object): String
607 if o
isa MModule then
608 return "<a href='#module-{o}'>{o}</a>"
609 else if o
isa MClass then
610 return "<a href='#class-{o}'>{o}</a>"
611 else if o
isa MClassDef then
612 return "<a href='#classdef-{o}'>{o}</a>"
613 else if o
isa MProperty then
614 return "<a href='#property-{o}'>{o}</a>"
615 else if o
isa MPropDef then
616 return "<a href='#propdef-{o}'>{o}</a>"
618 print
"cannot linkto {o.class_name}"
623 fun runtime_type
(modelbuilder
: ModelBuilder, mainmodule
: MModule)
625 var analysis
= modelbuilder
.do_rapid_type_analysis
(mainmodule
)
627 print
"--- Type Analysis ---"
628 print
"Number of live runtime types (instantied resolved type): {analysis.live_types.length}"
629 if analysis
.live_types
.length
< 8 then print
"\t{analysis.live_types.join(" ")}"
630 print
"Number of live method definitions: {analysis.live_methoddefs.length}"
631 if analysis
.live_methoddefs
.length
< 8 then print
"\t{analysis.live_methoddefs.join(" ")}"
632 print
"Number of live customized method definitions: {analysis.live_customized_methoddefs.length}"
633 if analysis
.live_customized_methoddefs
.length
< 8 then print
"\t{analysis.live_customized_methoddefs.join(" ")}"
634 print
"Number of live runtime cast types (ie used in as and isa): {analysis.live_cast_types.length}"
635 if analysis
.live_cast_types
.length
< 8 then print
"\t{analysis.live_cast_types.join(" ")}"
638 # Create a tool context to handle options and paths
639 var toolcontext
= new ToolContext
640 # We do not add other options, so process them now!
641 toolcontext
.process_options
643 # We need a model to collect stufs
644 var model
= new Model
645 # An a model builder to parse files
646 var modelbuilder
= new ModelBuilder(model
, toolcontext
)
648 # Here we load an process all modules passed on the command line
649 var mmodules
= modelbuilder
.parse_and_build
(toolcontext
.option_context
.rest
)
650 modelbuilder
.full_propdef_semantic_analysis
652 if mmodules
.length
== 0 then return
654 var mainmodule
: MModule
655 if mmodules
.length
== 1 then
656 mainmodule
= mmodules
.first
658 # We need a main module, so we build it by importing all modules
659 mainmodule
= new MModule(model
, null, "<main>", new Location(null, 0, 0, 0, 0))
660 mainmodule
.set_imported_mmodules
(mmodules
)
663 # Now, we just have to play with the model!
664 print
"*** STATS ***"
667 compute_statistics
(model
)
670 visit_self
(modelbuilder
)
673 visit_nullable_sends
(modelbuilder
)
676 #visit_news(modelbuilder, mainmodule)
679 count_ntypes
(modelbuilder
)
681 generate_module_hierarchy
(model
)
682 generate_classdef_hierarchy
(model
)
683 generate_class_hierarchy
(mainmodule
)
684 generate_model_hyperdoc
(model
)
687 compute_tables
(mainmodule
)
690 runtime_type
(modelbuilder
, mainmodule
)