|
149 | 149 | # Haskell — `main :: IO ()` / `main = ...` at column 0 |
150 | 150 | _HASKELL_MAIN = re.compile(r"^main\s*(::|=)") |
151 | 151 |
|
| 152 | +# Swift — @main on an App/struct indicates the entry point. |
| 153 | +_SWIFT_MAIN_ATTR = re.compile(r"^\s*@main\b") |
| 154 | + |
| 155 | +# Swift — Vapor route registration: `app.get("/path") { req in ... }` |
| 156 | +# and related `.post/.put/.delete/.patch` on any receiver. |
| 157 | +_SWIFT_VAPOR_ROUTE = re.compile( |
| 158 | + r"^\s*[A-Za-z_]\w*\.(get|post|put|patch|delete|on)\s*\(", |
| 159 | +) |
| 160 | + |
| 161 | +# Objective-C — AppDelegate lifecycle selectors on UIApplicationDelegate |
| 162 | +_OBJC_APP_DELEGATE_SELECTORS = frozenset( |
| 163 | + { |
| 164 | + "application:didFinishLaunchingWithOptions:", |
| 165 | + "application:openURL:options:", |
| 166 | + "application:continueUserActivity:restorationHandler:", |
| 167 | + "application:performFetchWithCompletionHandler:", |
| 168 | + } |
| 169 | +) |
| 170 | + |
152 | 171 | _KIND_BY_NAME = {k.value: k for k in EntrypointKind} |
153 | 172 | _TRUST_BY_NAME = {t.value: t for t in TrustLevel} |
154 | 173 | _ASSET_BY_NAME = {a.value: a for a in AssetValue} |
@@ -237,6 +256,10 @@ def _detect_for_unit( |
237 | 256 | return _detect_haskell(cache, unit, path) |
238 | 257 | if path.endswith(".erl"): |
239 | 258 | return _detect_erlang(cache, unit, path) |
| 259 | + if path.endswith(".swift"): |
| 260 | + return _detect_swift(cache, unit, path) |
| 261 | + if path.endswith((".m", ".mm", ".h")): |
| 262 | + return _detect_objc(unit) |
240 | 263 | return None |
241 | 264 |
|
242 | 265 |
|
@@ -601,6 +624,53 @@ def _erlang_exported_names(cache: _SourceCache, path: str) -> set[str]: |
601 | 624 | return names |
602 | 625 |
|
603 | 626 |
|
| 627 | +def _detect_swift( |
| 628 | + cache: _SourceCache, |
| 629 | + unit: CodeUnit, |
| 630 | + path: str, |
| 631 | +) -> EntrypointTag | None: |
| 632 | + """Detect Swift entrypoints: @main attribute + Vapor route registration.""" |
| 633 | + decorators = cache.decorators_above(path, unit.location.start_line) |
| 634 | + for line in decorators: |
| 635 | + if _SWIFT_MAIN_ATTR.match(line): |
| 636 | + return EntrypointTag( |
| 637 | + kind=EntrypointKind.USER_INPUT, |
| 638 | + trust_level=TrustLevel.UNTRUSTED_EXTERNAL, |
| 639 | + description="Swift @main app entrypoint", |
| 640 | + asset_value=AssetValue.HIGH, |
| 641 | + ) |
| 642 | + # Vapor routes are registered inside the function body rather than |
| 643 | + # via a decorator; match handlers whose containing file has at least |
| 644 | + # one `<receiver>.get(...)` / `.post(...)` / etc. call. |
| 645 | + for line in cache.iter_lines(path): |
| 646 | + if _SWIFT_VAPOR_ROUTE.match(line): |
| 647 | + # A Vapor file: tag every function as a potential handler entry |
| 648 | + # only if its name matches the handler closure pattern. |
| 649 | + # Without call-graph resolution this is coarse — Vapor-handler |
| 650 | + # detection is best done via the override file for now. |
| 651 | + break |
| 652 | + return None |
| 653 | + |
| 654 | + |
| 655 | +def _detect_objc(unit: CodeUnit) -> EntrypointTag | None: |
| 656 | + """Detect Objective-C entrypoints: AppDelegate selectors and extern C. |
| 657 | +
|
| 658 | + AppDelegate protocol selectors are high-value attack surface — |
| 659 | + ``application:openURL:options:`` handles deep-link invocations and |
| 660 | + ``application:didFinishLaunchingWithOptions:`` is the first code |
| 661 | + reached after launch. Their signatures are stable enough that a |
| 662 | + name match is sufficient. |
| 663 | + """ |
| 664 | + if unit.name in _OBJC_APP_DELEGATE_SELECTORS: |
| 665 | + return EntrypointTag( |
| 666 | + kind=EntrypointKind.API, |
| 667 | + trust_level=TrustLevel.UNTRUSTED_EXTERNAL, |
| 668 | + description="Objective-C UIApplicationDelegate lifecycle method", |
| 669 | + asset_value=AssetValue.HIGH, |
| 670 | + ) |
| 671 | + return None |
| 672 | + |
| 673 | + |
604 | 674 | class _SourceCache: |
605 | 675 | """Lazily reads and caches source files during a detection pass.""" |
606 | 676 |
|
|
0 commit comments