adl-toolkit 0.0.0-stage → 0.1.1
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.
- package/INSTALL_TOOLKIT.md +163 -0
- package/README.md +269 -2
- package/adl_drift/__init__.py +6 -0
- package/adl_drift/cli/__init__.py +1 -0
- package/adl_drift/cli/commands.py +993 -0
- package/adl_drift/cli/main.py +744 -0
- package/adl_drift/cortex/__init__.py +1 -0
- package/adl_drift/cortex/embeddings.py +96 -0
- package/adl_drift/cortex/memory.py +1749 -0
- package/adl_drift/cortex/patterns.py +89 -0
- package/adl_drift/cortex/telemetry.py +192 -0
- package/adl_drift/detectors/__init__.py +1 -0
- package/adl_drift/detectors/async_patterns.py +115 -0
- package/adl_drift/detectors/base.py +121 -0
- package/adl_drift/detectors/config.py +122 -0
- package/adl_drift/detectors/data_access.py +128 -0
- package/adl_drift/detectors/decorators.py +122 -0
- package/adl_drift/detectors/dynamic_patterns.py +260 -0
- package/adl_drift/detectors/errors.py +134 -0
- package/adl_drift/detectors/imports.py +132 -0
- package/adl_drift/detectors/logging_patterns.py +108 -0
- package/adl_drift/detectors/routes.py +257 -0
- package/adl_drift/detectors/security.py +236 -0
- package/adl_drift/detectors/taint.py +406 -0
- package/adl_drift/detectors/tests.py +95 -0
- package/adl_drift/detectors/type_conventions.py +81 -0
- package/adl_drift/graph/__init__.py +1 -0
- package/adl_drift/graph/builder.py +2670 -0
- package/adl_drift/graph/cfg.py +461 -0
- package/adl_drift/graph/cluster.py +40 -0
- package/adl_drift/graph/edges.py +86 -0
- package/adl_drift/graph/node.py +90 -0
- package/adl_drift/harness/__init__.py +10 -0
- package/adl_drift/harness/setup.py +832 -0
- package/adl_drift/harness/skills/drift-antigravity.md +19 -0
- package/adl_drift/harness/skills/drift-generic.md +19 -0
- package/adl_drift/harness/skills/drift.md +25 -0
- package/adl_drift/harness/watch.py +84 -0
- package/adl_drift/lang/__init__.py +8 -0
- package/adl_drift/lang/base.py +464 -0
- package/adl_drift/lang/java/__init__.py +6 -0
- package/adl_drift/lang/java/backend.py +97 -0
- package/adl_drift/lang/java/cfg.py +1424 -0
- package/adl_drift/lang/java/config.py +707 -0
- package/adl_drift/lang/java/detectors/__init__.py +29 -0
- package/adl_drift/lang/java/detectors/additional.py +445 -0
- package/adl_drift/lang/java/detectors/base.py +154 -0
- package/adl_drift/lang/java/detectors/data_access.py +148 -0
- package/adl_drift/lang/java/detectors/imports.py +53 -0
- package/adl_drift/lang/java/detectors/routes.py +159 -0
- package/adl_drift/lang/java/detectors/security.py +386 -0
- package/adl_drift/lang/java/detectors/tests.py +74 -0
- package/adl_drift/lang/java/extractor.py +582 -0
- package/adl_drift/lang/java/ids.py +99 -0
- package/adl_drift/lang/java/parser.py +49 -0
- package/adl_drift/lang/java/resolution/__init__.py +1 -0
- package/adl_drift/lang/java/resolution/constants.py +1619 -0
- package/adl_drift/lang/java/resolution/constructors.py +278 -0
- package/adl_drift/lang/java/resolution/context.py +305 -0
- package/adl_drift/lang/java/resolution/dispatch.py +540 -0
- package/adl_drift/lang/java/resolution/functional.py +1318 -0
- package/adl_drift/lang/java/resolution/generated.py +95 -0
- package/adl_drift/lang/java/resolution/model.py +153 -0
- package/adl_drift/lang/java/resolution/orchestration.py +866 -0
- package/adl_drift/lang/java/resolution/overloads.py +1275 -0
- package/adl_drift/lang/java/resolution/scopes.py +1111 -0
- package/adl_drift/lang/java/resolution/types.py +2947 -0
- package/adl_drift/lang/java/resolver.py +1401 -0
- package/adl_drift/lang/java/scopes.py +69 -0
- package/adl_drift/lang/java/taint.py +500 -0
- package/adl_drift/lang/java/type_provenance.py +342 -0
- package/adl_drift/lang/python/__init__.py +8 -0
- package/adl_drift/lang/python/backend.py +94 -0
- package/adl_drift/lang/python/cfg.py +537 -0
- package/adl_drift/lang/python/extractor.py +454 -0
- package/adl_drift/lang/python/parser.py +41 -0
- package/adl_drift/lang/python/resolver.py +1789 -0
- package/adl_drift/lang/python/taint.py +365 -0
- package/adl_drift/lang/registry.py +117 -0
- package/adl_drift/parser/__init__.py +1 -0
- package/adl_drift/parser/cache.py +38 -0
- package/adl_drift/parser/core.py +33 -0
- package/adl_drift/parser/extractor.py +17 -0
- package/adl_drift/parser/literal_eval.py +326 -0
- package/adl_drift/scanner/__init__.py +1 -0
- package/adl_drift/scanner/discovery.py +338 -0
- package/adl_drift/scanner/incremental.py +261 -0
- package/adl_drift/scanner/parallel.py +224 -0
- package/adl_drift/server/__init__.py +1 -0
- package/adl_drift/server/handlers.py +2849 -0
- package/adl_drift/server/mcp_server.py +127 -0
- package/adl_drift/server/mcp_stdio.py +335 -0
- package/adl_drift/server/protocol.py +71 -0
- package/adl_drift/server/static/index.html +696 -0
- package/adl_drift/server/web.py +350 -0
- package/adl_drift/utils/__init__.py +1 -0
- package/adl_drift/utils/config.py +77 -0
- package/adl_drift/utils/fswalk.py +52 -0
- package/adl_drift/utils/types.py +345 -0
- package/bin/adl-drift.mjs +35 -0
- package/bin/adl-toolkit.mjs +488 -0
- package/package.json +29 -4
- package/pyproject.toml +49 -0
- package/scripts/install_toolkit.py +3584 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Synthetic Java members required by language and annotation semantics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
7
|
+
from adl_drift.graph.edges import EdgeType, GraphEdge
|
|
8
|
+
from adl_drift.graph.node import GraphNode, NodeType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GeneratedMembersMixin:
|
|
12
|
+
def _ensure_generated_member_stubs(self) -> Dict[str, List[str]]:
|
|
13
|
+
"""Materialize mandatory enum methods and record accessors.
|
|
14
|
+
|
|
15
|
+
The extractor correctly records only source declarations, while enum
|
|
16
|
+
``values``/``valueOf`` and record component accessors are generated by
|
|
17
|
+
the compiler and therefore have no CST method declaration. Resolution
|
|
18
|
+
still needs real FUNCTION targets, so create only these language-
|
|
19
|
+
mandated members, with deterministic ids and normal CONTAINS edges.
|
|
20
|
+
"""
|
|
21
|
+
created: Dict[str, List[str]] = {}
|
|
22
|
+
type_nodes = [
|
|
23
|
+
node for node in list(self.builder._nodes.values())
|
|
24
|
+
if node.language == "java"
|
|
25
|
+
and node.node_type == NodeType.CLASS
|
|
26
|
+
and node.metadata.get("java_type_kind") in {"enum", "record"}
|
|
27
|
+
]
|
|
28
|
+
for type_node in type_nodes:
|
|
29
|
+
kind = type_node.metadata.get("java_type_kind")
|
|
30
|
+
# Keep the enclosing type path for nested enums. Returning only
|
|
31
|
+
# `Status[]` from `EventPublication.Status.values()` loses the
|
|
32
|
+
# owner and can become ambiguous (or unresolvable) at callers in
|
|
33
|
+
# another nested class. The portion after the module separator is
|
|
34
|
+
# the source qualname accepted by `_resolve_declared_type`.
|
|
35
|
+
enum_type = type_node.id.rsplit(":", 1)[-1]
|
|
36
|
+
methods = (
|
|
37
|
+
[("values", [], f"{enum_type}[]", True),
|
|
38
|
+
("valueOf", ["String"], enum_type, True)]
|
|
39
|
+
if kind == "enum"
|
|
40
|
+
else [
|
|
41
|
+
(name, [], str(component_type), False)
|
|
42
|
+
for name, component_type in dict(
|
|
43
|
+
type_node.metadata.get("record_components", {})
|
|
44
|
+
).items()
|
|
45
|
+
]
|
|
46
|
+
)
|
|
47
|
+
for name, params, return_type, is_static in methods:
|
|
48
|
+
signature_key = f"({','.join(params)})"
|
|
49
|
+
stub_id = f"{type_node.id}.{name}{signature_key}"
|
|
50
|
+
self._add_generated_member_stub(
|
|
51
|
+
type_node, stub_id, name, params, return_type, is_static,
|
|
52
|
+
)
|
|
53
|
+
created[stub_id] = params
|
|
54
|
+
return created
|
|
55
|
+
|
|
56
|
+
def _add_generated_member_stub(
|
|
57
|
+
self, type_node: GraphNode, stub_id: str, name: str,
|
|
58
|
+
params: List[str], return_type: str, is_static: bool,
|
|
59
|
+
) -> None:
|
|
60
|
+
if stub_id not in self.builder._nodes:
|
|
61
|
+
signature_key = f"({','.join(params)})"
|
|
62
|
+
marker = (
|
|
63
|
+
"java_synthetic_enum_method"
|
|
64
|
+
if type_node.metadata.get("java_type_kind") == "enum"
|
|
65
|
+
else "java_synthetic_record_accessor"
|
|
66
|
+
)
|
|
67
|
+
self.builder._add_node(GraphNode(
|
|
68
|
+
id=stub_id,
|
|
69
|
+
name=name,
|
|
70
|
+
node_type=NodeType.FUNCTION,
|
|
71
|
+
module=type_node.module,
|
|
72
|
+
file_path=type_node.file_path,
|
|
73
|
+
line=type_node.line,
|
|
74
|
+
column=type_node.column,
|
|
75
|
+
signature=f"{return_type} {name}{signature_key}",
|
|
76
|
+
contained_in=type_node.name,
|
|
77
|
+
language="java",
|
|
78
|
+
metadata={
|
|
79
|
+
"java_is_static": is_static,
|
|
80
|
+
"java_is_abstract": False,
|
|
81
|
+
"java_return_type": return_type,
|
|
82
|
+
marker: True,
|
|
83
|
+
},
|
|
84
|
+
))
|
|
85
|
+
self.builder._add_edge(GraphEdge(
|
|
86
|
+
source=type_node.id,
|
|
87
|
+
target=stub_id,
|
|
88
|
+
edge_type=EdgeType.CONTAINS,
|
|
89
|
+
file_path=type_node.file_path,
|
|
90
|
+
line=type_node.line,
|
|
91
|
+
column=type_node.column,
|
|
92
|
+
strategy="java_resolver",
|
|
93
|
+
))
|
|
94
|
+
|
|
95
|
+
# ------------------------------------------------------------------ CST utilities
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Shared data models for Java call resolution.
|
|
2
|
+
|
|
3
|
+
These types deliberately contain no graph traversal or resolution policy. Keeping
|
|
4
|
+
them in a dependency-leaf module lets the resolver's specialized components share
|
|
5
|
+
context without importing the orchestration class.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
from typing import Dict, FrozenSet, List, NamedTuple, Optional, Tuple
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SAMDescriptor(NamedTuple):
|
|
15
|
+
params: List[str]
|
|
16
|
+
return_type: Optional[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BeanCandidate(NamedTuple):
|
|
20
|
+
"""One concrete Spring bean that can satisfy an injected interface."""
|
|
21
|
+
|
|
22
|
+
type_id: str
|
|
23
|
+
bean_name: Optional[str]
|
|
24
|
+
qualifiers: FrozenSet[str]
|
|
25
|
+
primary: bool
|
|
26
|
+
source: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InjectedFieldMetadata(NamedTuple):
|
|
30
|
+
"""DI metadata inferred for a field from constructor or setter injection."""
|
|
31
|
+
|
|
32
|
+
qualifier: Optional[str]
|
|
33
|
+
source: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DINarrowResult(NamedTuple):
|
|
37
|
+
"""Result of Spring DI narrowing for an injected interface receiver."""
|
|
38
|
+
|
|
39
|
+
target: Optional[str]
|
|
40
|
+
confidence: float
|
|
41
|
+
strategy: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ImportContext(NamedTuple):
|
|
45
|
+
"""One source file's Java name-resolution context.
|
|
46
|
+
|
|
47
|
+
`enclosing_class_id` (optional, defaults to `None` for every pre-existing
|
|
48
|
+
construction site) is the CLASS node id whose body this context resolves
|
|
49
|
+
names *for* — the caller's own class for a call-site's argument context,
|
|
50
|
+
or a candidate method's declaring class for its parameter context. It
|
|
51
|
+
lets `_resolve_declared_type` look up a bare type name inherited from a
|
|
52
|
+
superclass/superinterface (JLS 6.4.1), which needs the referencing
|
|
53
|
+
class's own supertype chain, not just its module/imports."""
|
|
54
|
+
|
|
55
|
+
module: str
|
|
56
|
+
import_map: Dict[str, str]
|
|
57
|
+
wildcard_packages: List[str]
|
|
58
|
+
enclosing_class_id: Optional[str] = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class LocalScopes:
|
|
62
|
+
"""Lexically-scoped local-variable and allocation types for one body."""
|
|
63
|
+
|
|
64
|
+
__slots__ = ("_by_name", "_allocation_by_name", "_declared_by_name")
|
|
65
|
+
|
|
66
|
+
def __init__(self) -> None:
|
|
67
|
+
self._by_name: Dict[str, List[Tuple[str, int, int]]] = defaultdict(list)
|
|
68
|
+
self._allocation_by_name: Dict[
|
|
69
|
+
str, List[Tuple[str, int, int]]
|
|
70
|
+
] = defaultdict(list)
|
|
71
|
+
self._declared_by_name: Dict[str, List[Tuple[int, int]]] = defaultdict(list)
|
|
72
|
+
|
|
73
|
+
def add(self, name: str, type_text: str, start: int, end: int) -> None:
|
|
74
|
+
# Multiple inference passes may describe the same declaration range:
|
|
75
|
+
# the early fluent-lambda pass can seed a placeholder type variable
|
|
76
|
+
# (`E`), then SAM target typing refines it to `SomeEvent`. The later,
|
|
77
|
+
# more contextual fact must replace—not sit behind—the placeholder.
|
|
78
|
+
self._by_name[name] = [
|
|
79
|
+
entry for entry in self._by_name[name]
|
|
80
|
+
if entry[1] != start or entry[2] != end
|
|
81
|
+
]
|
|
82
|
+
self._by_name[name].append((type_text, start, end))
|
|
83
|
+
# A `var`'s first pass (`_collect_local_scopes`) may have recorded
|
|
84
|
+
# this exact range as "declared, type unknown" before a later,
|
|
85
|
+
# context-aware pass (`_collect_var_method_call_scopes`) resolved its
|
|
86
|
+
# real type — that placeholder must not outrank the now-known type
|
|
87
|
+
# in `type_at` just because it shares the same range.
|
|
88
|
+
if name in self._declared_by_name:
|
|
89
|
+
self._declared_by_name[name] = [
|
|
90
|
+
entry for entry in self._declared_by_name[name]
|
|
91
|
+
if entry[0] != start or entry[1] != end
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
def type_at(self, name: str, offset: int) -> Optional[str]:
|
|
95
|
+
best_type: Optional[str] = None
|
|
96
|
+
best_start = -1
|
|
97
|
+
for type_text, start, end in self._by_name.get(name, ()):
|
|
98
|
+
# Equal starts arise when a later contextual pass refines the
|
|
99
|
+
# same lambda parameter with a slightly different end boundary.
|
|
100
|
+
# Iteration order is inference order, so the later fact wins.
|
|
101
|
+
if start <= offset <= end and start >= best_start:
|
|
102
|
+
best_type, best_start = type_text, start
|
|
103
|
+
for start, end in self._declared_by_name.get(name, ()):
|
|
104
|
+
# A declared-but-type-unknown entry (a lambda parameter whose
|
|
105
|
+
# SAM type couldn't be inferred, or a `var` local whose
|
|
106
|
+
# initializer type couldn't be resolved) still shadows an outer
|
|
107
|
+
# typed declaration of the same name when it is the innermost
|
|
108
|
+
# match — the outer type must not leak into a nested scope it
|
|
109
|
+
# isn't actually visible from (P4.4 shadow-detection fix).
|
|
110
|
+
if start <= offset <= end and start >= best_start:
|
|
111
|
+
best_type, best_start = None, start
|
|
112
|
+
return best_type
|
|
113
|
+
|
|
114
|
+
def add_declared(self, name: str, start: int, end: int) -> None:
|
|
115
|
+
"""Record that `name` is a local whose type could not be resolved
|
|
116
|
+
(e.g. a `var` initialized from an unresolvable expression). `add()`
|
|
117
|
+
is skipped for these — there is no type to record — but callers that
|
|
118
|
+
only need "is this name shadowed by *some* local declaration here",
|
|
119
|
+
not its type, must still see it (P4.4 shadow-detection fix)."""
|
|
120
|
+
self._declared_by_name[name].append((start, end))
|
|
121
|
+
|
|
122
|
+
def declared_at(self, name: str, offset: int) -> bool:
|
|
123
|
+
return any(
|
|
124
|
+
start <= offset <= end
|
|
125
|
+
for start, end in self._declared_by_name.get(name, ())
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def add_allocation(self, name: str, type_text: str, start: int, end: int) -> None:
|
|
129
|
+
self._allocation_by_name[name].append((type_text, start, end))
|
|
130
|
+
|
|
131
|
+
def allocation_type_at(self, name: str, offset: int) -> Optional[str]:
|
|
132
|
+
best_type: Optional[str] = None
|
|
133
|
+
best_start = -1
|
|
134
|
+
for type_text, start, end in self._allocation_by_name.get(name, ()):
|
|
135
|
+
if start <= offset <= end and start > best_start:
|
|
136
|
+
best_type, best_start = type_text, start
|
|
137
|
+
return best_type
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class MethodFacts(NamedTuple):
|
|
141
|
+
"""Immutable CST facts shared by one Java method body in one resolve run.
|
|
142
|
+
|
|
143
|
+
Call sites, method references, and scope declarations all used to require
|
|
144
|
+
separate body DFS walks. This object intentionally carries syntax facts
|
|
145
|
+
only: scope/type inference and target resolution keep their existing
|
|
146
|
+
semantics and mutable per-caller state.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
call_sites: Tuple[Tuple[object, str], ...]
|
|
150
|
+
method_references: Tuple[object, ...]
|
|
151
|
+
assignment_expressions: Tuple[object, ...]
|
|
152
|
+
local_scope_nodes: Tuple[object, ...]
|
|
153
|
+
lambda_expressions: Tuple[object, ...]
|