Skip to main content

rust_code_analysis_code_split/metrics/
npa.rs

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