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
cseq/fast_parser.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from bisect import bisect_right
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from .model import (
|
|
10
|
+
CallSiteRecord,
|
|
11
|
+
CompactFunctionRecord,
|
|
12
|
+
CompactFunctionTable,
|
|
13
|
+
FileParseArtifact,
|
|
14
|
+
FunctionRecord,
|
|
15
|
+
ParseStatus,
|
|
16
|
+
SourceRange,
|
|
17
|
+
TranslationUnit,
|
|
18
|
+
)
|
|
19
|
+
from .parser import ParserBackend
|
|
20
|
+
|
|
21
|
+
_C_KEYWORDS = {
|
|
22
|
+
"if", "for", "while", "switch", "return", "sizeof", "_Alignof", "_Generic",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_FUNC_RE = re.compile(
|
|
26
|
+
r"(?P<header>(?:(?P<storage>static)\s+)?(?:inline\s+)?"
|
|
27
|
+
r"(?:const\s+)?(?:void|char|short|int|long|float|double|signed|unsigned|[A-Za-z_]\w*)"
|
|
28
|
+
r"(?:\s+|\s*\*\s*)(?P<name>[A-Za-z_]\w*)\s*\((?P<params>[^;{}()]*)\)\s*)\{",
|
|
29
|
+
re.MULTILINE,
|
|
30
|
+
)
|
|
31
|
+
_CALL_RE = re.compile(r"\b([A-Za-z_]\w*)\s*\(")
|
|
32
|
+
|
|
33
|
+
# Constructs whose semantics/provenance require the semantic backend. The fast
|
|
34
|
+
# path deliberately claims only a narrow, auditable subset.
|
|
35
|
+
_COMPLEX_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
|
|
36
|
+
("preprocessor-conditional", re.compile(r"(?m)^\s*#\s*(?:if|ifdef|ifndef|elif|else|endif)\b")),
|
|
37
|
+
("macro-definition", re.compile(r"(?m)^\s*#\s*define\b")),
|
|
38
|
+
("function-pointer", re.compile(r"\(\s*\*\s*[A-Za-z_]\w*\s*\)")),
|
|
39
|
+
("member-access", re.compile(r"(?:->|\.[A-Za-z_]\w*)")),
|
|
40
|
+
("aggregate-or-type", re.compile(r"\b(?:struct|union|enum|typedef)\b")),
|
|
41
|
+
("switch", re.compile(r"\b(?:switch|case|default)\b")),
|
|
42
|
+
("loop", re.compile(r"\b(?:for|while|do)\b")),
|
|
43
|
+
("goto", re.compile(r"\b(?:goto|setjmp|longjmp)\b")),
|
|
44
|
+
("extension", re.compile(r"\b(?:asm|__asm__|__attribute__|__declspec|typeof|__typeof__)\b")),
|
|
45
|
+
("reserved-extension", re.compile(r"\b__[A-Za-z_]\w*\b")),
|
|
46
|
+
("ternary", re.compile(r"\?[^:]+:")),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True, slots=True)
|
|
51
|
+
class FastEligibility:
|
|
52
|
+
eligible: bool
|
|
53
|
+
reasons: tuple[str, ...] = ()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class FastPrepared:
|
|
58
|
+
raw: str
|
|
59
|
+
code_only: str
|
|
60
|
+
spans: tuple[tuple[str, int, int, int, int, str | None, str], ...]
|
|
61
|
+
line_starts: tuple[int, ...]
|
|
62
|
+
eligibility: FastEligibility
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class FastLexicalParser(ParserBackend):
|
|
66
|
+
"""Conservative lexical parser for simple C translation units.
|
|
67
|
+
|
|
68
|
+
It is intentionally not a replacement C compiler. It only returns an
|
|
69
|
+
artifact when the TU is inside a small subset whose direct-call structure is
|
|
70
|
+
unambiguous. Everything else is refined by HybridParser's semantic backend.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
name = "fast-lexical-v1"
|
|
74
|
+
|
|
75
|
+
def prepare(self, translation_unit: TranslationUnit) -> FastPrepared:
|
|
76
|
+
raw = translation_unit.source_text if translation_unit.source_text is not None else translation_unit.source.path.read_text(encoding="utf-8", errors="replace")
|
|
77
|
+
masked = _mask_comments_and_literals(raw)
|
|
78
|
+
# Preprocessor include/pragma lines do not participate in lexical C
|
|
79
|
+
# structure. Blank them while preserving byte/character offsets.
|
|
80
|
+
code_only = re.sub(r"(?m)^[ \t]*#.*$", lambda m: " " * len(m.group(0)), masked)
|
|
81
|
+
reasons: list[str] = []
|
|
82
|
+
if translation_unit.arguments:
|
|
83
|
+
# Compile flags can alter parsing/active code. Until the fast path
|
|
84
|
+
# has a real preprocessor, defer such TUs to clang.
|
|
85
|
+
reasons.append("compile-arguments")
|
|
86
|
+
for name, pattern in _COMPLEX_PATTERNS[:2]:
|
|
87
|
+
if pattern.search(masked):
|
|
88
|
+
reasons.append(name)
|
|
89
|
+
for name, pattern in _COMPLEX_PATTERNS[2:]:
|
|
90
|
+
if pattern.search(code_only):
|
|
91
|
+
reasons.append(name)
|
|
92
|
+
spans = tuple(_function_spans(code_only))
|
|
93
|
+
if not spans and re.search(r"\b[A-Za-z_]\w*\s*\([^;{}]*\)\s*\{", code_only):
|
|
94
|
+
reasons.append("unrecognized-function-header")
|
|
95
|
+
# Calls nested under control flow need region/guard provenance. Do not
|
|
96
|
+
# rescan every function body here: parse_prepared already scans bodies
|
|
97
|
+
# for calls. If that scan finds a real call in a function containing an
|
|
98
|
+
# `if`, it raises and HybridParser refines the TU with clang.
|
|
99
|
+
return FastPrepared(
|
|
100
|
+
raw=raw,
|
|
101
|
+
code_only=code_only,
|
|
102
|
+
spans=spans,
|
|
103
|
+
line_starts=tuple(_line_starts(raw)),
|
|
104
|
+
eligibility=FastEligibility(not reasons, tuple(dict.fromkeys(reasons))),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def parse(self, translation_unit: TranslationUnit) -> FileParseArtifact:
|
|
108
|
+
prepared = self.prepare(translation_unit)
|
|
109
|
+
if not prepared.eligibility.eligible:
|
|
110
|
+
raise ValueError("TU is not eligible for fast parser: " + ", ".join(prepared.eligibility.reasons))
|
|
111
|
+
return self.parse_prepared(translation_unit, prepared)
|
|
112
|
+
|
|
113
|
+
def parse_prepared(self, translation_unit: TranslationUnit, prepared: FastPrepared) -> FileParseArtifact:
|
|
114
|
+
raw = prepared.raw
|
|
115
|
+
masked = prepared.code_only
|
|
116
|
+
line_starts = prepared.line_starts
|
|
117
|
+
functions = CompactFunctionTable(translation_unit.source.project_relative_path)
|
|
118
|
+
callsites: list[CallSiteRecord] = []
|
|
119
|
+
|
|
120
|
+
line_cursor = 0
|
|
121
|
+
line_count = len(line_starts)
|
|
122
|
+
|
|
123
|
+
def line_col_monotonic(pos: int) -> tuple[int, int]:
|
|
124
|
+
nonlocal line_cursor
|
|
125
|
+
while line_cursor + 1 < line_count and line_starts[line_cursor + 1] <= pos:
|
|
126
|
+
line_cursor += 1
|
|
127
|
+
return line_cursor + 1, pos - line_starts[line_cursor] + 1
|
|
128
|
+
|
|
129
|
+
for name, body_start, body_end, header_start, name_start, storage, params_text in prepared.spans:
|
|
130
|
+
start_line, start_col = line_col_monotonic(name_start)
|
|
131
|
+
end_line, end_col = line_col_monotonic(body_end)
|
|
132
|
+
storage = sys.intern(storage) if storage else None
|
|
133
|
+
tq = raw[header_start:name_start].strip()
|
|
134
|
+
type_qualifier = sys.intern(tq) if tq else None
|
|
135
|
+
functions.append_fields(
|
|
136
|
+
name, start_line, start_col, end_line, end_col,
|
|
137
|
+
storage, type_qualifier, _parameter_names(params_text),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
body = masked[body_start:body_end]
|
|
141
|
+
caller_id: str | None = None
|
|
142
|
+
has_if = bool(re.search(r"\bif\s*\(", body))
|
|
143
|
+
for match in _CALL_RE.finditer(body):
|
|
144
|
+
callee = match.group(1)
|
|
145
|
+
if callee in _C_KEYWORDS:
|
|
146
|
+
continue
|
|
147
|
+
# A real call in a function that contains control flow needs
|
|
148
|
+
# region provenance. Refine rather than approximating it.
|
|
149
|
+
if has_if:
|
|
150
|
+
raise ValueError("call-under-if requires semantic refinement")
|
|
151
|
+
absolute = body_start + match.start(1)
|
|
152
|
+
open_paren = body_start + match.end(0) - 1
|
|
153
|
+
call_end = _balanced_call_end(masked, open_paren, body_end)
|
|
154
|
+
if call_end is None:
|
|
155
|
+
continue
|
|
156
|
+
line, col = line_col_monotonic(absolute)
|
|
157
|
+
end_l, end_c = line_col_monotonic(call_end)
|
|
158
|
+
if caller_id is None:
|
|
159
|
+
caller_id = _function_id(
|
|
160
|
+
translation_unit, name, start_line, start_col, storage, type_qualifier
|
|
161
|
+
)
|
|
162
|
+
callsites.append(CallSiteRecord(
|
|
163
|
+
caller_id=caller_id,
|
|
164
|
+
caller_name=name,
|
|
165
|
+
callee_name=callee,
|
|
166
|
+
raw_text=raw[absolute:call_end + 1].strip(),
|
|
167
|
+
source_path=translation_unit.source.project_relative_path,
|
|
168
|
+
source_range=SourceRange(line, col, end_l, end_c),
|
|
169
|
+
))
|
|
170
|
+
|
|
171
|
+
return FileParseArtifact(
|
|
172
|
+
translation_unit=translation_unit,
|
|
173
|
+
status=ParseStatus.OK,
|
|
174
|
+
functions=functions,
|
|
175
|
+
callsites=callsites,
|
|
176
|
+
parser_backend=self.name,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _function_spans(text: str) -> list[tuple[str, int, int, int, int, str | None, str]]:
|
|
181
|
+
out: list[tuple[str, int, int, int, int, str | None, str]] = []
|
|
182
|
+
for m in _FUNC_RE.finditer(text):
|
|
183
|
+
brace = m.end() - 1
|
|
184
|
+
end = _matching_brace(text, brace)
|
|
185
|
+
if end is None:
|
|
186
|
+
continue
|
|
187
|
+
out.append((m.group("name"), brace + 1, end, m.start("header"), m.start("name"), m.group("storage"), m.group("params")))
|
|
188
|
+
return out
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _matching_brace(text: str, start: int) -> int | None:
|
|
192
|
+
depth = 0
|
|
193
|
+
for i in range(start, len(text)):
|
|
194
|
+
c = text[i]
|
|
195
|
+
if c == "{":
|
|
196
|
+
depth += 1
|
|
197
|
+
elif c == "}":
|
|
198
|
+
depth -= 1
|
|
199
|
+
if depth == 0:
|
|
200
|
+
return i
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _balanced_call_end(text: str, open_paren: int, body_end: int) -> int | None:
|
|
205
|
+
depth = 0
|
|
206
|
+
limit = min(len(text), body_end + 1)
|
|
207
|
+
for i in range(open_paren, limit):
|
|
208
|
+
c = text[i]
|
|
209
|
+
if c == "(":
|
|
210
|
+
depth += 1
|
|
211
|
+
elif c == ")":
|
|
212
|
+
depth -= 1
|
|
213
|
+
if depth == 0:
|
|
214
|
+
return i
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _line_starts(text: str) -> list[int]:
|
|
219
|
+
starts = [0]
|
|
220
|
+
starts.extend(i + 1 for i, c in enumerate(text) if c == "\n")
|
|
221
|
+
return starts
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _line_col(pos: int, line_starts: tuple[int, ...] | list[int]) -> tuple[int, int]:
|
|
225
|
+
idx = bisect_right(line_starts, pos) - 1
|
|
226
|
+
return idx + 1, pos - line_starts[idx] + 1
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@lru_cache(maxsize=4096)
|
|
230
|
+
def _parameter_names(params_text: str) -> tuple[str, ...]:
|
|
231
|
+
text = params_text.strip()
|
|
232
|
+
if not text or text == "void":
|
|
233
|
+
return ()
|
|
234
|
+
names: list[str] = []
|
|
235
|
+
for part in text.split(","):
|
|
236
|
+
part = part.strip()
|
|
237
|
+
if not part or part == "...":
|
|
238
|
+
continue
|
|
239
|
+
ids = re.findall(r"[A-Za-z_]\w*", part)
|
|
240
|
+
if ids:
|
|
241
|
+
names.append(sys.intern(ids[-1]))
|
|
242
|
+
return tuple(names)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _function_id(tu: TranslationUnit, name: str, line: int, col: int, storage: str | None, qual_type: str | None) -> str:
|
|
246
|
+
return f"{tu.source.project_relative_path}:{line}:{col}:{storage or 'external'}:{name}:{qual_type or '?'}"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _mask_comments_and_literals(text: str) -> str:
|
|
250
|
+
out = list(text)
|
|
251
|
+
i = 0
|
|
252
|
+
n = len(out)
|
|
253
|
+
while i < n:
|
|
254
|
+
if i + 1 < n and text[i:i+2] == "//":
|
|
255
|
+
j = text.find("\n", i + 2)
|
|
256
|
+
j = n if j < 0 else j
|
|
257
|
+
for k in range(i, j):
|
|
258
|
+
out[k] = " "
|
|
259
|
+
i = j
|
|
260
|
+
continue
|
|
261
|
+
if i + 1 < n and text[i:i+2] == "/*":
|
|
262
|
+
j = text.find("*/", i + 2)
|
|
263
|
+
j = n - 2 if j < 0 else j
|
|
264
|
+
for k in range(i, min(n, j + 2)):
|
|
265
|
+
if out[k] != "\n":
|
|
266
|
+
out[k] = " "
|
|
267
|
+
i = min(n, j + 2)
|
|
268
|
+
continue
|
|
269
|
+
if text[i] in {'"', "'"}:
|
|
270
|
+
quote = text[i]
|
|
271
|
+
j = i + 1
|
|
272
|
+
while j < n:
|
|
273
|
+
if text[j] == "\\":
|
|
274
|
+
j += 2
|
|
275
|
+
continue
|
|
276
|
+
if text[j] == quote:
|
|
277
|
+
j += 1
|
|
278
|
+
break
|
|
279
|
+
j += 1
|
|
280
|
+
for k in range(i, min(n, j)):
|
|
281
|
+
if out[k] != "\n":
|
|
282
|
+
out[k] = " "
|
|
283
|
+
i = j
|
|
284
|
+
continue
|
|
285
|
+
i += 1
|
|
286
|
+
return "".join(out)
|