serenecode 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- serenecode/__init__.py +281 -0
- serenecode/adapters/__init__.py +6 -0
- serenecode/adapters/coverage_adapter.py +1173 -0
- serenecode/adapters/crosshair_adapter.py +1069 -0
- serenecode/adapters/hypothesis_adapter.py +1824 -0
- serenecode/adapters/local_fs.py +169 -0
- serenecode/adapters/module_loader.py +492 -0
- serenecode/adapters/mypy_adapter.py +161 -0
- serenecode/checker/__init__.py +6 -0
- serenecode/checker/compositional.py +2216 -0
- serenecode/checker/coverage.py +186 -0
- serenecode/checker/properties.py +154 -0
- serenecode/checker/structural.py +1504 -0
- serenecode/checker/symbolic.py +178 -0
- serenecode/checker/types.py +148 -0
- serenecode/cli.py +478 -0
- serenecode/config.py +711 -0
- serenecode/contracts/__init__.py +6 -0
- serenecode/contracts/predicates.py +176 -0
- serenecode/core/__init__.py +6 -0
- serenecode/core/exceptions.py +38 -0
- serenecode/core/pipeline.py +807 -0
- serenecode/init.py +307 -0
- serenecode/models.py +308 -0
- serenecode/ports/__init__.py +6 -0
- serenecode/ports/coverage_analyzer.py +124 -0
- serenecode/ports/file_system.py +95 -0
- serenecode/ports/property_tester.py +69 -0
- serenecode/ports/symbolic_checker.py +70 -0
- serenecode/ports/type_checker.py +66 -0
- serenecode/reporter.py +346 -0
- serenecode/source_discovery.py +319 -0
- serenecode/templates/__init__.py +5 -0
- serenecode/templates/content.py +337 -0
- serenecode-0.1.0.dist-info/METADATA +298 -0
- serenecode-0.1.0.dist-info/RECORD +39 -0
- serenecode-0.1.0.dist-info/WHEEL +4 -0
- serenecode-0.1.0.dist-info/entry_points.txt +2 -0
- serenecode-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Symbolic verification checker for Serenecode (Level 4).
|
|
2
|
+
|
|
3
|
+
This module implements Level 4 verification: it transforms results from
|
|
4
|
+
symbolic execution backends into structured CheckResult objects.
|
|
5
|
+
The actual verification is delegated to adapters.
|
|
6
|
+
|
|
7
|
+
This is a core module — no I/O operations are permitted. Verification
|
|
8
|
+
results are received as structured data, not generated here.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import icontract
|
|
14
|
+
|
|
15
|
+
from serenecode.models import (
|
|
16
|
+
CheckResult,
|
|
17
|
+
CheckStatus,
|
|
18
|
+
Detail,
|
|
19
|
+
FunctionResult,
|
|
20
|
+
VerificationLevel,
|
|
21
|
+
make_check_result,
|
|
22
|
+
)
|
|
23
|
+
from serenecode.ports.symbolic_checker import SymbolicFinding
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@icontract.require(
|
|
27
|
+
lambda findings: isinstance(findings, list),
|
|
28
|
+
"findings must be a list",
|
|
29
|
+
)
|
|
30
|
+
@icontract.ensure(
|
|
31
|
+
lambda result: isinstance(result, CheckResult),
|
|
32
|
+
"result must be a CheckResult",
|
|
33
|
+
)
|
|
34
|
+
def transform_symbolic_results(
|
|
35
|
+
findings: list[SymbolicFinding],
|
|
36
|
+
file_path: str,
|
|
37
|
+
duration_seconds: float,
|
|
38
|
+
) -> CheckResult:
|
|
39
|
+
"""Transform symbolic verification findings into a CheckResult.
|
|
40
|
+
|
|
41
|
+
Maps each SymbolicFinding to a FunctionResult with appropriate
|
|
42
|
+
status, counterexamples, and suggestions.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
findings: List of symbolic findings from a verification adapter.
|
|
46
|
+
file_path: Source file path for reporting.
|
|
47
|
+
duration_seconds: How long the verification took.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
A CheckResult containing all symbolic verification results.
|
|
51
|
+
"""
|
|
52
|
+
func_results: list[FunctionResult] = []
|
|
53
|
+
|
|
54
|
+
# Loop invariant: func_results contains transformed results for findings[0..i]
|
|
55
|
+
for finding in findings:
|
|
56
|
+
details: list[Detail] = []
|
|
57
|
+
status: CheckStatus
|
|
58
|
+
level_achieved: int
|
|
59
|
+
|
|
60
|
+
if finding.outcome == "verified":
|
|
61
|
+
status = CheckStatus.PASSED
|
|
62
|
+
level_achieved = 5
|
|
63
|
+
details.append(Detail(
|
|
64
|
+
level=VerificationLevel.SYMBOLIC,
|
|
65
|
+
tool="crosshair",
|
|
66
|
+
finding_type="verified",
|
|
67
|
+
message=f"No counterexample found within analysis bounds: '{finding.function_name}'",
|
|
68
|
+
))
|
|
69
|
+
elif finding.outcome == "counterexample":
|
|
70
|
+
status = CheckStatus.FAILED
|
|
71
|
+
level_achieved = 4
|
|
72
|
+
details.append(Detail(
|
|
73
|
+
level=VerificationLevel.SYMBOLIC,
|
|
74
|
+
tool="crosshair",
|
|
75
|
+
finding_type="counterexample",
|
|
76
|
+
message=finding.message,
|
|
77
|
+
counterexample=finding.counterexample,
|
|
78
|
+
suggestion=_suggest_fix_symbolic(finding),
|
|
79
|
+
))
|
|
80
|
+
elif finding.outcome == "timeout":
|
|
81
|
+
status = CheckStatus.SKIPPED
|
|
82
|
+
level_achieved = 4
|
|
83
|
+
details.append(Detail(
|
|
84
|
+
level=VerificationLevel.SYMBOLIC,
|
|
85
|
+
tool="crosshair",
|
|
86
|
+
finding_type="timeout",
|
|
87
|
+
message=f"Symbolic verification timed out for '{finding.function_name}'",
|
|
88
|
+
suggestion=(
|
|
89
|
+
"The solver ran out of time. Options: "
|
|
90
|
+
"(1) increase --per-condition-timeout or --module-timeout, "
|
|
91
|
+
"(2) simplify the function logic or contracts, "
|
|
92
|
+
"(3) split the function into smaller pieces, "
|
|
93
|
+
"(4) add tighter preconditions to reduce the search space"
|
|
94
|
+
),
|
|
95
|
+
))
|
|
96
|
+
elif finding.outcome == "unsupported":
|
|
97
|
+
status = CheckStatus.EXEMPT
|
|
98
|
+
level_achieved = 4
|
|
99
|
+
details.append(Detail(
|
|
100
|
+
level=VerificationLevel.SYMBOLIC,
|
|
101
|
+
tool="crosshair",
|
|
102
|
+
finding_type="unsupported",
|
|
103
|
+
message=f"Symbolic verification unsupported for '{finding.function_name}'",
|
|
104
|
+
suggestion=(
|
|
105
|
+
"This function cannot be symbolically verified — "
|
|
106
|
+
"it has non-primitive parameter types that the solver cannot generate. "
|
|
107
|
+
"Ensure it is covered by property-based tests (L3) or explicit unit tests"
|
|
108
|
+
),
|
|
109
|
+
))
|
|
110
|
+
else:
|
|
111
|
+
status = CheckStatus.FAILED
|
|
112
|
+
level_achieved = 4
|
|
113
|
+
details.append(Detail(
|
|
114
|
+
level=VerificationLevel.SYMBOLIC,
|
|
115
|
+
tool="crosshair",
|
|
116
|
+
finding_type="error",
|
|
117
|
+
message=finding.message,
|
|
118
|
+
suggestion=(
|
|
119
|
+
"Symbolic verification encountered an internal error. "
|
|
120
|
+
"Check that the module imports cleanly and that all dependencies are installed"
|
|
121
|
+
),
|
|
122
|
+
))
|
|
123
|
+
|
|
124
|
+
func_results.append(FunctionResult(
|
|
125
|
+
function=finding.function_name,
|
|
126
|
+
file=file_path,
|
|
127
|
+
line=1,
|
|
128
|
+
level_requested=5,
|
|
129
|
+
level_achieved=level_achieved,
|
|
130
|
+
status=status,
|
|
131
|
+
details=tuple(details),
|
|
132
|
+
))
|
|
133
|
+
|
|
134
|
+
return make_check_result(
|
|
135
|
+
tuple(func_results),
|
|
136
|
+
level_requested=5,
|
|
137
|
+
duration_seconds=duration_seconds,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@icontract.require(
|
|
142
|
+
lambda finding: isinstance(finding, SymbolicFinding),
|
|
143
|
+
"finding must be a SymbolicFinding",
|
|
144
|
+
)
|
|
145
|
+
@icontract.ensure(
|
|
146
|
+
lambda result: result is None or isinstance(result, str),
|
|
147
|
+
"result must be None or a string",
|
|
148
|
+
)
|
|
149
|
+
def _suggest_fix_symbolic(finding: SymbolicFinding) -> str | None:
|
|
150
|
+
"""Generate a fix suggestion from a symbolic finding.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
finding: The symbolic finding to generate a suggestion for.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A suggestion string, or None.
|
|
157
|
+
"""
|
|
158
|
+
if finding.counterexample is not None and isinstance(finding.counterexample, dict) and finding.counterexample:
|
|
159
|
+
inputs = ", ".join(f"{k}={v}" for k, v in finding.counterexample.items())
|
|
160
|
+
fix_steps = (
|
|
161
|
+
f"Counterexample: {inputs}. "
|
|
162
|
+
"To fix: (1) if the inputs are invalid, add a @icontract.require "
|
|
163
|
+
"precondition to exclude them; (2) if the inputs are valid, fix the "
|
|
164
|
+
"implementation so the postcondition holds"
|
|
165
|
+
)
|
|
166
|
+
if finding.condition:
|
|
167
|
+
fix_steps += f"; violated condition: {finding.condition}"
|
|
168
|
+
return fix_steps
|
|
169
|
+
if finding.condition:
|
|
170
|
+
return (
|
|
171
|
+
f"Condition '{finding.condition}' violated. "
|
|
172
|
+
"Either fix the implementation to satisfy the postcondition, "
|
|
173
|
+
"or add a precondition to narrow the valid input domain"
|
|
174
|
+
)
|
|
175
|
+
return (
|
|
176
|
+
"Symbolic verification found a violation. "
|
|
177
|
+
"Read the function's postconditions and check which one can fail"
|
|
178
|
+
)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Type checking checker for Serenecode (Level 2).
|
|
2
|
+
|
|
3
|
+
This module implements Level 2 verification: it transforms results from
|
|
4
|
+
static type analysis backends (like mypy) into structured CheckResult
|
|
5
|
+
objects. The actual type checking is delegated to adapters.
|
|
6
|
+
|
|
7
|
+
This is a core module — no I/O operations are permitted. Type check
|
|
8
|
+
results are received as structured data, not generated here.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import icontract
|
|
14
|
+
|
|
15
|
+
from serenecode.contracts.predicates import is_non_empty_string
|
|
16
|
+
from serenecode.models import (
|
|
17
|
+
CheckResult,
|
|
18
|
+
CheckStatus,
|
|
19
|
+
Detail,
|
|
20
|
+
FunctionResult,
|
|
21
|
+
VerificationLevel,
|
|
22
|
+
make_check_result,
|
|
23
|
+
)
|
|
24
|
+
from serenecode.ports.type_checker import TypeIssue
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@icontract.require(
|
|
28
|
+
lambda issues: isinstance(issues, list),
|
|
29
|
+
"issues must be a list",
|
|
30
|
+
)
|
|
31
|
+
@icontract.ensure(
|
|
32
|
+
lambda result: isinstance(result, CheckResult),
|
|
33
|
+
"result must be a CheckResult",
|
|
34
|
+
)
|
|
35
|
+
def transform_type_results(
|
|
36
|
+
issues: list[TypeIssue],
|
|
37
|
+
duration_seconds: float,
|
|
38
|
+
) -> CheckResult:
|
|
39
|
+
"""Transform mypy type issues into a CheckResult.
|
|
40
|
+
|
|
41
|
+
Groups issues by file and line, creating FunctionResult entries
|
|
42
|
+
for each location with type errors.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
issues: List of type issues from a type checker adapter.
|
|
46
|
+
duration_seconds: How long the type checking took.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
A CheckResult containing all type checking findings.
|
|
50
|
+
"""
|
|
51
|
+
# Group issues by (file, line)
|
|
52
|
+
grouped: dict[tuple[str, int], list[TypeIssue]] = {}
|
|
53
|
+
# Loop invariant: grouped contains all issues from issues[0..i]
|
|
54
|
+
for issue in issues:
|
|
55
|
+
key = (issue.file, issue.line)
|
|
56
|
+
grouped.setdefault(key, []).append(issue)
|
|
57
|
+
|
|
58
|
+
func_results: list[FunctionResult] = []
|
|
59
|
+
|
|
60
|
+
# Loop invariant: func_results contains results for all groups processed
|
|
61
|
+
for (file_path, line), file_issues in sorted(grouped.items()):
|
|
62
|
+
errors = [i for i in file_issues if i.severity == "error"]
|
|
63
|
+
if not errors:
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
details: list[Detail] = []
|
|
67
|
+
# Loop invariant: details contains Detail for errors[0..j]
|
|
68
|
+
for error in errors:
|
|
69
|
+
suggestion = (
|
|
70
|
+
_suggest_from_mypy_code(error.code, error.message)
|
|
71
|
+
if error.code and is_non_empty_string(error.message)
|
|
72
|
+
else None
|
|
73
|
+
)
|
|
74
|
+
details.append(Detail(
|
|
75
|
+
level=VerificationLevel.TYPES,
|
|
76
|
+
tool="mypy",
|
|
77
|
+
finding_type="violation",
|
|
78
|
+
message=f"{error.message}" + (f" [{error.code}]" if error.code else ""),
|
|
79
|
+
suggestion=suggestion,
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
func_results.append(FunctionResult(
|
|
83
|
+
function=f"<line {line}>",
|
|
84
|
+
file=file_path,
|
|
85
|
+
line=line,
|
|
86
|
+
level_requested=2,
|
|
87
|
+
level_achieved=1,
|
|
88
|
+
status=CheckStatus.FAILED,
|
|
89
|
+
details=tuple(details),
|
|
90
|
+
))
|
|
91
|
+
|
|
92
|
+
return make_check_result(
|
|
93
|
+
tuple(func_results),
|
|
94
|
+
level_requested=2,
|
|
95
|
+
duration_seconds=duration_seconds,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@icontract.require(
|
|
100
|
+
lambda code: code is None or is_non_empty_string(code),
|
|
101
|
+
"code must be a non-empty string when provided",
|
|
102
|
+
)
|
|
103
|
+
@icontract.require(
|
|
104
|
+
lambda message: is_non_empty_string(message),
|
|
105
|
+
"message must be a non-empty string",
|
|
106
|
+
)
|
|
107
|
+
@icontract.ensure(
|
|
108
|
+
lambda result: result is None or isinstance(result, str),
|
|
109
|
+
"result must be a string or None",
|
|
110
|
+
)
|
|
111
|
+
def _suggest_from_mypy_code(code: str | None, message: str) -> str | None:
|
|
112
|
+
"""Generate a fix suggestion from a mypy error code.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
code: The mypy error code (e.g. "arg-type").
|
|
116
|
+
message: The mypy error message.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
A suggestion string, or None.
|
|
120
|
+
"""
|
|
121
|
+
suggestions: dict[str, str] = {
|
|
122
|
+
"arg-type": "Change the argument to match the expected parameter type, or update the parameter annotation",
|
|
123
|
+
"return-value": "Change the return expression to match the declared return type, or fix the return annotation",
|
|
124
|
+
"assignment": "Change the assigned value to match the variable's type annotation, or fix the annotation",
|
|
125
|
+
"attr-defined": "The attribute does not exist on this type — check for typos, add the attribute, or use hasattr/cast",
|
|
126
|
+
"name-defined": "This name is not defined — add an import or fix the spelling",
|
|
127
|
+
"override": "The method signature must match the parent class — update parameter or return types to be compatible",
|
|
128
|
+
"misc": "Review the type annotation for correctness",
|
|
129
|
+
"union-attr": "Not all union variants have this attribute — narrow the type with isinstance() or handle each variant",
|
|
130
|
+
"no-untyped-def": "Add type annotations to all parameters and the return type",
|
|
131
|
+
"type-arg": "Generic type needs explicit type parameters (e.g. list[str] not list)",
|
|
132
|
+
"var-annotated": "Add a type annotation to this variable",
|
|
133
|
+
"no-any-return": "The return type should not be Any — use a specific type",
|
|
134
|
+
"import-untyped": "This module has no type stubs — add a # type: ignore[import-untyped] comment or install stubs",
|
|
135
|
+
"call-overload": "No overload variant matches these argument types — check the function's overload signatures",
|
|
136
|
+
"index": "Invalid index type — use the correct key/index type for this container",
|
|
137
|
+
"operator": "This operator is not supported for these types — check operand types",
|
|
138
|
+
"redundant-cast": "This cast is unnecessary — the expression already has the target type",
|
|
139
|
+
"unreachable": "This code is unreachable — review the control flow above it",
|
|
140
|
+
"truthy-bool": "This expression is always truthy/falsy — the condition may be wrong",
|
|
141
|
+
"possibly-undefined": "This variable might not be defined on all code paths — initialize it or add a check",
|
|
142
|
+
}
|
|
143
|
+
if code and code in suggestions:
|
|
144
|
+
return suggestions[code]
|
|
145
|
+
# Fallback: include the error code so the agent can look it up
|
|
146
|
+
if code:
|
|
147
|
+
return f"Fix the type error (mypy code: {code}) — run 'mypy --show-error-codes' for details"
|
|
148
|
+
return "Fix the type error — run 'mypy --strict' on this file for full details"
|