mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 22:48:57 +02:00
ios-qa: guard DebugBridgeTouch.m on DEBUG, not just TARGET_OS_IOS
DebugBridgeTouch.m and its header both promise the code is DEBUG-only and
never shipped:
"Uses these private UIKit selectors (DEBUG-only; never shipped to App Store)"
"DEBUG-only — never link in Release."
Nothing enforced it. The only guard was `#if TARGET_OS_IOS`, so a Release build
for iOS compiled the entire implementation in, private API and all.
Measured on a real app (an iOS Release build, `nm -j` on the app binary):
DebugBridge symbols 15
IOHIDEventCreateDigitizer 2
AXSSetAutomationEnabled 1 symbol, 2 strings
IOKit.framework 4 strings
including +[DebugBridgeTouch sendTapAtPoint:inWindow:] and
_OBJC_CLASS_$_DebugBridgeTouch. That is a Guideline 2.5.1 private-API exposure
in a shippable binary, and it fails Package.swift's own stated CI invariant:
nm -j build/Release/<binary> | grep -q DebugBridge && exit 1
WHY THE EXISTING GUARD DOES NOT COVER THIS
Package.swift documents the protection as `.when(configuration: .debug)` on the
consuming target's dependency. That works for SwiftPM consumers. It cannot be
expressed by an app that integrates DebugBridge as a local package inside an
.xcodeproj: Xcode's Filters column under Frameworks, Libraries, and Embedded
Content offers platform conditions only — iOS, macOS, visionOS — never build
configuration. So for xcodeproj consumers the documented guard silently does
nothing, which is precisely the case that was measured.
The Swift targets were already safe: all four .swift files are `#if DEBUG`
guarded and Package.swift defines DEBUG for them via swiftSettings. Only the
Objective-C target, the one that actually links private API, was unguarded.
THE FIX
1. DebugBridgeTouch.m.template now branches `#if !defined(DEBUG)` first and
emits nothing at all in Release, falling through to the existing iOS and
non-iOS branches only in Debug.
2. Package.swift.template declares DEBUG explicitly for the ObjC target:
cSettings: [.define("DEBUG", .when(configuration: .debug))]
The two Swift targets already did this. Relying on SwiftPM's implicit DEBUG
for C-family targets is not worth betting a private-API exposure on.
VERIFIED, by compiling the generated file for iOS both ways:
xcrun -sdk iphoneos clang -c DebugBridgeTouch.m -arch arm64 ...
Release (no -DDEBUG) 0 DebugBridge symbols, 0 private-API symbols, 448 B
Debug (-DDEBUG=1) 7 DebugBridge symbols, 6 private-API symbols, 13104 B
The harness is unchanged in Debug. Release now emits an empty translation unit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4d0e7b7c2a
commit
ecfd9af9d6
@@ -20,7 +20,29 @@
|
||||
#import "DebugBridgeTouch.h"
|
||||
#import <TargetConditionals.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#if !defined(DEBUG)
|
||||
|
||||
// RELEASE BUILD: this file deliberately emits NOTHING — no class, no symbols, no
|
||||
// private-API references. The header still declares the interface, which is inert
|
||||
// on its own; every consumer of DebugBridgeTouch is itself `#if DEBUG` guarded, so
|
||||
// nothing references it here and nothing fails to link.
|
||||
//
|
||||
// This guard was added because the comments at the top of this file and in the
|
||||
// header both PROMISED "DEBUG-only; never shipped to App Store" and "never link in
|
||||
// Release" — and nothing enforced it. Only `#if TARGET_OS_IOS` was tested, so a
|
||||
// Release build for iOS compiled the whole implementation in. Measured on a real
|
||||
// app (Seize Day, 2026-08-15), `nm -j` on a Release binary returned 15 DebugBridge
|
||||
// symbols including +[DebugBridgeTouch sendTapAtPoint:inWindow:], plus
|
||||
// IOHIDEventCreateDigitizer, AXSSetAutomationEnabled and IOKit.framework strings.
|
||||
// That is a Guideline 2.5.1 private-API exposure in a shippable binary.
|
||||
//
|
||||
// Package.swift's stated guard — `.when(configuration: .debug)` on the consuming
|
||||
// target's dependency — only exists for SwiftPM consumers. An app integrating this
|
||||
// as a local package inside an .xcodeproj cannot express it: Xcode's Filters column
|
||||
// in Frameworks/Libraries offers platform conditions only, never configuration. So
|
||||
// the guard has to live here, in the source, where it holds for every consumer.
|
||||
|
||||
#elif TARGET_OS_IOS
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <objc/runtime.h>
|
||||
@@ -340,4 +362,4 @@ static id DBT_HitTestView(UIWindow *window, CGPoint point) {
|
||||
}
|
||||
@end
|
||||
|
||||
#endif // TARGET_OS_IOS
|
||||
#endif // !DEBUG / TARGET_OS_IOS
|
||||
|
||||
@@ -12,9 +12,22 @@
|
||||
// MutationBridge implementations. Depends on the other
|
||||
// two.
|
||||
//
|
||||
// The structural Release-build guard is the `.when(configuration: .debug)`
|
||||
// conditional on every consuming target's dependency. SwiftPM refuses to link
|
||||
// DebugBridge* in Release config.
|
||||
// Release-build guards, in order of reliability:
|
||||
//
|
||||
// 1. SOURCE. Every file is `#if DEBUG` guarded, including DebugBridgeTouch.m,
|
||||
// which holds regardless of how the package is integrated. This is the one
|
||||
// that actually protects you.
|
||||
// 2. `.when(configuration: .debug)` on the consuming target's dependency. This
|
||||
// works for SwiftPM consumers ONLY. An app integrating this as a local
|
||||
// package inside an .xcodeproj CANNOT express it — Xcode's Filters column in
|
||||
// Frameworks/Libraries offers platform conditions, never configuration — so
|
||||
// do not rely on it alone.
|
||||
//
|
||||
// Guard 1 was added 2026-08-15 after a measured failure: DebugBridgeTouch.m was
|
||||
// guarded only by `#if TARGET_OS_IOS`, so a Release iOS build of a real app linked
|
||||
// it, and `nm -j` returned 15 DebugBridge symbols plus IOHIDEventCreateDigitizer,
|
||||
// AXSSetAutomationEnabled and IOKit.framework strings. Guideline 2.5.1 exposure in
|
||||
// a shippable binary, with guard 2 present and doing nothing.
|
||||
//
|
||||
// CI invariant: `swift build -c release` + `nm -j build/Release/<binary>
|
||||
// | grep -q DebugBridge && exit 1`.
|
||||
@@ -43,6 +56,13 @@ let package = Package(
|
||||
dependencies: [],
|
||||
path: "Sources/DebugBridgeTouch",
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [
|
||||
// Explicit, because the source guard depends on it. SwiftPM's
|
||||
// implicit DEBUG for C-family targets is not something to bet a
|
||||
// private-API exposure on — the two Swift targets already declare
|
||||
// it, and this target is the one that actually links private API.
|
||||
.define("DEBUG", .when(configuration: .debug)),
|
||||
],
|
||||
linkerSettings: [
|
||||
// IOKit is loaded dynamically via dlopen at runtime (it's a
|
||||
// private framework on iOS and can't be linked statically).
|
||||
|
||||
Reference in New Issue
Block a user