-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexceptions.py
More file actions
365 lines (291 loc) · 10.9 KB
/
Copy pathexceptions.py
File metadata and controls
365 lines (291 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
#!/usr/bin/env python3
"""
Custom Exception Hierarchy for Counterscarp Engine.
Provides a clean, structured exception hierarchy for all Counterscarp Engine
errors. Each exception supports optional details dict for structured error
context and preserves original exception chaining.
Example:
>>> from exceptions import CounterscarpConfigError
>>> try:
... load_config("invalid.toml")
... except Exception as e:
... raise CounterscarpConfigError(
... "Failed to load config", details={"path": "invalid.toml"}
... ) from e
"""
from typing import Optional, Dict, Any
class CounterscarpError(Exception):
"""Base exception for all Counterscarp Engine errors.
All custom exceptions in the Counterscarp Engine should inherit from this
class to allow for unified error handling.
Attributes:
message: The error message.
details: Optional dictionary containing structured error context.
Example:
>>> raise CounterscarpError("Generic error occurred")
>>> raise CounterscarpError("Generic error", details={"code": 500})
"""
def __init__(
self,
message: str,
details: Optional[Dict[str, Any]] = None,
) -> None:
"""Initialize the exception.
Args:
message: Human-readable error message.
details: Optional dictionary with structured error context.
"""
super().__init__(message)
self.message = message
self.details = details or {}
def __str__(self) -> str:
"""Return string representation of the error.
Returns:
Formatted error message including details if present.
"""
if self.details:
details_str = ", ".join(
f"{k}={repr(v)}" for k, v in self.details.items()
)
return f"{self.message} ({details_str})"
return self.message
def to_dict(self) -> Dict[str, Any]:
"""Convert exception to dictionary for serialization.
Returns:
Dictionary containing error information.
"""
result: Dict[str, Any] = {
"type": self.__class__.__name__,
"message": self.message,
}
if self.details:
result["details"] = self.details
if self.__cause__:
result["cause"] = str(self.__cause__)
return result
class CounterscarpConfigError(CounterscarpError):
"""Raised for configuration loading/validation errors.
This exception is raised when there's a problem with loading or
validating the Counterscarp configuration file (counterscarp.toml).
Example:
>>> raise CounterscarpConfigError(
... "Invalid TOML syntax",
... details={"path": "/path/to/config.toml", "line": 42}
... )
"""
def __init__(
self,
message: str,
details: Optional[Dict[str, Any]] = None,
) -> None:
"""Initialize the config error.
Args:
message: Human-readable error message.
details: Optional dictionary with context like 'path', 'line'.
"""
super().__init__(message, details)
class CounterscarpAnalysisError(CounterscarpError):
"""Raised when a security analyzer fails.
This exception is raised when static analysis, fuzzing, or other
security analysis tools fail to execute properly.
Example:
>>> raise CounterscarpAnalysisError(
... "Slither analysis failed",
... details={"tool": "slither", "contract": "Token.sol"}
... )
"""
def __init__(
self,
message: str,
details: Optional[Dict[str, Any]] = None,
) -> None:
"""Initialize the analysis error.
Args:
message: Human-readable error message.
details: Optional dictionary with context like 'tool', 'contract'.
"""
super().__init__(message, details)
class CounterscarpAPIError(CounterscarpError):
"""Raised for external API call failures.
This exception is raised when external API calls fail, such as
threat intelligence lookups, OSV database queries, or other
external service interactions.
Example:
>>> raise CounterscarpAPIError(
... "OSV API request failed",
... details={
... "api": "osv", "status_code": 503, "endpoint": "/v1/query"
... }
... )
"""
def __init__(
self,
message: str,
details: Optional[Dict[str, Any]] = None,
) -> None:
"""Initialize the API error.
Args:
message: Human-readable error message.
details: Optional dictionary with context like 'api',
'status_code'.
"""
super().__init__(message, details)
class CounterscarpReportError(CounterscarpError):
"""Raised for report generation failures.
This exception is raised when the report generator fails to create
output files or format findings properly.
Example:
>>> raise CounterscarpReportError(
... "Failed to write HTML report",
... details={"format": "html", "output_path": "/reports/out.html"}
... )
"""
def __init__(
self,
message: str,
details: Optional[Dict[str, Any]] = None,
) -> None:
"""Initialize the report error.
Args:
message: Human-readable error message.
details: Optional dictionary with context like 'format',
'output_path'.
"""
super().__init__(message, details)
class CounterscarpToolNotFoundError(CounterscarpError):
"""Raised when a required external tool is not found.
This exception is raised when external tools like Slither, Aderyn,
Medusa, or Mythril are not installed or not available in PATH.
Example:
>>> raise CounterscarpToolNotFoundError(
... "Slither not found in PATH",
... details={
... "tool": "slither",
... "install_cmd": "pip install slither-analyzer"
... }
... )
"""
def __init__(
self,
message: str,
details: Optional[Dict[str, Any]] = None,
) -> None:
"""Initialize the tool not found error.
Args:
message: Human-readable error message.
details: Optional dictionary with context like 'tool',
'install_cmd'.
"""
super().__init__(message, details)
class CounterscarpValidationError(CounterscarpError):
"""Raised for input validation failures.
This exception is raised when user input or scanned code fails
validation checks.
Example:
>>> raise CounterscarpValidationError(
... "Invalid contract address format",
... details={"field": "address", "value": "0x123"}
... )
"""
def __init__(
self,
message: str,
details: Optional[Dict[str, Any]] = None,
) -> None:
"""Initialize the validation error.
Args:
message: Human-readable error message.
details: Optional dictionary with context like 'field', 'value'.
"""
super().__init__(message, details)
class CounterscarpTimeoutError(CounterscarpError):
"""Raised when an operation times out.
This exception is raised when analysis operations exceed their
configured timeout limits.
Example:
>>> raise CounterscarpTimeoutError(
... "Mythril analysis timed out",
... details={
... "operation": "symbolic_analysis", "timeout_seconds": 300
... }
... )
"""
def __init__(
self,
message: str,
details: Optional[Dict[str, Any]] = None,
) -> None:
"""Initialize the timeout error.
Args:
message: Human-readable error message.
details: Optional dictionary with context like 'operation',
'timeout_seconds'.
"""
super().__init__(message, details)
# Module-level convenience functions
def format_exception_chain(exc: Exception) -> str:
"""Format an exception and its cause chain for display.
Args:
exc: The exception to format.
Returns:
Formatted string showing the exception chain.
Example:
>>> try:
... risky_operation()
... except Exception as e:
... print(format_exception_chain(e))
"""
lines = [str(exc)]
current = exc.__cause__
while current:
lines.append(f" Caused by: {current}")
current = current.__cause__
return "\n".join(lines)
def is_counterscarp_error(exc: Exception) -> bool:
"""Check if an exception is a Counterscarp Engine error.
Args:
exc: The exception to check.
Returns:
True if the exception is a CounterscarpError or subclass.
"""
return isinstance(exc, CounterscarpError)
if __name__ == "__main__":
# Demo/test code
print("Testing Counterscarp Exception Hierarchy\n")
# Test basic exception
try:
raise CounterscarpError("Generic error", details={"code": 500})
except CounterscarpError as e:
print(f"1. Basic error: {e}")
print(f" Dict: {e.to_dict()}\n")
# Test exception chaining
try:
try:
raise ValueError("Original error")
except ValueError as original:
raise CounterscarpConfigError(
"Config load failed",
details={"path": "config.toml"}
) from original
except CounterscarpError as e:
print(f"2. Chained error: {e}")
print(f" Cause: {e.__cause__}")
print(f" Formatted chain:\n {format_exception_chain(e)}\n")
# Test all exception types
exceptions_to_test = [
CounterscarpConfigError("Config error", {"file": "test.toml"}),
CounterscarpAnalysisError("Analysis failed", {"tool": "slither"}),
CounterscarpAPIError("API error", {"status": 500}),
CounterscarpReportError("Report failed", {"format": "html"}),
CounterscarpToolNotFoundError("Tool missing", {"tool": "mythril"}),
CounterscarpValidationError("Invalid input", {"field": "address"}),
CounterscarpTimeoutError("Timeout", {"seconds": 30}),
]
print("3. All exception types:")
for exc in exceptions_to_test:
print(f" - {exc.__class__.__name__}: {exc}")
print("\n4. Exception hierarchy check:")
print(f" CounterscarpConfigError is CounterscarpError: "
f"{is_counterscarp_error(CounterscarpConfigError('test'))}")
print(f" ValueError is CounterscarpError: "
f"{is_counterscarp_error(ValueError('test'))}")