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