src: Updated uses of CSV library
[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 metrics_base
21 import mclasses_metrics
22 import semantize
23
24 redef class ToolContext
25 var nullables_metrics_phase: Phase = new NullablesMetricsPhase(self, null)
26 end
27
28 private class NullablesMetricsPhase
29 super Phase
30 redef fun process_mainmodule(mainmodule, given_mmodules)
31 do
32 if not toolcontext.opt_nullables.value and not toolcontext.opt_all.value then return
33
34 var csv = toolcontext.opt_csv.value
35 var out = "{toolcontext.opt_dir.value or else "metrics"}/nullables"
36 out.mkdir
37
38 print toolcontext.format_h1("\n# Nullable metrics")
39
40 var model = toolcontext.modelbuilder.model
41 var model_view = model.private_view
42
43 var metrics = new MetricSet
44 metrics.register(new CNBA(mainmodule, model_view))
45 metrics.register(new CNBNA(mainmodule, model_view))
46
47 var mclasses = new HashSet[MClass]
48 for mpackage in model.mpackages do
49
50 print toolcontext.format_h2("\n ## package {mpackage}")
51
52 for mgroup in mpackage.mgroups do
53 if mgroup.mmodules.is_empty then continue
54 metrics.clear
55
56 # Scalar metrics
57 print toolcontext.format_h3(" `- group {mgroup.full_name}")
58 var mod_mclasses = new HashSet[MClass]
59 for mmodule in mgroup.mmodules do mod_mclasses.add_all(mmodule.intro_mclasses)
60 if mod_mclasses.is_empty then continue
61 mclasses.add_all(mod_mclasses)
62 metrics.collect(new HashSet[MClass].from(mod_mclasses))
63 metrics.to_console(1, not toolcontext.opt_nocolors.value)
64 if csv then metrics.to_csv.write_to_file("{out}/{mgroup}.csv")
65 end
66 end
67 if not mclasses.is_empty then
68 metrics.clear
69 # Global metrics
70 print toolcontext.format_h2("\n ## global metrics")
71 metrics.collect(mclasses)
72 metrics.to_console(1, not toolcontext.opt_nocolors.value)
73 if csv then metrics.to_csv.write_to_file("{out}/summary.csv")
74 end
75
76 compute_nullables_metrics(toolcontext.modelbuilder)
77 end
78 end
79
80 # Class Metric: Number of nullables MAttributes
81 class CNBNA
82 super MClassMetric
83 super IntMetric
84 redef fun name do return "cnbna"
85 redef fun desc do return "number of accessible nullable attributes (inherited + local)"
86
87 var mainmodule: MModule
88 var model_view: ModelView
89
90 init(mainmodule: MModule, model_view: ModelView) do
91 self.mainmodule = mainmodule
92 self.model_view = model_view
93 end
94
95 redef fun collect(mclasses) do
96 for mclass in mclasses do
97 var all = mclass.collect_accessible_mattributes(model_view)
98 for mattr in all do
99 if mattr.is_nullable then values.inc(mclass)
100 end
101 end
102 end
103 end
104
105 redef class MAttribute
106 # Is this attribute nullable for sure?
107 #
108 # This mean that its introduction is declarred with a nullable static type
109 # since attributes are invariant this will work on most cases
110 # attributes with static type anchored with a virtual type are not "nullable for-sure"
111 # because this type can be redefined in subclasses
112 private fun is_nullable: Bool do return intro.static_mtype isa MNullableType
113 end
114
115 private class NullableSends
116 super Visitor
117 var modelbuilder: ModelBuilder
118 var nclassdef: AClassdef
119
120 var total_sends: Int = 0
121 var nullable_sends: Int = 0
122 var nullable_eq_sends: Int = 0
123 var buggy_sends: Int = 0
124
125 # Get a new visitor on a classef to add type count in `typecount`.
126 init(modelbuilder: ModelBuilder, nclassdef: AClassdef)
127 do
128 self.modelbuilder = modelbuilder
129 self.nclassdef = nclassdef
130 end
131
132 redef fun visit(n)
133 do
134 n.visit_all(self)
135 if n isa ASendExpr then
136 self.total_sends += 1
137 var t = n.n_expr.mtype
138 if t == null then
139 self.buggy_sends += 1
140 return
141 end
142 t = t.anchor_to(self.nclassdef.mclassdef.mmodule, self.nclassdef.mclassdef.bound_mtype)
143 if t isa MNullableType then
144 var p = n.callsite.mproperty
145 if p.is_null_safe then
146 self.nullable_eq_sends += 1
147 else
148 self.nullable_sends += 1
149 end
150 else if t isa MClassType then
151 # Nothing
152 else
153 n.debug("Problem: strange receiver type found: {t} ({t.class_name})")
154 end
155 end
156 end
157 end
158
159 # Visit the AST and print metrics about the usage of send on nullable reciever.
160 fun compute_nullables_metrics(modelbuilder: ModelBuilder)
161 do
162 print "--- Sends on Nullable Receiver ---"
163 var total_sends = 0
164 var nullable_sends = 0
165 var nullable_eq_sends = 0
166 var buggy_sends = 0
167
168 # Visit all the source code to collect data
169 for nmodule in modelbuilder.nmodules do
170 for nclassdef in nmodule.n_classdefs do
171 var visitor = new NullableSends(modelbuilder, nclassdef)
172 visitor.enter_visit(nclassdef)
173 total_sends += visitor.total_sends
174 nullable_sends += visitor.nullable_sends
175 nullable_eq_sends += visitor.nullable_eq_sends
176 buggy_sends += visitor.buggy_sends
177 end
178 end
179 print "Total number of sends: {total_sends}"
180 print "Number of sends on a unsafe nullable receiver: {nullable_sends} ({div(nullable_sends*100,total_sends)}%)"
181 print "Number of sends on a safe nullable receiver: {nullable_eq_sends} ({div(nullable_eq_sends*100,total_sends)}%)"
182 print "Number of buggy sends (cannot determine the type of the receiver): {buggy_sends} ({div(buggy_sends*100,total_sends)}%)"
183 end