895f7d5cf3689b2ed148d7a82969413d0801ce8c
[nit.git] / src / metrics / nullables_metrics.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2012 Jean Privat <jean@pryen.org>
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16
17 # Statistics about the usage of nullables
18 module nullables_metrics
19
20 import modelbuilder
21 private import typing
22 import model_utils
23 import mclasses_metrics
24 import frontend
25
26 redef class ToolContext
27 var nullables_metrics_phase: Phase = new NullablesMetricsPhase(self, null)
28 end
29
30 private class NullablesMetricsPhase
31 super Phase
32 redef fun process_mainmodule(mainmodule, given_mmodules)
33 do
34 if not toolcontext.opt_nullables.value and not toolcontext.opt_all.value then return
35
36 var csv = toolcontext.opt_csv.value
37 var out = "{toolcontext.opt_dir.value or else "metrics"}/nullables"
38 out.mkdir
39
40 print toolcontext.format_h1("\n# Nullable metrics")
41
42 var metrics = new MetricSet
43 var min_vis = private_visibility
44 metrics.register(new CNBA(mainmodule, min_vis))
45 metrics.register(new CNBNA(mainmodule, min_vis))
46
47 var model = toolcontext.modelbuilder.model
48 var mclasses = new HashSet[MClass]
49 for mproject in model.mprojects do
50
51 print toolcontext.format_h2("\n ## project {mproject}")
52
53 for mgroup in mproject.mgroups do
54 if mgroup.mmodules.is_empty then continue
55 metrics.clear
56
57 # Scalar metrics
58 print toolcontext.format_h3(" `- group {mgroup.full_name}")
59 var mod_mclasses = new HashSet[MClass]
60 for mmodule in mgroup.mmodules do mod_mclasses.add_all(mmodule.intro_mclasses)
61 if mod_mclasses.is_empty then continue
62 mclasses.add_all(mod_mclasses)
63 metrics.collect(new HashSet[MClass].from(mod_mclasses))
64 metrics.to_console(1, not toolcontext.opt_nocolors.value)
65 if csv then metrics.to_csv.save("{out}/{mgroup}.csv")
66 end
67 end
68 if not mclasses.is_empty then
69 metrics.clear
70 # Global metrics
71 print toolcontext.format_h2("\n ## global metrics")
72 metrics.collect(mclasses)
73 metrics.to_console(1, not toolcontext.opt_nocolors.value)
74 if csv then metrics.to_csv.save("{out}/summary.csv")
75 end
76
77 compute_nullables_metrics(toolcontext.modelbuilder)
78 end
79 end
80
81 # Class Metric: Number of nullables MAttributes
82 class CNBNA
83 super MClassMetric
84 super IntMetric
85 redef fun name do return "cnbna"
86 redef fun desc do return "number of accessible nullable attributes (inherited + local)"
87
88 var mainmodule: MModule
89 var min_visibility: MVisibility
90
91 init(mainmodule: MModule, min_visibility: MVisibility) do
92 self.mainmodule = mainmodule
93 self.min_visibility = min_visibility
94 end
95
96 redef fun collect(mclasses) do
97 for mclass in mclasses do
98 var all = mclass.all_mattributes(mainmodule, min_visibility)
99 for mattr in all do
100 if mattr.is_nullable then values.inc(mclass)
101 end
102 end
103 end
104 end
105
106
107 private class NullableSends
108 super Visitor
109 var modelbuilder: ModelBuilder
110 var nclassdef: AClassdef
111
112 var total_sends: Int = 0
113 var nullable_sends: Int = 0
114 var buggy_sends: Int = 0
115
116 # Get a new visitor on a classef to add type count in `typecount`.
117 init(modelbuilder: ModelBuilder, nclassdef: AClassdef)
118 do
119 self.modelbuilder = modelbuilder
120 self.nclassdef = nclassdef
121 end
122
123 redef fun visit(n)
124 do
125 n.visit_all(self)
126 if n isa ASendExpr then
127 self.total_sends += 1
128 var t = n.n_expr.mtype
129 if t == null then
130 self.buggy_sends += 1
131 return
132 end
133 t = t.anchor_to(self.nclassdef.mclassdef.mmodule, self.nclassdef.mclassdef.bound_mtype)
134 if t isa MNullableType then
135 self.nullable_sends += 1
136 else if t isa MClassType then
137 # Nothing
138 else
139 n.debug("Problem: strange receiver type found: {t} ({t.class_name})")
140 end
141 end
142 end
143 end
144
145 # Visit the AST and print metrics about the usage of send on nullable reciever.
146 fun compute_nullables_metrics(modelbuilder: ModelBuilder)
147 do
148 print "--- Sends on Nullable Receiver ---"
149 var total_sends = 0
150 var nullable_sends = 0
151 var buggy_sends = 0
152
153 # Visit all the source code to collect data
154 for nmodule in modelbuilder.nmodules do
155 for nclassdef in nmodule.n_classdefs do
156 var visitor = new NullableSends(modelbuilder, nclassdef)
157 visitor.enter_visit(nclassdef)
158 total_sends += visitor.total_sends
159 nullable_sends += visitor.nullable_sends
160 buggy_sends += visitor.buggy_sends
161 end
162 end
163 print "Total number of sends: {total_sends}"
164 print "Number of sends on a nullable receiver: {nullable_sends} ({div(nullable_sends*100,total_sends)}%)"
165 print "Number of buggy sends (cannot determine the type of the receiver): {buggy_sends} ({div(buggy_sends*100,total_sends)}%)"
166 end