1
1
from enum import Enum
2
- from typing import Dict , List , Optional , Set
2
+ from typing import Dict , List , Optional , Set , ForwardRef
3
3
from pydantic import BaseModel , Field
4
4
5
5
6
6
class RustVisibility (Enum ):
7
7
"""Represents Rust visibility modifiers."""
8
+
8
9
PUBLIC = "pub"
9
- PRIVATE = ""
10
+ PRIVATE = ""
10
11
CRATE = "pub(crate)"
11
12
SUPER = "pub(super)"
12
13
IN_PATH = "pub(in path)"
13
14
14
15
15
16
class SafetyClassification (Enum ):
16
17
"""Classifies the safety level of Rust code."""
18
+
17
19
SAFE = "safe"
18
20
UNSAFE = "unsafe"
19
- UNSAFE_CONTAINER = "unsafe_container"
21
+ UNSAFE_CONTAINER = "unsafe_container"
20
22
FFI = "ffi"
21
23
22
24
23
25
class UnsafeReason (Enum ):
24
26
"""Reasons why code might be unsafe."""
27
+
25
28
RAW_POINTER_DEREF = "raw_pointer_deref"
26
29
MUTABLE_STATIC = "mutable_static"
27
30
FFI_CALL = "ffi_call"
28
31
UNION_FIELD_ACCESS = "union_field_access"
29
32
INLINE_ASSEMBLY = "inline_assembly"
30
33
UNSAFE_TRAIT_IMPL = "unsafe_trait_impl"
31
- CUSTOM = "custom"
34
+ CUSTOM = "custom"
32
35
33
36
34
37
class UnsafeBlock (BaseModel ):
35
38
"""Represents an unsafe block within Rust code."""
39
+
36
40
start_line : int
37
41
end_line : int
38
42
reasons : List [UnsafeReason ] = Field (default_factory = list )
@@ -42,6 +46,7 @@ class UnsafeBlock(BaseModel):
42
46
43
47
class RustType (BaseModel ):
44
48
"""Represents a Rust type."""
49
+
45
50
name : str
46
51
is_reference : bool = False
47
52
is_mutable : bool = False
@@ -55,13 +60,15 @@ class RustType(BaseModel):
55
60
56
61
class RustAttribute (BaseModel ):
57
62
"""Represents a Rust attribute."""
63
+
58
64
name : str
59
65
arguments : List [str ] = Field (default_factory = list )
60
66
is_inner : bool = False # #![foo] vs #[foo]
61
67
62
68
63
69
class SafetyAnalysis (BaseModel ):
64
70
"""Analyzes and tracks safety-related information."""
71
+
65
72
classification : SafetyClassification
66
73
unsafe_blocks : List [UnsafeBlock ] = Field (default_factory = list )
67
74
unsafe_fn_calls : List [str ] = Field (default_factory = list )
@@ -74,6 +81,7 @@ class SafetyAnalysis(BaseModel):
74
81
75
82
class RustCallable (BaseModel ):
76
83
"""Represents a Rust function or method."""
84
+
77
85
name : str
78
86
visibility : RustVisibility = RustVisibility .PRIVATE
79
87
doc_comment : Optional [str ] = None
@@ -97,23 +105,24 @@ class RustCallable(BaseModel):
97
105
variable_declarations : List ["RustVariableDeclaration" ] = Field (default_factory = list )
98
106
cyclomatic_complexity : Optional [int ] = None
99
107
safety_analysis : SafetyAnalysis
100
-
108
+
101
109
def is_fully_safe (self ) -> bool :
102
110
"""Check if the function is completely safe (no unsafe blocks or calls)."""
103
111
return (
104
112
self .safety_analysis .classification == SafetyClassification .SAFE
105
113
and not self .safety_analysis .unsafe_blocks
106
114
and not self .safety_analysis .unsafe_fn_calls
107
115
)
108
-
116
+
109
117
def contains_unsafe (self ) -> bool :
110
118
"""Check if the function contains any unsafe code."""
111
119
return (
112
- self .safety_analysis .classification in [SafetyClassification .UNSAFE , SafetyClassification .UNSAFE_CONTAINER ]
120
+ self .safety_analysis .classification
121
+ in [SafetyClassification .UNSAFE , SafetyClassification .UNSAFE_CONTAINER ]
113
122
or bool (self .safety_analysis .unsafe_blocks )
114
123
or bool (self .safety_analysis .unsafe_fn_calls )
115
124
)
116
-
125
+
117
126
def get_unsafe_reasons (self ) -> Set [UnsafeReason ]:
118
127
"""Get all reasons for unsafe usage in this function."""
119
128
reasons = set ()
@@ -124,6 +133,7 @@ def get_unsafe_reasons(self) -> Set[UnsafeReason]:
124
133
125
134
class RustModule (BaseModel ):
126
135
"""Represents a Rust module."""
136
+
127
137
name : str
128
138
doc_comment : Optional [str ] = None
129
139
attributes : List [RustAttribute ] = Field (default_factory = list )
@@ -132,36 +142,47 @@ class RustModule(BaseModel):
132
142
functions : Dict [str , RustCallable ] = Field (default_factory = dict )
133
143
safe_functions : Dict [str , RustCallable ] = Field (default_factory = dict )
134
144
unsafe_functions : Dict [str , RustCallable ] = Field (default_factory = dict )
135
- submodules : Dict [str , ' RustModule' ] = Field (default_factory = dict )
145
+ submodules : Dict [str , " RustModule" ] = Field (default_factory = dict )
136
146
constants : List ["RustVariableDeclaration" ] = Field (default_factory = list )
137
147
macros : List [str ] = Field (default_factory = list )
138
148
use_declarations : List [str ] = Field (default_factory = list )
139
149
extern_crates : List [str ] = Field (default_factory = list )
140
150
is_unsafe : bool = False
141
151
file_path : Optional [str ] = None
142
152
is_mod_rs : bool = False
153
+ is_root_module : bool = False
143
154
144
155
def categorize_functions (self ):
145
156
"""Categorize functions based on their safety analysis."""
146
157
self .safe_functions .clear ()
147
158
self .unsafe_functions .clear ()
148
-
159
+
149
160
for name , func in self .functions .items ():
150
161
if func .is_fully_safe ():
151
162
self .safe_functions [name ] = func
152
163
else :
153
164
self .unsafe_functions [name ] = func
154
165
155
166
167
+ class RustDependency (BaseModel ):
168
+ """Represents a Rust Dependency, Maybe internal or external"""
169
+
170
+ name : str
171
+ is_external : bool = True
172
+ crate : Optional [ForwardRef ("RustCrate" )]
173
+
174
+
156
175
class RustCrate (BaseModel ):
157
176
"""Represents a complete Rust crate."""
177
+
158
178
name : str
159
179
version : str
160
- root_module : RustModule
161
- dependencies : List ["RustDependencyEdge" ] = Field ( default_factory = list )
180
+ is_lib : bool = False
181
+ modules : List [RustModule ]
162
182
edition : str = "2021"
163
183
features : List [str ] = Field (default_factory = list )
164
-
184
+ dependencies : List [RustDependency ] = Field (default_factory = list )
185
+
165
186
def analyze_safety (self ) -> Dict [str , int ]:
166
187
"""Analyze safety statistics across the crate."""
167
188
stats = {
@@ -171,7 +192,7 @@ def analyze_safety(self) -> Dict[str, int]:
171
192
"unsafe_blocks" : 0 ,
172
193
"ffi_functions" : 0 ,
173
194
}
174
-
195
+
175
196
def analyze_module (module : RustModule ):
176
197
for func in module .functions .values ():
177
198
stats ["total_functions" ] += 1
@@ -182,26 +203,26 @@ def analyze_module(module: RustModule):
182
203
if func .safety_analysis .classification == SafetyClassification .FFI :
183
204
stats ["ffi_functions" ] += 1
184
205
stats ["unsafe_blocks" ] += len (func .safety_analysis .unsafe_blocks )
185
-
206
+
186
207
for submodule in module .submodules .values ():
187
208
analyze_module (submodule )
188
-
209
+
189
210
analyze_module (self .root_module )
190
211
return stats
191
-
212
+
192
213
def get_unsafe_functions (self ) -> List [tuple [str , RustCallable ]]:
193
214
"""Get all unsafe functions in the crate with their module paths."""
194
215
unsafe_fns = []
195
-
216
+
196
217
def collect_unsafe (module : RustModule , path : str ):
197
218
for name , func in module .functions .items ():
198
219
if not func .is_fully_safe ():
199
220
full_path = f"{ path } ::{ name } " if path else name
200
221
unsafe_fns .append ((full_path , func ))
201
-
222
+
202
223
for submod_name , submod in module .submodules .items ():
203
224
new_path = f"{ path } ::{ submod_name } " if path else submod_name
204
225
collect_unsafe (submod , new_path )
205
-
226
+
206
227
collect_unsafe (self .root_module , "" )
207
- return unsafe_fns
228
+ return unsafe_fns
0 commit comments