@@ -20,18 +20,31 @@ class CWE:
2020 id (int): The CWE identifier.
2121 name (str): The name of the weakness.
2222 description (str): A description of the weakness.
23+ parent (CWE | None): The parent CWE weakness, if any.
24+ children (set[CWE]): A set of child CWE weaknesses.
2325
2426 """
2527
26- def __init__ (self , id : int , name : str , description : str ) -> None :
28+ def __init__ (
29+ self ,
30+ id : int ,
31+ name : str ,
32+ description : str ,
33+ parent : Self | None = None ,
34+ children : set [Self ] | None = None ,
35+ ) -> None :
2736 """Initialize a CWE instance.
2837
2938 Args:
3039 id: The CWE identifier.
3140 name: The name of the weakness.
3241 description: A description of the weakness.
42+ parent: The parent CWE weakness, if any.
43+ children: A set of child CWE weaknesses, if any.
3344
3445 """
46+ if children is None :
47+ children = set ()
3548 self .id = id
3649 if r := re .search (r"\('(.*)'\)" , name ):
3750 self .name = r .group (1 )
@@ -40,6 +53,8 @@ def __init__(self, id: int, name: str, description: str) -> None:
4053 self .name = self .full_name = name
4154
4255 self .description = description
56+ self .parent = parent
57+ self .children = children or set ()
4358
4459 def __eq__ (self , other : Self | int ) -> bool :
4560 """Compare this CWE with another object for equality.
@@ -72,6 +87,31 @@ def __repr__(self) -> str:
7287 """
7388 return f"{ self .__class__ .__name__ } (id={ self .id } )"
7489
90+ def extend (self , distance : int = 1 ) -> set [Self ]:
91+ """Retrieve the set of related CWEs within a specified distance in the hierarchy.
92+
93+ Recursively finds parent and child CWEs up to the given distance level.
94+ Includes the current CWE in the returned set.
95+
96+ Args:
97+ distance: The number of levels to traverse up (parents) and down (children).
98+ Defaults to 1.
99+
100+ Returns:
101+ A set of CWE objects including the self and related weaknesses.
102+
103+ """
104+ cwes : set [Self ] = set ([self ])
105+ for _ in range (distance ):
106+ new_cwes = cwes .copy ()
107+ for cwe in cwes :
108+ if cwe .parent :
109+ new_cwes .add (cwe .parent )
110+ for child in cwe .children :
111+ new_cwes .add (child )
112+ cwes = new_cwes .copy ()
113+ return cwes
114+
75115
76116class CWEsCollection :
77117 """Manage the collection of all CWEs.
@@ -136,24 +176,50 @@ def download(self) -> None:
136176 )
137177 progress .update (task , advance = 25 )
138178
139- def load (self ) -> list [CWE ]:
140- """Load CWE data from the CSV file.
179+ def load (self ) -> dict [int , CWE ]:
180+ """Load and parse CWE data from cached CSV files.
181+
182+ Reads the CSV files defined in `cwes_data`, instantiates `CWE` objects,
183+ and establishes parent-child relationships based on the "Related Weaknesses" field.
141184
142185 Returns:
143- A list of CWE objects.
186+ A dictionary mapping CWE IDs (int) to `CWE` objects.
144187
145188 """
146- cwes = []
189+ cwes = {}
190+ cwes_parent = {}
191+ cwes_children = {}
147192 for filename in self .cwes_data .values ():
148193 reader = csv .DictReader ((self .directory / filename ).open (encoding = "utf-8" ))
149194 for cwe_dict in reader :
150- cwes . append (
151- CWE (
152- id = int ( cwe_dict [ " CWE-ID" ]),
153- name = cwe_dict [ "Name" ] ,
154- description = cwe_dict ["Description " ],
155- )
195+ cwe_id = int ( cwe_dict [ "CWE-ID" ])
196+
197+ cwes [ cwe_id ] = CWE (
198+ id = cwe_id ,
199+ name = cwe_dict ["Name " ],
200+ description = cwe_dict [ "Description" ],
156201 )
202+
203+ for related in cwe_dict ["Related Weaknesses" ].split ("::" ):
204+ if m := re .search (r"NATURE:ChildOf:CWE ID:(\d+):" , related ):
205+ parent_id = int (m .group (1 ))
206+
207+ cwes_parent [cwe_id ] = parent_id
208+
209+ if cwes_children .get (parent_id ):
210+ cwes_children [parent_id ].add (cwe_id )
211+ else :
212+ cwes_children [parent_id ] = {cwe_id }
213+
214+ break
215+
216+ for cwe_id , cwe in cwes .items ():
217+ if p_id := cwes_parent .get (cwe_id ):
218+ cwe .parent = cwes .get (p_id , None )
219+ for c_id in cwes_children .get (cwe_id , []):
220+ if child_cwe := cwes .get (c_id ):
221+ cwe .children .add (child_cwe )
222+
157223 return cwes
158224
159225 def from_string (self , cwe_string : str ) -> CWE :
@@ -181,10 +247,7 @@ def from_id(self, cwe_id: int) -> CWE:
181247 The CWE object if found, otherwise a default CWE object with ID -1.
182248
183249 """
184- try :
185- return self .cwes [self .cwes .index (cwe_id )]
186- except ValueError :
187- return self .NOCWE
250+ return self .cwes .get (cwe_id , self .NOCWE )
188251
189252
190253CWEs = CWEsCollection ()
0 commit comments