rust_code_analysis/metrics/
npa.rs

1use serde::ser::{SerializeStruct, Serializer};
2use serde::Serialize;
3use std::fmt;
4
5use crate::checker::Checker;
6use crate::langs::*;
7use crate::node::Node;
8use crate::*;
9
10/// The `Npa` metric.
11///
12/// This metric counts the number of public attributes
13/// of classes/interfaces.
14#[derive(Clone, Debug, Default)]
15pub struct Stats {
16    class_npa: usize,
17    interface_npa: usize,
18    class_na: usize,
19    interface_na: usize,
20    class_npa_sum: usize,
21    interface_npa_sum: usize,
22    class_na_sum: usize,
23    interface_na_sum: usize,
24    is_class_space: bool,
25}
26
27impl Serialize for Stats {
28    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
29    where
30        S: Serializer,
31    {
32        let mut st = serializer.serialize_struct("npa", 9)?;
33        st.serialize_field("classes", &self.class_npa_sum())?;
34        st.serialize_field("interfaces", &self.interface_npa_sum())?;
35        st.serialize_field("class_attributes", &self.class_na_sum())?;
36        st.serialize_field("interface_attributes", &self.interface_na_sum())?;
37        st.serialize_field("classes_average", &self.class_cda())?;
38        st.serialize_field("interfaces_average", &self.interface_cda())?;
39        st.serialize_field("total", &self.total_npa())?;
40        st.serialize_field("total_attributes", &self.total_na())?;
41        st.serialize_field("average", &self.total_cda())?;
42        st.end()
43    }
44}
45
46impl fmt::Display for Stats {
47    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48        write!(
49            f,
50            "classes: {}, interfaces: {}, class_attributes: {}, interface_attributes: {}, classes_average: {}, interfaces_average: {}, total: {}, total_attributes: {}, average: {}",
51            self.class_npa_sum(),
52            self.interface_npa_sum(),
53            self.class_na_sum(),
54            self.interface_na_sum(),
55            self.class_cda(),
56            self.interface_cda(),
57            self.total_npa(),
58            self.total_na(),
59            self.total_cda()
60        )
61    }
62}
63
64impl Stats {
65    /// Merges a second `Npa` metric into the first one
66    pub fn merge(&mut self, other: &Stats) {
67        self.class_npa_sum += other.class_npa_sum;
68        self.interface_npa_sum += other.interface_npa_sum;
69        self.class_na_sum += other.class_na_sum;
70        self.interface_na_sum += other.interface_na_sum;
71    }
72
73    /// Returns the number of class public attributes in a space.
74    #[inline(always)]
75    pub fn class_npa(&self) -> f64 {
76        self.class_npa as f64
77    }
78
79    /// Returns the number of interface public attributes in a space.
80    #[inline(always)]
81    pub fn interface_npa(&self) -> f64 {
82        self.interface_npa as f64
83    }
84
85    /// Returns the number of class attributes in a space.
86    #[inline(always)]
87    pub fn class_na(&self) -> f64 {
88        self.class_na as f64
89    }
90
91    /// Returns the number of interface attributes in a space.
92    #[inline(always)]
93    pub fn interface_na(&self) -> f64 {
94        self.interface_na as f64
95    }
96
97    /// Returns the number of class public attributes sum in a space.
98    #[inline(always)]
99    pub fn class_npa_sum(&self) -> f64 {
100        self.class_npa_sum as f64
101    }
102
103    /// Returns the number of interface public attributes sum in a space.
104    #[inline(always)]
105    pub fn interface_npa_sum(&self) -> f64 {
106        self.interface_npa_sum as f64
107    }
108
109    /// Returns the number of class attributes sum in a space.
110    #[inline(always)]
111    pub fn class_na_sum(&self) -> f64 {
112        self.class_na_sum as f64
113    }
114
115    /// Returns the number of interface attributes sum in a space.
116    #[inline(always)]
117    pub fn interface_na_sum(&self) -> f64 {
118        self.interface_na_sum as f64
119    }
120
121    /// Returns the class `Cda` metric value
122    ///
123    /// The `Class Data Accessibility` metric value for a class
124    /// is computed by dividing the `Npa` value of the class
125    /// by the total number of attributes defined in the class.
126    ///
127    /// This metric is an adaptation of the `Classified Class Data Accessibility` (`CCDA`)
128    /// security metric for not classified attributes.
129    /// Paper: <https://ieeexplore.ieee.org/abstract/document/5381538>
130    #[inline(always)]
131    pub fn class_cda(&self) -> f64 {
132        self.class_npa_sum() / self.class_na_sum as f64
133    }
134
135    /// Returns the interface `Cda` metric value
136    ///
137    /// The `Class Data Accessibility` metric value for an interface
138    /// is computed by dividing the `Npa` value of the interface
139    /// by the total number of attributes defined in the interface.
140    ///
141    /// This metric is an adaptation of the `Classified Class Data Accessibility` (`CCDA`)
142    /// security metric for not classified attributes.
143    /// Paper: <https://ieeexplore.ieee.org/abstract/document/5381538>
144    #[inline(always)]
145    pub fn interface_cda(&self) -> f64 {
146        // For the Java language it's not necessary to compute the metric value
147        // The metric value in Java can only be 1.0 or f64:NAN
148        if self.interface_npa_sum == self.interface_na_sum && self.interface_npa_sum != 0 {
149            1.0
150        } else {
151            self.interface_npa_sum() / self.interface_na_sum()
152        }
153    }
154
155    /// Returns the total `Cda` metric value
156    ///
157    /// The total `Class Data Accessibility` metric value
158    /// is computed by dividing the total `Npa` value
159    /// by the total number of attributes.
160    ///
161    /// This metric is an adaptation of the `Classified Class Data Accessibility` (`CCDA`)
162    /// security metric for not classified attributes.
163    /// Paper: <https://ieeexplore.ieee.org/abstract/document/5381538>
164    #[inline(always)]
165    pub fn total_cda(&self) -> f64 {
166        self.total_npa() / self.total_na()
167    }
168
169    /// Returns the total number of public attributes in a space.
170    #[inline(always)]
171    pub fn total_npa(&self) -> f64 {
172        self.class_npa_sum() + self.interface_npa_sum()
173    }
174
175    /// Returns the total number of attributes in a space.
176    #[inline(always)]
177    pub fn total_na(&self) -> f64 {
178        self.class_na_sum() + self.interface_na_sum()
179    }
180
181    // Accumulates the number of class and interface
182    // public and not public attributes into the sums
183    #[inline(always)]
184    pub(crate) fn compute_sum(&mut self) {
185        self.class_npa_sum += self.class_npa;
186        self.interface_npa_sum += self.interface_npa;
187        self.class_na_sum += self.class_na;
188        self.interface_na_sum += self.interface_na;
189    }
190
191    // Checks if the `Npa` metric is disabled
192    #[inline(always)]
193    pub(crate) fn is_disabled(&self) -> bool {
194        !self.is_class_space
195    }
196}
197
198#[doc(hidden)]
199pub trait Npa
200where
201    Self: Checker,
202{
203    fn compute(_node: &Node, _stats: &mut Stats) {}
204}
205
206impl Npa for PythonCode {}
207impl Npa for MozjsCode {}
208impl Npa for JavascriptCode {}
209impl Npa for TypescriptCode {}
210impl Npa for TsxCode {}
211impl Npa for RustCode {}
212impl Npa for CppCode {}
213impl Npa for PreprocCode {}
214impl Npa for CcommentCode {}
215
216impl Npa for JavaCode {
217    fn compute(node: &Node, stats: &mut Stats) {
218        use Java::*;
219
220        // Enables the `Npa` metric if computing stats of a class space
221        if Self::is_func_space(node) && stats.is_disabled() {
222            stats.is_class_space = true;
223        }
224
225        match node.object().kind_id().into() {
226            ClassBody => {
227                stats.class_na += node
228                    .children()
229                    .filter(|node| matches!(node.object().kind_id().into(), FieldDeclaration))
230                    .map(|declaration| {
231                        let attributes = declaration
232                            .children()
233                            .filter(|n| matches!(n.object().kind_id().into(), VariableDeclarator))
234                            .count();
235                        // The first child node contains the list of attribute modifiers
236                        // There are several modifiers that may be part of a field declaration
237                        // Source: https://docs.oracle.com/javase/tutorial/reflect/member/fieldModifiers.html
238                        if declaration.object().child(0).map_or(false, |modifiers| {
239                            // Looks for the `public` keyword in the list of attribute modifiers
240                            matches!(modifiers.kind_id().into(), Modifiers)
241                                && Node::new(modifiers)
242                                    .first_child(|id| id == Public)
243                                    .is_some()
244                        }) {
245                            stats.class_npa += attributes;
246                        }
247                        attributes
248                    })
249                    .sum::<usize>();
250            }
251            // Every field declaration in the body of an interface is implicitly public, static, and final
252            // Source: https://docs.oracle.com/javase/specs/jls/se7/html/jls-9.html
253            InterfaceBody => {
254                // Children nodes are filtered because Java interfaces
255                // can contain constants but also methods and nested types
256                // Source: https://docs.oracle.com/javase/tutorial/java/IandI/createinterface.html
257                stats.interface_na += node
258                    .children()
259                    .filter(|node| matches!(node.object().kind_id().into(), ConstantDeclaration))
260                    .flat_map(|node| node.children())
261                    .filter(|node| matches!(node.object().kind_id().into(), VariableDeclarator))
262                    .count();
263                stats.interface_npa = stats.interface_na;
264            }
265            _ => {}
266        }
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use std::path::PathBuf;
273
274    use super::*;
275
276    #[test]
277    fn java_single_attributes() {
278        check_metrics!(
279            "class X {
280                public byte a;      // +1
281                public short b;     // +1
282                public int c;       // +1
283                public long d;      // +1
284                public float e;     // +1
285                public double f;    // +1
286                public boolean g;   // +1
287                public char h;      // +1
288                byte i;
289                short j;
290                int k;
291                long l;
292                float m;
293                double n;
294                boolean o;
295                char p;
296            }",
297            "foo.java",
298            JavaParser,
299            npa,
300            [
301                (class_npa_sum, 8, usize),
302                (interface_npa_sum, 0, usize),
303                (class_na_sum, 16, usize),
304                (interface_na_sum, 0, usize),
305                (total_npa, 8, usize),
306                (total_na, 16, usize)
307            ],
308            [
309                (class_cda, 0.5),
310                (interface_cda, f64::NAN),
311                (total_cda, 0.5)
312            ]
313        );
314    }
315
316    #[test]
317    fn java_multiple_attributes() {
318        check_metrics!(
319            "class X {
320                public byte a1;                 // +1
321                public short b1, b2;            // +2
322                public int c1, c2, c3;          // +3
323                public long d1, d2, d3, d4;     // +4
324                public float e1, e2, e3, e4;    // +4
325                public double f1, f2, f3;       // +3
326                public boolean g1, g2;          // +2
327                public char h1;                 // +1
328                byte i1, i2, i3, i4;
329                short j1, j2, j3;
330                int k1, k2;
331                long l1;
332                float m1;
333                double n1, n2;
334                boolean o1, o2, o3;
335                char p1, p2, p3, p4;
336            }",
337            "foo.java",
338            JavaParser,
339            npa,
340            [
341                (class_npa_sum, 20, usize),
342                (interface_npa_sum, 0, usize),
343                (class_na_sum, 40, usize),
344                (interface_na_sum, 0, usize),
345                (total_npa, 20, usize),
346                (total_na, 40, usize)
347            ],
348            [
349                (class_cda, 0.5),
350                (interface_cda, f64::NAN),
351                (total_cda, 0.5)
352            ]
353        );
354    }
355
356    #[test]
357    fn java_initialized_attributes() {
358        check_metrics!(
359            "class X {
360                public byte a1 = 1;                             // +1
361                public short b1 = 2, b2;                        // +2
362                public int c1, c2 = 3, c3;                      // +3
363                public long d1 = 4, d2, d3, d4 = 5;             // +4
364                public float e1, e2 = 6.0f, e3 = 7.0f, e4;      // +4
365                public double f1 = 8.0, f2 = 9.0, f3 = 10.0;    // +3
366                public boolean g1 = true, g2;                   // +2
367                public char h1 = 'a';                           // +1
368                byte i1 = 1, i2 = 2, i3 = 3, i4 = 4;
369                short j1 = 5, j2, j3 = 6;
370                int k1, k2 = 7;
371                long l1 = 8;
372                float m1 = 9.0f;
373                double n1, n2 = 10.0;
374                boolean o1, o2 = false, o3;
375                char p1 = 'a', p2 = 'b', p3 = 'c', p4 = 'd';
376            }",
377            "foo.java",
378            JavaParser,
379            npa,
380            [
381                (class_npa_sum, 20, usize),
382                (interface_npa_sum, 0, usize),
383                (class_na_sum, 40, usize),
384                (interface_na_sum, 0, usize),
385                (total_npa, 20, usize),
386                (total_na, 40, usize)
387            ],
388            [
389                (class_cda, 0.5),
390                (interface_cda, f64::NAN),
391                (total_cda, 0.5)
392            ]
393        );
394    }
395
396    #[test]
397    fn java_array_attributes() {
398        check_metrics!(
399            "class X {
400                public byte[] a1, a2, a3, a4;                       // +4
401                public short b1[], b2[], b3[];                      // +3
402                public int[] c1 = { 1 }, c2;                        // +2
403                public long d1[] = { 1 };                           // +1
404                public float[] e1 = { 1.0f, 2.0f, 3.0f };           // +1
405                public double f1[] = { 1.0, 2.0, 3.0 }, f2[];       // +2
406                public boolean[] g1 = new boolean[5], g2, g3;       // +3
407                public char[] h1 = new char[5], h2[], h3[], h4[];   // +4
408                byte[] i1;
409                short j1[], j2[];
410                int[] k1, k2, k3 = { 1 };
411                long l1[], l2[] = { 1 }, l3[] = { 2 }, l4[];
412                float[] m1, m2, m3, m4 = { 1.0f, 2.0f, 3.0f };
413                double n1[], n2[] = { 1.0, 2.0, 3.0 }, n3[];
414                boolean[] o1, o2 = new boolean[5];
415                char[] p1 = new char[5];
416            }",
417            "foo.java",
418            JavaParser,
419            npa,
420            [
421                (class_npa_sum, 20, usize),
422                (interface_npa_sum, 0, usize),
423                (class_na_sum, 40, usize),
424                (interface_na_sum, 0, usize),
425                (total_npa, 20, usize),
426                (total_na, 40, usize)
427            ],
428            [
429                (class_cda, 0.5),
430                (interface_cda, f64::NAN),
431                (total_cda, 0.5)
432            ]
433        );
434    }
435
436    #[test]
437    fn java_object_attributes() {
438        check_metrics!(
439            "class X {
440                public Integer[] a1 = { 1 };                                    // +1
441                public Integer b1, b2;                                          // +2
442                public String[] c1 = { \"Hello\" }, c2, c3 = { \"World!\" };    // +3
443                public String d1[][] = { { \"Hello\" }, { \"World!\" } };       // +1
444                public Y[] e1, e2[];                                            // +2
445                public Y f1[], f2[][], f3[][][];                                // +3
446                Integer[] g1 = { new Integer(1) };
447                Integer h1 = new Integer(1), h2 = new Integer(2);
448                String[] i1, i2 = { \"Hello World!\" }, i3;
449                String j1 = \"Hello World!\";
450                Y[] k1[], k2;
451                Y l1[][], l2[], l3 = new Y();
452            }",
453            "foo.java",
454            JavaParser,
455            npa,
456            [
457                (class_npa_sum, 12, usize),
458                (interface_npa_sum, 0, usize),
459                (class_na_sum, 24, usize),
460                (interface_na_sum, 0, usize),
461                (total_npa, 12, usize),
462                (total_na, 24, usize)
463            ],
464            [
465                (class_cda, 0.5),
466                (interface_cda, f64::NAN),
467                (total_cda, 0.5)
468            ]
469        );
470    }
471
472    #[test]
473    fn java_generic_attributes() {
474        check_metrics!(
475            "class X<T, S extends T> {
476                public T a1;                            // +1
477                public Entry<T, S> b1, b2[];            // +2
478                public ArrayList<T> c1, c2, c3;         // +3
479                public HashMap<Long, Double> d1, d2;    // +2
480                public TreeSet<String> e1;              // +1
481                S f1;
482                Entry<S, T> g1[], g2;
483                ArrayList<S> h1, h2, h3;
484                HashMap<Long, Float> i1, i2;
485                TreeSet<Entry<S, T>> j1;
486            }",
487            "foo.java",
488            JavaParser,
489            npa,
490            [
491                (class_npa_sum, 9, usize),
492                (interface_npa_sum, 0, usize),
493                (class_na_sum, 18, usize),
494                (interface_na_sum, 0, usize),
495                (total_npa, 9, usize),
496                (total_na, 18, usize)
497            ],
498            [
499                (class_cda, 0.5),
500                (interface_cda, f64::NAN),
501                (total_cda, 0.5)
502            ]
503        );
504    }
505
506    #[test]
507    fn java_attribute_modifiers() {
508        check_metrics!(
509            "class X {
510                public transient volatile static int a;     // +1
511                transient public volatile static int b;     // +1
512                transient volatile public static int c;     // +1
513                transient volatile static public int d;     // +1
514                public transient static final int e = 1;    // +1
515                transient public static final int f = 2;    // +1
516                transient static public final int g = 3;    // +1
517                transient static final public int h = 4;    // +1
518                protected transient volatile static int i;
519                transient volatile static protected int j;
520                private transient volatile static int k;
521                transient volatile static private int l;
522                transient volatile static int m;
523                transient static final int n = 5;
524                static public final int o = 6;              // +1
525                final public int p = 7;                     // +1
526            }",
527            "foo.java",
528            JavaParser,
529            npa,
530            [
531                (class_npa_sum, 10, usize),
532                (interface_npa_sum, 0, usize),
533                (class_na_sum, 16, usize),
534                (interface_na_sum, 0, usize),
535                (total_npa, 10, usize),
536                (total_na, 16, usize)
537            ],
538            [
539                (class_cda, 0.625),
540                (interface_cda, f64::NAN),
541                (total_cda, 0.625)
542            ]
543        );
544    }
545
546    #[test]
547    fn java_classes() {
548        check_metrics!(
549            "class X {
550                public int a;       // +1
551                public boolean b;   // +1
552                private char c;
553            }
554            class Y {
555                private double d;
556                private long e;
557                public float f;      // +1
558            }",
559            "foo.java",
560            JavaParser,
561            npa,
562            [
563                (class_npa_sum, 3, usize),
564                (interface_npa_sum, 0, usize),
565                (class_na_sum, 6, usize),
566                (interface_na_sum, 0, usize),
567                (total_npa, 3, usize),
568                (total_na, 6, usize)
569            ],
570            [
571                (class_cda, 0.5),
572                (interface_cda, f64::NAN),
573                (total_cda, 0.5)
574            ]
575        );
576    }
577
578    #[test]
579    fn java_nested_inner_classes() {
580        check_metrics!(
581            "class X {
582                public int a;           // +1
583                class Y {
584                    public boolean b;   // +1
585                    class Z {
586                        public char c;  // +1
587                    }
588                }
589            }",
590            "foo.java",
591            JavaParser,
592            npa,
593            [
594                (class_npa_sum, 3, usize),
595                (interface_npa_sum, 0, usize),
596                (class_na_sum, 3, usize),
597                (interface_na_sum, 0, usize),
598                (total_npa, 3, usize),
599                (total_na, 3, usize)
600            ],
601            [
602                (class_cda, 1.0),
603                (interface_cda, f64::NAN),
604                (total_cda, 1.0)
605            ]
606        );
607    }
608
609    #[test]
610    fn java_local_inner_classes() {
611        check_metrics!(
612            "class X {
613                public int a;                   // +1
614                void x() {
615                    class Y {
616                        public boolean b;       // +1
617                        void y() {
618                            class Z {
619                                public char c;  // +1
620                                void z() {}
621                            }
622                        }
623                    }
624                }
625            }",
626            "foo.java",
627            JavaParser,
628            npa,
629            [
630                (class_npa_sum, 3, usize),
631                (interface_npa_sum, 0, usize),
632                (class_na_sum, 3, usize),
633                (interface_na_sum, 0, usize),
634                (total_npa, 3, usize),
635                (total_na, 3, usize)
636            ],
637            [
638                (class_cda, 1.0),
639                (interface_cda, f64::NAN),
640                (total_cda, 1.0)
641            ]
642        );
643    }
644
645    #[test]
646    fn java_anonymous_inner_classes() {
647        check_metrics!(
648            "abstract class X {
649                public int a;               // +1
650            }
651            abstract class Y {
652                boolean b;
653            }
654            class Z {
655                public char c;              // +1
656                public void z(){
657                    X x1 = new X() {
658                        public double d;    // +1
659                    };
660                    Y y1 = new Y() {
661                        long e;
662                    };
663                }
664            }",
665            "foo.java",
666            JavaParser,
667            npa,
668            [
669                (class_npa_sum, 3, usize),
670                (interface_npa_sum, 0, usize),
671                (class_na_sum, 5, usize),
672                (interface_na_sum, 0, usize),
673                (total_npa, 3, usize),
674                (total_na, 5, usize)
675            ],
676            [
677                (class_cda, 0.6),
678                (interface_cda, f64::NAN),
679                (total_cda, 0.6)
680            ]
681        );
682    }
683
684    #[test]
685    fn java_interface() {
686        check_metrics!(
687            "interface X {
688                public int a = 0;           // +1
689                static boolean b = false;   // +1
690                final char c = ' ';         // +1
691            }",
692            "foo.java",
693            JavaParser,
694            npa,
695            [
696                (class_npa_sum, 0, usize),
697                (interface_npa_sum, 3, usize),
698                (class_na_sum, 0, usize),
699                (interface_na_sum, 3, usize),
700                (total_npa, 3, usize),
701                (total_na, 3, usize)
702            ],
703            [
704                (class_cda, f64::NAN),
705                (interface_cda, 1.0),
706                (total_cda, 1.0)
707            ]
708        );
709    }
710}