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