Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: stop crashing from objc_getClassList #7187

Merged
merged 13 commits into from
Jan 15, 2024
12 changes: 12 additions & 0 deletions cli/src/ios/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
import type { Plugin } from '../plugin';
import { copy as copyTask } from '../tasks/copy';
import { convertToUnixPath } from '../util/fs';
import { getPluginFiles, findPluginClasses, writePluginJSON } from '../util/iosplugin'
import { resolveNode } from '../util/node';
import { checkPackageManager, generatePackageFile } from '../util/spm';
import { runCommand, isInstalled } from '../util/subprocess';
Expand All @@ -56,6 +57,8 @@ export async function updateIOS(
await updateIOSCocoaPods(config, plugins, deployment);
}

generateIOSPackageJSON(config, plugins)

printPlugins(capacitorPlugins, 'ios');
}

Expand Down Expand Up @@ -173,6 +176,15 @@ end`,
}
}

async function generateIOSPackageJSON(config: Config, plugins: Plugin[]): Promise<void> {
const outputDir = config.ios.nativeTargetDirAbs
const outputFile = join(outputDir, 'package.ios.json')

const fileList = await getPluginFiles(plugins)
const classList = await findPluginClasses(fileList)
writePluginJSON(outputFile, classList)
}

async function getRelativeCapacitoriOSPath(config: Config) {
const capacitoriOSPath = resolveNode(
config.app.rootDir,
Expand Down
57 changes: 57 additions & 0 deletions cli/src/util/iosplugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { ReaddirPOptions } from '@ionic/utils-fs';
import { readFileSync, writeFileSync, readdirp } from '@ionic/utils-fs';
import { resolve } from 'path';

import type { Plugin } from '../plugin';

export async function getPluginFiles(plugins: Plugin[]): Promise<string[]> {
let filenameList: string[] = []

const options: ReaddirPOptions = {
filter: (item) => {
if (item.path.endsWith('.swift') || item.path.endsWith('.m')) {
return true
} else {
return false
}
}
}

for (const plugin of plugins) {
if (typeof plugin.ios?.name !== 'undefined') {
const pluginPath = resolve(plugin.rootPath, plugin.ios?.path)
const filenames = await readdirp(pluginPath, options)
filenameList = filenameList.concat(filenames)
}
}

return filenameList
}

export async function findPluginClasses(files: string[]): Promise<string[]> {
const classList: string[] = []

for (const file of files) {
const fileData = readFileSync(file, 'utf-8')
const swiftPluginRegex = RegExp(/@objc\(([A-Za-z0-9_-]+)\)/)
const objcPluginRegex = RegExp(/CAP_PLUGIN\(([A-Za-z0-9_-]+)/)

const swiftMatches = swiftPluginRegex.exec(fileData)
if (swiftMatches?.[1] != null) {
classList.push(swiftMatches[1])
}

const objcMatches = objcPluginRegex.exec(fileData)
if (objcMatches?.[1] != null) {
classList.push(objcMatches[1])
}
}

return classList
}

export async function writePluginJSON(fileName: string, classList: string[]): Promise<void> {
const outputObj = { 'pluginList': classList }
const outputJSON = JSON.stringify(outputObj, null, 2)
writeFileSync(fileName, outputJSON)
}
81 changes: 61 additions & 20 deletions ios/Capacitor/Capacitor/CapacitorBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ import Cordova

internal typealias CapacitorPlugin = CAPPlugin & CAPBridgedPlugin

#warning("remove this before merging! To test for crash")
markemer marked this conversation as resolved.
Show resolved Hide resolved
#if canImport(RoomPlan)

import RoomPlan

@available(iOS 16, *)
class Foo: NSObject {
var bar: CapturedRoom?
}

#endif

struct RegistrationList: Codable {
let pluginList: [String]
}

/**
An internal class adopting a public protocol means that we have a lot of `public` methods
but that is by design not a mistake. And since the bridge is the center of the whole project
Expand Down Expand Up @@ -277,30 +293,28 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
Register all plugins that have been declared
*/
func registerPlugins() {
if autoRegisterPlugins {
let classCount = objc_getClassList(nil, 0)
let classes = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(classCount))

let releasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classes)
let numClasses: Int32 = objc_getClassList(releasingClasses, classCount)
var pluginList: [AnyClass] = [CAPHttpPlugin.self, CAPConsolePlugin.self, CAPWebViewPlugin.self, CAPCookiesPlugin.self]

for classIndex in 0..<Int(numClasses) {
if let aClass: AnyClass = classes[classIndex] {
if class_getSuperclass(aClass) == CDVPlugin.self {
injectCordovaFiles = true
}
if class_conformsToProtocol(aClass, CAPBridgedPlugin.self),
let pluginType = aClass as? CapacitorPlugin.Type {
if aClass is CAPInstancePlugin.Type { continue }
registerPlugin(pluginType)
if autoRegisterPlugins {
do {
if let pluginJSON = Bundle.main.url(forResource: "package.ios", withExtension: "json") {
let pluginData = try Data(contentsOf: pluginJSON)
let registrationList = try JSONDecoder().decode(RegistrationList.self, from: pluginData)
for plugin in registrationList.pluginList {
if let pluginClass = NSClassFromString(plugin) {
pluginList.append(pluginClass)
}
}
}
} catch {
print(error)
}
}

for plugin in pluginList {
if let capPlugin = plugin as? CapacitorPlugin.Type {
markemer marked this conversation as resolved.
Show resolved Hide resolved
registerPlugin(capPlugin)
}
classes.deallocate()
} else {
// register core plugins only
[CAPHttpPlugin.self, CAPConsolePlugin.self, CAPWebViewPlugin.self, CAPCookiesPlugin.self]
.forEach { registerPluginType($0) }
}
}

Expand Down Expand Up @@ -719,4 +733,31 @@ open class CapacitorBridge: NSObject, CAPBridgeProtocol {
self.tmpWindow = nil
}
}



// MARK: - Private Methods
private func oldStyleAutoRegister() {
let classCount = objc_getClassList(nil, 0)
let classes = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(classCount))

let releasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classes)
let numClasses: Int32 = objc_getClassList(releasingClasses, classCount)

for classIndex in 0..<Int(numClasses) {
if let aClass: AnyClass = classes[classIndex] {
if class_getSuperclass(aClass) == CDVPlugin.self {
print("Cordova: \(aClass)")
injectCordovaFiles = true
}
if class_conformsToProtocol(aClass, CAPBridgedPlugin.self),
let pluginType = aClass as? CapacitorPlugin.Type {
if aClass is CAPInstancePlugin.Type { continue }
print("CapPlugin: \(aClass), \(pluginType)")
registerPlugin(pluginType)
}
}
}
classes.deallocate()
}
}
Loading