cseq 0.0.1__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.
- cseq/__init__.py +9 -0
- cseq/acceptance.py +162 -0
- cseq/api.py +328 -0
- cseq/binary.py +122 -0
- cseq/cache.py +420 -0
- cseq/cli.py +895 -0
- cseq/compile_db.py +98 -0
- cseq/config.py +138 -0
- cseq/cpu_rules.py +48 -0
- cseq/dependencies.py +202 -0
- cseq/docs.py +21 -0
- cseq/dwarf.py +115 -0
- cseq/event_store.py +378 -0
- cseq/explain.py +113 -0
- cseq/fast_parser.py +286 -0
- cseq/html.py +485 -0
- cseq/html_bundle.py +464 -0
- cseq/hybrid_parser.py +120 -0
- cseq/linker.py +117 -0
- cseq/marker.py +294 -0
- cseq/model.py +367 -0
- cseq/parser.py +797 -0
- cseq/parser_contract_cases.json +69 -0
- cseq/parser_dependency_lock.json +59 -0
- cseq/parser_environment.py +186 -0
- cseq/parser_migration.py +126 -0
- cseq/plugin.py +216 -0
- cseq/project.py +305 -0
- cseq/query.py +62 -0
- cseq/resources/README.md +171 -0
- cseq/resources/design.md +6197 -0
- cseq/resources/verification_report.html +26 -0
- cseq/runtime.py +177 -0
- cseq/runtime_address.py +79 -0
- cseq/runtime_cpu.py +87 -0
- cseq/scanner.py +54 -0
- cseq/sequence.py +281 -0
- cseq/server.py +104 -0
- cseq/source_index.py +227 -0
- cseq/static_store.py +280 -0
- cseq/trace_analysis.py +336 -0
- cseq/trace_diff.py +56 -0
- cseq/tree_sitter_parser.py +468 -0
- cseq/valueflow.py +179 -0
- cseq-0.0.1.dist-info/METADATA +181 -0
- cseq-0.0.1.dist-info/RECORD +49 -0
- cseq-0.0.1.dist-info/WHEEL +5 -0
- cseq-0.0.1.dist-info/entry_points.txt +2 -0
- cseq-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"purpose": "G2B Tree-sitter Fast Syntax Front-end acceptance corpus",
|
|
4
|
+
"cases": [
|
|
5
|
+
{
|
|
6
|
+
"id": "TS-01-basic-direct-call",
|
|
7
|
+
"source": "static int add1(int x){return x+1;} int main(void){return add1(1);}",
|
|
8
|
+
"required_node_types": [
|
|
9
|
+
"function_definition",
|
|
10
|
+
"call_expression",
|
|
11
|
+
"return_statement"
|
|
12
|
+
],
|
|
13
|
+
"must_not_require_semantic_refinement": true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "TS-02-control-structure",
|
|
17
|
+
"source": "int f(int x){if(x){while(x--){;} } else {for(;;){break;}} return x;}",
|
|
18
|
+
"required_node_types": [
|
|
19
|
+
"function_definition",
|
|
20
|
+
"if_statement",
|
|
21
|
+
"while_statement",
|
|
22
|
+
"for_statement",
|
|
23
|
+
"break_statement"
|
|
24
|
+
],
|
|
25
|
+
"must_not_require_semantic_refinement": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "TS-03-typedef-struct",
|
|
29
|
+
"source": "typedef struct Ctx { int value; } Ctx; int f(Ctx *c){return c->value;}",
|
|
30
|
+
"required_node_types": [
|
|
31
|
+
"type_definition",
|
|
32
|
+
"struct_specifier",
|
|
33
|
+
"field_expression"
|
|
34
|
+
],
|
|
35
|
+
"must_not_require_semantic_refinement": false,
|
|
36
|
+
"refinement_reason": "TYPE_AMBIGUOUS"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "TS-04-preprocessor",
|
|
40
|
+
"source": "#ifdef FEATURE_X\nint f(void){return 1;}\n#else\nint f(void){return 0;}\n#endif\n",
|
|
41
|
+
"required_node_types": [
|
|
42
|
+
"preproc_ifdef",
|
|
43
|
+
"function_definition"
|
|
44
|
+
],
|
|
45
|
+
"must_preserve_all_branches": true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "TS-05-error-recovery",
|
|
49
|
+
"source": "int good(void){return 1;} int broken( { int after(void){return 2;}",
|
|
50
|
+
"required_node_types": [],
|
|
51
|
+
"must_report_error_or_missing_node": true,
|
|
52
|
+
"must_preserve_recoverable_regions": true,
|
|
53
|
+
"expected_parse_status_any_of": [
|
|
54
|
+
"ERROR_RECOVERED",
|
|
55
|
+
"PARTIAL"
|
|
56
|
+
],
|
|
57
|
+
"minimum_recovered_functions": 1
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "TS-06-vendor-extension",
|
|
61
|
+
"source": "__vendor_magic int helper(void){return 1;} int main(void){return helper();}",
|
|
62
|
+
"required_node_types": [
|
|
63
|
+
"translation_unit"
|
|
64
|
+
],
|
|
65
|
+
"must_require_semantic_refinement": true,
|
|
66
|
+
"refinement_reason": "COMPILER_EXTENSION"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"decision_id": "ADR-001",
|
|
4
|
+
"canonical_frontend": "tree-sitter-c",
|
|
5
|
+
"packages": {
|
|
6
|
+
"tree-sitter": {
|
|
7
|
+
"version": "0.26.0",
|
|
8
|
+
"python_requires": ">=3.10",
|
|
9
|
+
"artifacts": {
|
|
10
|
+
"cp313-linux-x86_64-glibc": {
|
|
11
|
+
"filename": "tree_sitter-0.26.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
|
|
12
|
+
"sha256": "7075ef857ef86f327dbb72d1e2574dda78db5754b3a1fca6506acd7fe5d561a7"
|
|
13
|
+
},
|
|
14
|
+
"cp313-windows-x86_64": {
|
|
15
|
+
"filename": "tree_sitter-0.26.0-cp313-cp313-win_amd64.whl",
|
|
16
|
+
"sha256": "93e220cab7e6a823efeb2046c49171427de92ef71c7c681c01820d14d8d3721f"
|
|
17
|
+
},
|
|
18
|
+
"cp311-linux-x86_64-glibc": {
|
|
19
|
+
"filename": "tree_sitter-0.26.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
|
|
20
|
+
"sha256": "335294ce0504fcefde5245dff596778ffaf820205b98ae0b549c72e48855f1d8"
|
|
21
|
+
},
|
|
22
|
+
"cp311-windows-x86_64": {
|
|
23
|
+
"filename": "tree_sitter-0.26.0-cp311-cp311-win_amd64.whl",
|
|
24
|
+
"sha256": "0f8793fd18ad7eec276ed4b51c097b4bf2002b357259b66b0d75db1f3f41c754"
|
|
25
|
+
},
|
|
26
|
+
"cp312-linux-x86_64-glibc": {
|
|
27
|
+
"filename": "tree_sitter-0.26.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
|
|
28
|
+
"sha256": "5a6b333b0282d8bb0af741f9b018bd2523d4eecb2686bf6717066a625fecfaa4"
|
|
29
|
+
},
|
|
30
|
+
"cp312-windows-x86_64": {
|
|
31
|
+
"filename": "tree_sitter-0.26.0-cp312-cp312-win_amd64.whl",
|
|
32
|
+
"sha256": "ca89e361a276dbc934b28a43dd881199e25d34ff5493ee0ce45f3c52a6124a37"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"tree-sitter-c": {
|
|
37
|
+
"version": "0.24.2",
|
|
38
|
+
"python_requires": ">=3.10",
|
|
39
|
+
"grammar_abi": 15,
|
|
40
|
+
"artifacts": {
|
|
41
|
+
"abi3-linux-x86_64-glibc": {
|
|
42
|
+
"filename": "tree_sitter_c-0.24.2-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",
|
|
43
|
+
"sha256": "5041ef67eb68ce6bc8bb0b1f8ef3a5585ce523dae0c7eec109ab0627dd75aede"
|
|
44
|
+
},
|
|
45
|
+
"abi3-windows-x86_64": {
|
|
46
|
+
"filename": "tree_sitter_c-0.24.2-cp310-abi3-win_amd64.whl",
|
|
47
|
+
"sha256": "abb549225091f7b25df2dd3a0143ece6e208f7055d8bcb4700b41ee79b9ef1e1"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"expected_api": {
|
|
53
|
+
"language_factory": "tree_sitter.Language(tree_sitter_c.language())",
|
|
54
|
+
"parser_factory": "tree_sitter.Parser(language)",
|
|
55
|
+
"parse_call": "parser.parse(source_bytes)",
|
|
56
|
+
"smoke_source": "int main(void) { return 0; }",
|
|
57
|
+
"smoke_root_type": "translation_unit"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
from importlib import metadata, util
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import platform
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_LOCK_PATH = Path(__file__).with_name("parser_dependency_lock.json")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class ParserEnvironment:
|
|
17
|
+
tree_sitter_available: bool
|
|
18
|
+
tree_sitter_c_available: bool
|
|
19
|
+
tree_sitter_version: str | None
|
|
20
|
+
tree_sitter_c_version: str | None
|
|
21
|
+
expected_tree_sitter_version: str
|
|
22
|
+
expected_tree_sitter_c_version: str
|
|
23
|
+
versions_match_lock: bool
|
|
24
|
+
api_smoke_tested: bool
|
|
25
|
+
api_smoke_ok: bool | None
|
|
26
|
+
grammar_abi: int | None
|
|
27
|
+
expected_grammar_abi: int
|
|
28
|
+
grammar_abi_match: bool | None
|
|
29
|
+
platform_lock_key: str | None
|
|
30
|
+
runtime_wheel_filename: str | None
|
|
31
|
+
runtime_wheel_sha256: str | None
|
|
32
|
+
grammar_wheel_filename: str | None
|
|
33
|
+
grammar_wheel_sha256: str | None
|
|
34
|
+
canonical_fast_frontend_ready: bool
|
|
35
|
+
fallback_required: bool
|
|
36
|
+
incompatibility_reasons: tuple[str, ...]
|
|
37
|
+
|
|
38
|
+
def to_dict(self) -> dict[str, object]:
|
|
39
|
+
return asdict(self)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def load_parser_dependency_lock() -> dict[str, Any]:
|
|
43
|
+
return json.loads(_LOCK_PATH.read_text(encoding="utf-8"))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _distribution_version(name: str) -> str | None:
|
|
47
|
+
try:
|
|
48
|
+
return metadata.version(name)
|
|
49
|
+
except metadata.PackageNotFoundError:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _platform_keys() -> tuple[str | None, str | None]:
|
|
54
|
+
machine = platform.machine().lower()
|
|
55
|
+
if machine in {"amd64", "x86_64"}:
|
|
56
|
+
arch = "x86_64"
|
|
57
|
+
elif machine in {"arm64", "aarch64"}:
|
|
58
|
+
arch = "aarch64"
|
|
59
|
+
else:
|
|
60
|
+
arch = machine
|
|
61
|
+
py = f"cp{sys.version_info.major}{sys.version_info.minor}"
|
|
62
|
+
system = platform.system().lower()
|
|
63
|
+
if system == "linux":
|
|
64
|
+
return f"{py}-linux-{arch}-glibc", f"abi3-linux-{arch}-glibc"
|
|
65
|
+
if system == "windows":
|
|
66
|
+
return f"{py}-windows-{arch}", f"abi3-windows-{arch}"
|
|
67
|
+
return None, None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _artifact(lock: dict[str, Any], package: str, key: str | None) -> tuple[str | None, str | None]:
|
|
71
|
+
if key is None:
|
|
72
|
+
return None, None
|
|
73
|
+
entry = lock["packages"][package].get("artifacts", {}).get(key)
|
|
74
|
+
if not entry:
|
|
75
|
+
return None, None
|
|
76
|
+
return entry.get("filename"), entry.get("sha256")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _smoke_tree_sitter(expected_abi: int, smoke_source: str, smoke_root: str) -> tuple[bool, int | None, str | None]:
|
|
80
|
+
"""Import the locked API and execute a minimal C parse.
|
|
81
|
+
|
|
82
|
+
The function is called only when both distributions are discoverable. Any
|
|
83
|
+
native/API incompatibility becomes a diagnostic reason rather than leaking
|
|
84
|
+
an import exception out of `cseq doctor`.
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
import tree_sitter # type: ignore
|
|
88
|
+
import tree_sitter_c # type: ignore
|
|
89
|
+
|
|
90
|
+
language = tree_sitter.Language(tree_sitter_c.language())
|
|
91
|
+
abi = int(getattr(language, "abi_version"))
|
|
92
|
+
parser = tree_sitter.Parser(language)
|
|
93
|
+
tree = parser.parse(smoke_source.encode("utf-8"))
|
|
94
|
+
root_type = getattr(tree.root_node, "type", None)
|
|
95
|
+
ok = abi == expected_abi and root_type == smoke_root
|
|
96
|
+
reason = None if ok else f"smoke-mismatch: abi={abi}, root={root_type!r}"
|
|
97
|
+
return ok, abi, reason
|
|
98
|
+
except Exception as exc: # pragma: no cover - depends on native runtime
|
|
99
|
+
return False, None, f"api-smoke-failed: {type(exc).__name__}: {exc}"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def probe_parser_environment() -> ParserEnvironment:
|
|
103
|
+
"""Inspect the parser runtime against the G2 dependency lock.
|
|
104
|
+
|
|
105
|
+
Availability alone is insufficient. Canonical readiness requires the exact
|
|
106
|
+
locked distribution versions plus a real API/grammar smoke parse. Missing
|
|
107
|
+
dependencies and incompatible installed dependencies are therefore reported
|
|
108
|
+
as distinct states by the returned diagnostics.
|
|
109
|
+
"""
|
|
110
|
+
lock = load_parser_dependency_lock()
|
|
111
|
+
ts_lock = lock["packages"]["tree-sitter"]
|
|
112
|
+
c_lock = lock["packages"]["tree-sitter-c"]
|
|
113
|
+
expected_ts = str(ts_lock["version"])
|
|
114
|
+
expected_c = str(c_lock["version"])
|
|
115
|
+
expected_abi = int(c_lock["grammar_abi"])
|
|
116
|
+
|
|
117
|
+
ts = util.find_spec("tree_sitter") is not None
|
|
118
|
+
tsc = util.find_spec("tree_sitter_c") is not None
|
|
119
|
+
ts_version = _distribution_version("tree-sitter") if ts else None
|
|
120
|
+
c_version = _distribution_version("tree-sitter-c") if tsc else None
|
|
121
|
+
versions_match = ts_version == expected_ts and c_version == expected_c
|
|
122
|
+
|
|
123
|
+
runtime_key, grammar_key = _platform_keys()
|
|
124
|
+
runtime_filename, runtime_hash = _artifact(lock, "tree-sitter", runtime_key)
|
|
125
|
+
grammar_filename, grammar_hash = _artifact(lock, "tree-sitter-c", grammar_key)
|
|
126
|
+
|
|
127
|
+
reasons: list[str] = []
|
|
128
|
+
if not ts:
|
|
129
|
+
reasons.append("tree-sitter-not-installed")
|
|
130
|
+
if not tsc:
|
|
131
|
+
reasons.append("tree-sitter-c-not-installed")
|
|
132
|
+
if ts and ts_version != expected_ts:
|
|
133
|
+
reasons.append(f"tree-sitter-version-mismatch:{ts_version}!={expected_ts}")
|
|
134
|
+
if tsc and c_version != expected_c:
|
|
135
|
+
reasons.append(f"tree-sitter-c-version-mismatch:{c_version}!={expected_c}")
|
|
136
|
+
if runtime_key is not None and runtime_filename is None:
|
|
137
|
+
reasons.append(f"runtime-wheel-not-locked-for:{runtime_key}")
|
|
138
|
+
if grammar_key is not None and grammar_filename is None:
|
|
139
|
+
reasons.append(f"grammar-wheel-not-locked-for:{grammar_key}")
|
|
140
|
+
|
|
141
|
+
smoke_tested = bool(ts and tsc and versions_match)
|
|
142
|
+
smoke_ok: bool | None = None
|
|
143
|
+
grammar_abi: int | None = None
|
|
144
|
+
grammar_abi_match: bool | None = None
|
|
145
|
+
if smoke_tested:
|
|
146
|
+
smoke_ok, grammar_abi, smoke_reason = _smoke_tree_sitter(
|
|
147
|
+
expected_abi,
|
|
148
|
+
str(lock["expected_api"]["smoke_source"]),
|
|
149
|
+
str(lock["expected_api"]["smoke_root_type"]),
|
|
150
|
+
)
|
|
151
|
+
grammar_abi_match = grammar_abi == expected_abi if grammar_abi is not None else False
|
|
152
|
+
if smoke_reason:
|
|
153
|
+
reasons.append(smoke_reason)
|
|
154
|
+
|
|
155
|
+
ready = bool(
|
|
156
|
+
ts
|
|
157
|
+
and tsc
|
|
158
|
+
and versions_match
|
|
159
|
+
and smoke_tested
|
|
160
|
+
and smoke_ok
|
|
161
|
+
and grammar_abi_match
|
|
162
|
+
and runtime_filename
|
|
163
|
+
and grammar_filename
|
|
164
|
+
)
|
|
165
|
+
return ParserEnvironment(
|
|
166
|
+
tree_sitter_available=ts,
|
|
167
|
+
tree_sitter_c_available=tsc,
|
|
168
|
+
tree_sitter_version=ts_version,
|
|
169
|
+
tree_sitter_c_version=c_version,
|
|
170
|
+
expected_tree_sitter_version=expected_ts,
|
|
171
|
+
expected_tree_sitter_c_version=expected_c,
|
|
172
|
+
versions_match_lock=versions_match,
|
|
173
|
+
api_smoke_tested=smoke_tested,
|
|
174
|
+
api_smoke_ok=smoke_ok,
|
|
175
|
+
grammar_abi=grammar_abi,
|
|
176
|
+
expected_grammar_abi=expected_abi,
|
|
177
|
+
grammar_abi_match=grammar_abi_match,
|
|
178
|
+
platform_lock_key=runtime_key,
|
|
179
|
+
runtime_wheel_filename=runtime_filename,
|
|
180
|
+
runtime_wheel_sha256=runtime_hash,
|
|
181
|
+
grammar_wheel_filename=grammar_filename,
|
|
182
|
+
grammar_wheel_sha256=grammar_hash,
|
|
183
|
+
canonical_fast_frontend_ready=ready,
|
|
184
|
+
fallback_required=not ready,
|
|
185
|
+
incompatibility_reasons=tuple(reasons),
|
|
186
|
+
)
|
cseq/parser_migration.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .parser_environment import load_parser_dependency_lock, probe_parser_environment
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_CONTRACT_PATH = Path(__file__).with_name("parser_contract_cases.json")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class WheelArtifactCheck:
|
|
17
|
+
package: str
|
|
18
|
+
filename: str | None
|
|
19
|
+
expected_sha256: str | None
|
|
20
|
+
path: str | None
|
|
21
|
+
exists: bool
|
|
22
|
+
actual_sha256: str | None
|
|
23
|
+
hash_match: bool
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> dict[str, object]:
|
|
26
|
+
return asdict(self)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class G2BResumeReport:
|
|
31
|
+
status: str
|
|
32
|
+
environment_ready: bool
|
|
33
|
+
wheelhouse_checked: bool
|
|
34
|
+
wheelhouse_ready: bool | None
|
|
35
|
+
wheel_checks: tuple[WheelArtifactCheck, ...]
|
|
36
|
+
contract_case_count: int
|
|
37
|
+
next_action: str
|
|
38
|
+
|
|
39
|
+
def to_dict(self) -> dict[str, object]:
|
|
40
|
+
payload = asdict(self)
|
|
41
|
+
payload["wheel_checks"] = [item.to_dict() for item in self.wheel_checks]
|
|
42
|
+
return payload
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _sha256(path: Path) -> str:
|
|
46
|
+
h = hashlib.sha256()
|
|
47
|
+
with path.open("rb") as f:
|
|
48
|
+
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
|
49
|
+
h.update(chunk)
|
|
50
|
+
return h.hexdigest()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_parser_contract_cases() -> tuple[dict[str, Any], ...]:
|
|
54
|
+
raw = json.loads(_CONTRACT_PATH.read_text(encoding="utf-8"))
|
|
55
|
+
cases = raw.get("cases", [])
|
|
56
|
+
if not isinstance(cases, list):
|
|
57
|
+
raise ValueError("parser contract cases must be a list")
|
|
58
|
+
return tuple(cases)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def verify_parser_wheelhouse(directory: str | Path) -> tuple[WheelArtifactCheck, ...]:
|
|
62
|
+
"""Verify the exact locked wheels required by the current platform.
|
|
63
|
+
|
|
64
|
+
This deliberately does not install anything. It makes the G2B restart
|
|
65
|
+
condition deterministic and safe for offline/corporate environments.
|
|
66
|
+
"""
|
|
67
|
+
root = Path(directory)
|
|
68
|
+
env = probe_parser_environment()
|
|
69
|
+
required = (
|
|
70
|
+
("tree-sitter", env.runtime_wheel_filename, env.runtime_wheel_sha256),
|
|
71
|
+
("tree-sitter-c", env.grammar_wheel_filename, env.grammar_wheel_sha256),
|
|
72
|
+
)
|
|
73
|
+
checks: list[WheelArtifactCheck] = []
|
|
74
|
+
for package, filename, expected in required:
|
|
75
|
+
if filename is None or expected is None:
|
|
76
|
+
checks.append(WheelArtifactCheck(package, filename, expected, None, False, None, False))
|
|
77
|
+
continue
|
|
78
|
+
path = root / filename
|
|
79
|
+
exists = path.is_file()
|
|
80
|
+
actual = _sha256(path) if exists else None
|
|
81
|
+
checks.append(WheelArtifactCheck(
|
|
82
|
+
package=package,
|
|
83
|
+
filename=filename,
|
|
84
|
+
expected_sha256=expected,
|
|
85
|
+
path=str(path),
|
|
86
|
+
exists=exists,
|
|
87
|
+
actual_sha256=actual,
|
|
88
|
+
hash_match=bool(exists and actual == expected),
|
|
89
|
+
))
|
|
90
|
+
return tuple(checks)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def build_g2b_resume_report(wheel_dir: str | Path | None = None) -> G2BResumeReport:
|
|
94
|
+
env = probe_parser_environment()
|
|
95
|
+
contract_count = len(load_parser_contract_cases())
|
|
96
|
+
if env.canonical_fast_frontend_ready:
|
|
97
|
+
return G2BResumeReport(
|
|
98
|
+
status="READY_FOR_TREE_SITTER_EXECUTION",
|
|
99
|
+
environment_ready=True,
|
|
100
|
+
wheelhouse_checked=wheel_dir is not None,
|
|
101
|
+
wheelhouse_ready=None if wheel_dir is None else all(x.hash_match for x in verify_parser_wheelhouse(wheel_dir)),
|
|
102
|
+
wheel_checks=() if wheel_dir is None else verify_parser_wheelhouse(wheel_dir),
|
|
103
|
+
contract_case_count=contract_count,
|
|
104
|
+
next_action="run-cseq-analysis",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
checks = () if wheel_dir is None else verify_parser_wheelhouse(wheel_dir)
|
|
108
|
+
wheel_ready = None if wheel_dir is None else bool(checks and all(x.hash_match for x in checks))
|
|
109
|
+
if wheel_ready:
|
|
110
|
+
next_action = "offline-install-locked-wheels-then-rerun-doctor"
|
|
111
|
+
status = "WHEELS_VERIFIED_INSTALL_REQUIRED"
|
|
112
|
+
elif wheel_dir is not None:
|
|
113
|
+
next_action = "supply-exact-locked-wheels"
|
|
114
|
+
status = "WHEELHOUSE_INCOMPLETE_OR_MISMATCHED"
|
|
115
|
+
else:
|
|
116
|
+
next_action = "supply-locked-wheelhouse-or-install-locked-runtime"
|
|
117
|
+
status = "DEPENDENCY_BLOCKED"
|
|
118
|
+
return G2BResumeReport(
|
|
119
|
+
status=status,
|
|
120
|
+
environment_ready=False,
|
|
121
|
+
wheelhouse_checked=wheel_dir is not None,
|
|
122
|
+
wheelhouse_ready=wheel_ready,
|
|
123
|
+
wheel_checks=checks,
|
|
124
|
+
contract_case_count=contract_count,
|
|
125
|
+
next_action=next_action,
|
|
126
|
+
)
|
cseq/plugin.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from importlib import metadata
|
|
6
|
+
from typing import Any, Iterable, Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
from .model import CallSiteRecord, FunctionRecord
|
|
9
|
+
from .runtime import RuntimeEvent
|
|
10
|
+
|
|
11
|
+
PLUGIN_API_VERSION = 1
|
|
12
|
+
ENTRY_POINT_GROUP = "cseq.plugins"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PluginKind(str, Enum):
|
|
16
|
+
COMPILER_EXTENSION = "CompilerExtensionPlugin"
|
|
17
|
+
CPU_RULE_PROVIDER = "CpuRuleProvider"
|
|
18
|
+
SEMANTIC_HINT_PROVIDER = "SemanticHintProvider"
|
|
19
|
+
RUNTIME_TIMESTAMP_PARSER = "RuntimeTimestampParser"
|
|
20
|
+
RUNTIME_EVENT_PROVIDER = "RuntimeEventProvider"
|
|
21
|
+
LINKER_MAP_ADAPTER = "LinkerMapAdapter"
|
|
22
|
+
RENDERER_EXTENSION = "RendererExtension"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class FunctionView:
|
|
27
|
+
name: str
|
|
28
|
+
qualified_id: str
|
|
29
|
+
source_path: str
|
|
30
|
+
storage_class: str | None
|
|
31
|
+
parameters: tuple[str, ...]
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_record(cls, record: FunctionRecord) -> "FunctionView":
|
|
35
|
+
return cls(record.name, record.qualified_id, record.source_path, record.storage_class, record.parameters)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True, slots=True)
|
|
39
|
+
class CallSiteView:
|
|
40
|
+
caller_id: str
|
|
41
|
+
caller_name: str
|
|
42
|
+
callee_name: str | None
|
|
43
|
+
raw_text: str
|
|
44
|
+
source_path: str
|
|
45
|
+
target_kind: str
|
|
46
|
+
target_function_ids: tuple[str, ...]
|
|
47
|
+
unknown_possible: bool
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_record(cls, record: CallSiteRecord) -> "CallSiteView":
|
|
51
|
+
return cls(
|
|
52
|
+
record.caller_id,
|
|
53
|
+
record.caller_name,
|
|
54
|
+
record.callee_name,
|
|
55
|
+
record.raw_text,
|
|
56
|
+
record.source_path,
|
|
57
|
+
record.target_kind.value,
|
|
58
|
+
record.target_function_ids,
|
|
59
|
+
record.unknown_possible,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True, slots=True)
|
|
64
|
+
class RuntimeEventView:
|
|
65
|
+
event_index: int
|
|
66
|
+
marker_id: str
|
|
67
|
+
timestamp_raw: str | None
|
|
68
|
+
cpu_hint: str | None
|
|
69
|
+
task_hint: str | None
|
|
70
|
+
message: str | None
|
|
71
|
+
payload: tuple[tuple[str, str], ...]
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_record(cls, record: RuntimeEvent) -> "RuntimeEventView":
|
|
75
|
+
return cls(
|
|
76
|
+
record.event_index,
|
|
77
|
+
record.marker_id,
|
|
78
|
+
record.timestamp_raw,
|
|
79
|
+
record.cpu_hint,
|
|
80
|
+
record.task_hint,
|
|
81
|
+
record.message,
|
|
82
|
+
record.payload,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True, slots=True)
|
|
87
|
+
class PluginEvidence:
|
|
88
|
+
plugin_name: str
|
|
89
|
+
kind: PluginKind
|
|
90
|
+
target: str
|
|
91
|
+
evidence_type: str
|
|
92
|
+
value: str
|
|
93
|
+
confidence: str = "MEDIUM"
|
|
94
|
+
details: tuple[tuple[str, str], ...] = ()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(frozen=True, slots=True)
|
|
98
|
+
class PluginDiagnostic:
|
|
99
|
+
plugin_name: str
|
|
100
|
+
severity: str
|
|
101
|
+
code: str
|
|
102
|
+
message: str
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True, slots=True)
|
|
106
|
+
class PluginDescriptor:
|
|
107
|
+
name: str
|
|
108
|
+
kinds: tuple[PluginKind, ...]
|
|
109
|
+
api_version: int = PLUGIN_API_VERSION
|
|
110
|
+
version: str | None = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@runtime_checkable
|
|
114
|
+
class CseqPlugin(Protocol):
|
|
115
|
+
descriptor: PluginDescriptor
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(slots=True)
|
|
119
|
+
class PluginRunResult:
|
|
120
|
+
evidence: list[PluginEvidence] = field(default_factory=list)
|
|
121
|
+
diagnostics: list[PluginDiagnostic] = field(default_factory=list)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def has_errors(self) -> bool:
|
|
125
|
+
return any(d.severity in {"error", "fatal"} for d in self.diagnostics)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class PluginManager:
|
|
129
|
+
"""Fail-safe plugin executor.
|
|
130
|
+
|
|
131
|
+
Plugins receive immutable façade views rather than internal IR objects. Any
|
|
132
|
+
plugin exception becomes a diagnostic; the core caller can continue with
|
|
133
|
+
ordinary analysis and simply lose the extra evidence.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(self, plugins: Iterable[CseqPlugin] = ()):
|
|
137
|
+
self._plugins: dict[str, CseqPlugin] = {}
|
|
138
|
+
self.diagnostics: list[PluginDiagnostic] = []
|
|
139
|
+
for plugin in plugins:
|
|
140
|
+
self.register(plugin)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def plugins(self) -> tuple[CseqPlugin, ...]:
|
|
144
|
+
return tuple(self._plugins[name] for name in sorted(self._plugins))
|
|
145
|
+
|
|
146
|
+
def register(self, plugin: CseqPlugin) -> None:
|
|
147
|
+
descriptor = getattr(plugin, "descriptor", None)
|
|
148
|
+
if not isinstance(descriptor, PluginDescriptor):
|
|
149
|
+
raise TypeError("plugin must expose PluginDescriptor as descriptor")
|
|
150
|
+
if descriptor.api_version != PLUGIN_API_VERSION:
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"plugin {descriptor.name} API {descriptor.api_version} is incompatible with cseq API {PLUGIN_API_VERSION}"
|
|
153
|
+
)
|
|
154
|
+
self._plugins[descriptor.name] = plugin
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def discover(cls, enabled: Iterable[str] | None = None) -> "PluginManager":
|
|
158
|
+
enabled_set = set(enabled or ())
|
|
159
|
+
manager = cls()
|
|
160
|
+
try:
|
|
161
|
+
eps = metadata.entry_points()
|
|
162
|
+
group = eps.select(group=ENTRY_POINT_GROUP) if hasattr(eps, "select") else eps.get(ENTRY_POINT_GROUP, [])
|
|
163
|
+
except Exception as exc: # discovery itself must not kill core analysis
|
|
164
|
+
manager.diagnostics.append(PluginDiagnostic("<discovery>", "warning", "PLUGIN_DISCOVERY_FAILED", str(exc)))
|
|
165
|
+
return manager
|
|
166
|
+
discovered_names: set[str] = set()
|
|
167
|
+
for ep in group:
|
|
168
|
+
discovered_names.add(ep.name)
|
|
169
|
+
if enabled_set and ep.name not in enabled_set:
|
|
170
|
+
continue
|
|
171
|
+
try:
|
|
172
|
+
obj = ep.load()
|
|
173
|
+
plugin = obj() if isinstance(obj, type) else obj
|
|
174
|
+
manager.register(plugin)
|
|
175
|
+
except Exception as exc:
|
|
176
|
+
manager.diagnostics.append(PluginDiagnostic(ep.name, "warning", "PLUGIN_LOAD_FAILED", str(exc)))
|
|
177
|
+
for missing in sorted(enabled_set - discovered_names):
|
|
178
|
+
manager.diagnostics.append(PluginDiagnostic(missing, "warning", "PLUGIN_NOT_FOUND", "enabled plugin was not discovered"))
|
|
179
|
+
return manager
|
|
180
|
+
|
|
181
|
+
def run_semantic_hints(
|
|
182
|
+
self,
|
|
183
|
+
*,
|
|
184
|
+
functions: Iterable[FunctionRecord] = (),
|
|
185
|
+
callsites: Iterable[CallSiteRecord] = (),
|
|
186
|
+
) -> PluginRunResult:
|
|
187
|
+
fviews = tuple(FunctionView.from_record(x) for x in functions)
|
|
188
|
+
cviews = tuple(CallSiteView.from_record(x) for x in callsites)
|
|
189
|
+
return self._run_hook("semantic_hints", functions=fviews, callsites=cviews)
|
|
190
|
+
|
|
191
|
+
def run_runtime_event_hints(self, events: Iterable[RuntimeEvent]) -> PluginRunResult:
|
|
192
|
+
views = tuple(RuntimeEventView.from_record(x) for x in events)
|
|
193
|
+
return self._run_hook("runtime_event_hints", events=views)
|
|
194
|
+
|
|
195
|
+
def _run_hook(self, hook: str, **kwargs: Any) -> PluginRunResult:
|
|
196
|
+
result = PluginRunResult(diagnostics=list(self.diagnostics))
|
|
197
|
+
for plugin in self.plugins:
|
|
198
|
+
fn = getattr(plugin, hook, None)
|
|
199
|
+
if fn is None:
|
|
200
|
+
continue
|
|
201
|
+
name = plugin.descriptor.name
|
|
202
|
+
try:
|
|
203
|
+
returned = fn(**kwargs)
|
|
204
|
+
if returned is None:
|
|
205
|
+
continue
|
|
206
|
+
for item in returned:
|
|
207
|
+
if not isinstance(item, PluginEvidence):
|
|
208
|
+
raise TypeError(f"{hook} must yield PluginEvidence, got {type(item).__name__}")
|
|
209
|
+
if item.plugin_name != name:
|
|
210
|
+
raise ValueError("PluginEvidence.plugin_name must match plugin descriptor name")
|
|
211
|
+
if item.kind not in plugin.descriptor.kinds:
|
|
212
|
+
raise ValueError(f"evidence kind {item.kind.value} was not declared by plugin")
|
|
213
|
+
result.evidence.append(item)
|
|
214
|
+
except Exception as exc:
|
|
215
|
+
result.diagnostics.append(PluginDiagnostic(name, "warning", "PLUGIN_HOOK_FAILED", f"{hook}: {exc}"))
|
|
216
|
+
return result
|