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