diff --git a/.github/actions/setup-xcode/action.yml b/.github/actions/setup-xcode/action.yml new file mode 100644 index 0000000000..7462743166 --- /dev/null +++ b/.github/actions/setup-xcode/action.yml @@ -0,0 +1,49 @@ +name: 'Setup Xcode Conditionally' +description: 'Check current Xcode version and setup if different from required version (Avoid unessary sudo/password input for self hosted runner)' + +inputs: + xcode-version: + description: 'Required Xcode version' + required: true + +outputs: + current-version: + description: 'Current Xcode version' + value: ${{ steps.current-xcode.outputs.version }} + setup-skipped: + description: 'Whether Xcode setup was skipped' + value: ${{ steps.xcode-check.outputs.skip-setup }} + +runs: + using: 'composite' + steps: + - name: Get current Xcode version + id: current-xcode + shell: bash + run: | + CURRENT_VERSION=$(xcode-select -p | grep -o '[0-9]\+\.[0-9]\+' || echo "none") + echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "Current Xcode version: $CURRENT_VERSION" + + - name: Check if Xcode version matches + id: xcode-check + shell: bash + run: | + CURRENT="${{ steps.current-xcode.outputs.version }}" + REQUIRED="${{ inputs.xcode-version }}" + if [ "$CURRENT" = "$REQUIRED" ]; then + echo "skip-setup=true" >> $GITHUB_OUTPUT + echo "✅ Xcode version matches: $CURRENT = $REQUIRED, skipping setup" + else + echo "skip-setup=false" >> $GITHUB_OUTPUT + echo "❌ Xcode version mismatch: $CURRENT ≠ $REQUIRED, setup required" + fi + + - name: Setup Xcode + if: steps.xcode-check.outputs.skip-setup == 'false' + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: ${{ inputs.xcode-version }} + - name: Swift version + shell: bash + run: swift --version \ No newline at end of file diff --git a/.github/workflows/CITest.yml b/.github/workflows/CITest.yml index dd3f5b547c..a28192af99 100644 --- a/.github/workflows/CITest.yml +++ b/.github/workflows/CITest.yml @@ -226,3 +226,31 @@ jobs: cmake --preset=${{ matrix.config.platform }}-x64${{ matrix.config.diet_build == true && '-diet' || '' }} cmake --build --preset build-${{ matrix.config.platform }}${{ matrix.config.diet_build == true && '-diet' || '' }}-release cmake --build --preset install-${{ matrix.config.platform }}${{ matrix.config.diet_build == true && '-diet' || '' }}-release + + cmake --preset=${{ matrix.config.platform }}-x64 + cmake --build --preset build-${{ matrix.config.platform }}-release + cmake --build --preset install-${{ matrix.config.platform }}-release + macOS: + name: Execute tests on macOS + strategy: + fail-fast: false + matrix: + os: [macos-15] + xcode-version: ["16.4"] + runs-on: ${{ matrix.os }} + env: + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 + - name: Setup Xcode + uses: ./.github/actions/setup-xcode + with: + xcode-version: ${{ matrix.xcode-version }} + - name: Build and run tests in debug mode + run: | + swift test \ + -c debug + - name: Build and run tests in release mode + run: | + swift test \ + -c release diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000000..fe40b0e317 --- /dev/null +++ b/Package.swift @@ -0,0 +1,53 @@ +// swift-tools-version: 6.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "capstone", + products: [ + .library( + name: "Ccapstone", + targets: ["Ccapstone"] + ), + ], + targets: [ + .target( + name: "Ccapstone", + path: "bindings/swift/Ccapstone", + cSettings: [ + .headerSearchPath("../../../include"), + .define("CAPSTONE_USE_SYS_DYN_MEM"), + .define("CAPSTONE_HAS_ARM"), + .define("CAPSTONE_HAS_ARM64"), + .define("CAPSTONE_HAS_AARCH64"), + .define("CAPSTONE_HAS_MIPS"), + .define("CAPSTONE_HAS_X86"), + .define("CAPSTONE_HAS_POWERPC"), + .define("CAPSTONE_HAS_SPARC"), + .define("CAPSTONE_HAS_SYSTEMZ"), + .define("CAPSTONE_HAS_XCORE"), + .define("CAPSTONE_HAS_M68K"), + .define("CAPSTONE_HAS_TMS320C64X"), + .define("CAPSTONE_HAS_M680X"), + .define("CAPSTONE_HAS_EVM"), + .define("CAPSTONE_HAS_MOS65XX"), + .define("CAPSTONE_HAS_WASM"), + .define("CAPSTONE_HAS_BPF"), + .define("CAPSTONE_HAS_RISCV"), + .define("CAPSTONE_HAS_SH"), + .define("CAPSTONE_HAS_TRICORE"), + .define("CAPSTONE_HAS_ALPHA"), + .define("CAPSTONE_HAS_HPPA"), + .define("CAPSTONE_HAS_LOONGARCH"), + .define("CAPSTONE_HAS_XTENSA"), + .define("CAPSTONE_HAS_ARC"), + ], + ), + .testTarget( + name: "CcapstoneTests", + dependencies: ["Ccapstone"], + path: "bindings/swift/CcapstoneTests", + ), + ] +) diff --git a/bindings/swift/Ccapstone/LEB128.h b/bindings/swift/Ccapstone/LEB128.h new file mode 120000 index 0000000000..371e091330 --- /dev/null +++ b/bindings/swift/Ccapstone/LEB128.h @@ -0,0 +1 @@ +../../../LEB128.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCAsmInfo.h b/bindings/swift/Ccapstone/MCAsmInfo.h new file mode 120000 index 0000000000..31ab35ea22 --- /dev/null +++ b/bindings/swift/Ccapstone/MCAsmInfo.h @@ -0,0 +1 @@ +../../../MCAsmInfo.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCDisassembler.h b/bindings/swift/Ccapstone/MCDisassembler.h new file mode 120000 index 0000000000..6ea5de0e0d --- /dev/null +++ b/bindings/swift/Ccapstone/MCDisassembler.h @@ -0,0 +1 @@ +../../../MCDisassembler.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCFixedLenDisassembler.h b/bindings/swift/Ccapstone/MCFixedLenDisassembler.h new file mode 120000 index 0000000000..123782ab1f --- /dev/null +++ b/bindings/swift/Ccapstone/MCFixedLenDisassembler.h @@ -0,0 +1 @@ +../../../MCFixedLenDisassembler.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCInst.c b/bindings/swift/Ccapstone/MCInst.c new file mode 120000 index 0000000000..4ba686ffdc --- /dev/null +++ b/bindings/swift/Ccapstone/MCInst.c @@ -0,0 +1 @@ +../../../MCInst.c \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCInst.h b/bindings/swift/Ccapstone/MCInst.h new file mode 120000 index 0000000000..0044b5a6f1 --- /dev/null +++ b/bindings/swift/Ccapstone/MCInst.h @@ -0,0 +1 @@ +../../../MCInst.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCInstPrinter.c b/bindings/swift/Ccapstone/MCInstPrinter.c new file mode 120000 index 0000000000..6a4081296b --- /dev/null +++ b/bindings/swift/Ccapstone/MCInstPrinter.c @@ -0,0 +1 @@ +../../../MCInstPrinter.c \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCInstPrinter.h b/bindings/swift/Ccapstone/MCInstPrinter.h new file mode 120000 index 0000000000..386cb88aea --- /dev/null +++ b/bindings/swift/Ccapstone/MCInstPrinter.h @@ -0,0 +1 @@ +../../../MCInstPrinter.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCInstrDesc.c b/bindings/swift/Ccapstone/MCInstrDesc.c new file mode 120000 index 0000000000..73e916fe5a --- /dev/null +++ b/bindings/swift/Ccapstone/MCInstrDesc.c @@ -0,0 +1 @@ +../../../MCInstrDesc.c \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCInstrDesc.h b/bindings/swift/Ccapstone/MCInstrDesc.h new file mode 120000 index 0000000000..294dee6759 --- /dev/null +++ b/bindings/swift/Ccapstone/MCInstrDesc.h @@ -0,0 +1 @@ +../../../MCInstrDesc.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCRegisterInfo.c b/bindings/swift/Ccapstone/MCRegisterInfo.c new file mode 120000 index 0000000000..caaa608336 --- /dev/null +++ b/bindings/swift/Ccapstone/MCRegisterInfo.c @@ -0,0 +1 @@ +../../../MCRegisterInfo.c \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MCRegisterInfo.h b/bindings/swift/Ccapstone/MCRegisterInfo.h new file mode 120000 index 0000000000..6db0beb49f --- /dev/null +++ b/bindings/swift/Ccapstone/MCRegisterInfo.h @@ -0,0 +1 @@ +../../../MCRegisterInfo.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/Mapping.c b/bindings/swift/Ccapstone/Mapping.c new file mode 120000 index 0000000000..3e10b8e7b6 --- /dev/null +++ b/bindings/swift/Ccapstone/Mapping.c @@ -0,0 +1 @@ +../../../Mapping.c \ No newline at end of file diff --git a/bindings/swift/Ccapstone/Mapping.h b/bindings/swift/Ccapstone/Mapping.h new file mode 120000 index 0000000000..69bdb7d354 --- /dev/null +++ b/bindings/swift/Ccapstone/Mapping.h @@ -0,0 +1 @@ +../../../Mapping.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/MathExtras.h b/bindings/swift/Ccapstone/MathExtras.h new file mode 120000 index 0000000000..5034ce41d9 --- /dev/null +++ b/bindings/swift/Ccapstone/MathExtras.h @@ -0,0 +1 @@ +../../../MathExtras.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/SStream.c b/bindings/swift/Ccapstone/SStream.c new file mode 120000 index 0000000000..c744dfa1f8 --- /dev/null +++ b/bindings/swift/Ccapstone/SStream.c @@ -0,0 +1 @@ +../../../SStream.c \ No newline at end of file diff --git a/bindings/swift/Ccapstone/SStream.h b/bindings/swift/Ccapstone/SStream.h new file mode 120000 index 0000000000..2f4e1515fd --- /dev/null +++ b/bindings/swift/Ccapstone/SStream.h @@ -0,0 +1 @@ +../../../SStream.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/UpdateSymlink.sh b/bindings/swift/Ccapstone/UpdateSymlink.sh new file mode 100755 index 0000000000..e859bc7c1c --- /dev/null +++ b/bindings/swift/Ccapstone/UpdateSymlink.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +target_dir="../../.." + +find "$target_dir" -maxdepth 1 \( -name "*.h" -o -name "*.c" \) -print0 | while IFS= read -r -d $'\0' header_file; do + + relative_path=$(echo "$header_file" | sed "s#^$PWD/##") + + ln -s "$relative_path" . + + echo "Created symlink: $(basename "$header_file") -> $relative_path" +done diff --git a/bindings/swift/Ccapstone/arch b/bindings/swift/Ccapstone/arch new file mode 120000 index 0000000000..c96956df19 --- /dev/null +++ b/bindings/swift/Ccapstone/arch @@ -0,0 +1 @@ +../../../arch \ No newline at end of file diff --git a/bindings/swift/Ccapstone/cs.c b/bindings/swift/Ccapstone/cs.c new file mode 120000 index 0000000000..f3de1cec49 --- /dev/null +++ b/bindings/swift/Ccapstone/cs.c @@ -0,0 +1 @@ +../../../cs.c \ No newline at end of file diff --git a/bindings/swift/Ccapstone/cs_priv.h b/bindings/swift/Ccapstone/cs_priv.h new file mode 120000 index 0000000000..ae5dd91549 --- /dev/null +++ b/bindings/swift/Ccapstone/cs_priv.h @@ -0,0 +1 @@ +../../../cs_priv.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/cs_simple_types.h b/bindings/swift/Ccapstone/cs_simple_types.h new file mode 120000 index 0000000000..0a33a17b44 --- /dev/null +++ b/bindings/swift/Ccapstone/cs_simple_types.h @@ -0,0 +1 @@ +../../../cs_simple_types.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/UpdateSymlink.sh b/bindings/swift/Ccapstone/include/capstone/UpdateSymlink.sh new file mode 100755 index 0000000000..227b4220e5 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/UpdateSymlink.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +target_dir="../../../../../include/capstone" + +find "$target_dir" -name "*.h" -print0 | while IFS= read -r -d $'\0' header_file; do + relative_path=$(echo "$header_file" | sed "s#^$PWD/##") + + ln -s "$relative_path" . + + echo "Created symlink: $(basename "$header_file") -> $relative_path" +done diff --git a/bindings/swift/Ccapstone/include/capstone/aarch64.h b/bindings/swift/Ccapstone/include/capstone/aarch64.h new file mode 120000 index 0000000000..ffc5977379 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/aarch64.h @@ -0,0 +1 @@ +../../../../../include/capstone/aarch64.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/alpha.h b/bindings/swift/Ccapstone/include/capstone/alpha.h new file mode 120000 index 0000000000..22833d9929 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/alpha.h @@ -0,0 +1 @@ +../../../../../include/capstone/alpha.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/arc.h b/bindings/swift/Ccapstone/include/capstone/arc.h new file mode 120000 index 0000000000..ef9ea57a33 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/arc.h @@ -0,0 +1 @@ +../../../../../include/capstone/arc.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/arm.h b/bindings/swift/Ccapstone/include/capstone/arm.h new file mode 120000 index 0000000000..69727b0f41 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/arm.h @@ -0,0 +1 @@ +../../../../../include/capstone/arm.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/arm64.h b/bindings/swift/Ccapstone/include/capstone/arm64.h new file mode 120000 index 0000000000..d2f4951d52 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/arm64.h @@ -0,0 +1 @@ +../../../../../include/capstone/arm64.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/bpf.h b/bindings/swift/Ccapstone/include/capstone/bpf.h new file mode 120000 index 0000000000..17ef426656 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/bpf.h @@ -0,0 +1 @@ +../../../../../include/capstone/bpf.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/capstone.h b/bindings/swift/Ccapstone/include/capstone/capstone.h new file mode 120000 index 0000000000..83845c68c0 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/capstone.h @@ -0,0 +1 @@ +../../../../../include/capstone/capstone.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/cs_operand.h b/bindings/swift/Ccapstone/include/capstone/cs_operand.h new file mode 120000 index 0000000000..206ec46ecc --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/cs_operand.h @@ -0,0 +1 @@ +../../../../../include/capstone/cs_operand.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/evm.h b/bindings/swift/Ccapstone/include/capstone/evm.h new file mode 120000 index 0000000000..c99a289ca9 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/evm.h @@ -0,0 +1 @@ +../../../../../include/capstone/evm.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/hppa.h b/bindings/swift/Ccapstone/include/capstone/hppa.h new file mode 120000 index 0000000000..57c266124c --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/hppa.h @@ -0,0 +1 @@ +../../../../../include/capstone/hppa.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/loongarch.h b/bindings/swift/Ccapstone/include/capstone/loongarch.h new file mode 120000 index 0000000000..a4c0baaa26 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/loongarch.h @@ -0,0 +1 @@ +../../../../../include/capstone/loongarch.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/m680x.h b/bindings/swift/Ccapstone/include/capstone/m680x.h new file mode 120000 index 0000000000..5edfe318c7 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/m680x.h @@ -0,0 +1 @@ +../../../../../include/capstone/m680x.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/m68k.h b/bindings/swift/Ccapstone/include/capstone/m68k.h new file mode 120000 index 0000000000..d5abdeb03e --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/m68k.h @@ -0,0 +1 @@ +../../../../../include/capstone/m68k.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/mips.h b/bindings/swift/Ccapstone/include/capstone/mips.h new file mode 120000 index 0000000000..bff4dd303d --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/mips.h @@ -0,0 +1 @@ +../../../../../include/capstone/mips.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/mos65xx.h b/bindings/swift/Ccapstone/include/capstone/mos65xx.h new file mode 120000 index 0000000000..5f3e52e288 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/mos65xx.h @@ -0,0 +1 @@ +../../../../../include/capstone/mos65xx.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/platform.h b/bindings/swift/Ccapstone/include/capstone/platform.h new file mode 120000 index 0000000000..dd63320910 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/platform.h @@ -0,0 +1 @@ +../../../../../include/capstone/platform.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/ppc.h b/bindings/swift/Ccapstone/include/capstone/ppc.h new file mode 120000 index 0000000000..292bf2bda6 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/ppc.h @@ -0,0 +1 @@ +../../../../../include/capstone/ppc.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/riscv.h b/bindings/swift/Ccapstone/include/capstone/riscv.h new file mode 120000 index 0000000000..5e914a7454 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/riscv.h @@ -0,0 +1 @@ +../../../../../include/capstone/riscv.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/sh.h b/bindings/swift/Ccapstone/include/capstone/sh.h new file mode 120000 index 0000000000..0d149e4dd6 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/sh.h @@ -0,0 +1 @@ +../../../../../include/capstone/sh.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/sparc.h b/bindings/swift/Ccapstone/include/capstone/sparc.h new file mode 120000 index 0000000000..2c1db54bfd --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/sparc.h @@ -0,0 +1 @@ +../../../../../include/capstone/sparc.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/systemz.h b/bindings/swift/Ccapstone/include/capstone/systemz.h new file mode 120000 index 0000000000..c4bcec06ee --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/systemz.h @@ -0,0 +1 @@ +../../../../../include/capstone/systemz.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/systemz_compatibility.h b/bindings/swift/Ccapstone/include/capstone/systemz_compatibility.h new file mode 120000 index 0000000000..d42c47dea9 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/systemz_compatibility.h @@ -0,0 +1 @@ +../../../../../include/capstone/systemz_compatibility.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/tms320c64x.h b/bindings/swift/Ccapstone/include/capstone/tms320c64x.h new file mode 120000 index 0000000000..83a88a41de --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/tms320c64x.h @@ -0,0 +1 @@ +../../../../../include/capstone/tms320c64x.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/tricore.h b/bindings/swift/Ccapstone/include/capstone/tricore.h new file mode 120000 index 0000000000..2095a7903e --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/tricore.h @@ -0,0 +1 @@ +../../../../../include/capstone/tricore.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/wasm.h b/bindings/swift/Ccapstone/include/capstone/wasm.h new file mode 120000 index 0000000000..6850e121c3 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/wasm.h @@ -0,0 +1 @@ +../../../../../include/capstone/wasm.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/x86.h b/bindings/swift/Ccapstone/include/capstone/x86.h new file mode 120000 index 0000000000..62e0f8e0cb --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/x86.h @@ -0,0 +1 @@ +../../../../../include/capstone/x86.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/xcore.h b/bindings/swift/Ccapstone/include/capstone/xcore.h new file mode 120000 index 0000000000..66dde9902e --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/xcore.h @@ -0,0 +1 @@ +../../../../../include/capstone/xcore.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/include/capstone/xtensa.h b/bindings/swift/Ccapstone/include/capstone/xtensa.h new file mode 120000 index 0000000000..8b64b00556 --- /dev/null +++ b/bindings/swift/Ccapstone/include/capstone/xtensa.h @@ -0,0 +1 @@ +../../../../../include/capstone/xtensa.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/platform.h b/bindings/swift/Ccapstone/platform.h new file mode 120000 index 0000000000..1349962ee5 --- /dev/null +++ b/bindings/swift/Ccapstone/platform.h @@ -0,0 +1 @@ +../../../include/platform.h \ No newline at end of file diff --git a/bindings/swift/Ccapstone/utils.c b/bindings/swift/Ccapstone/utils.c new file mode 120000 index 0000000000..37965fc4bc --- /dev/null +++ b/bindings/swift/Ccapstone/utils.c @@ -0,0 +1 @@ +../../../utils.c \ No newline at end of file diff --git a/bindings/swift/Ccapstone/utils.h b/bindings/swift/Ccapstone/utils.h new file mode 120000 index 0000000000..bb469b1eb3 --- /dev/null +++ b/bindings/swift/Ccapstone/utils.h @@ -0,0 +1 @@ +../../../utils.h \ No newline at end of file diff --git a/bindings/swift/CcapstoneTests/AdvancedAPITests.swift b/bindings/swift/CcapstoneTests/AdvancedAPITests.swift new file mode 100644 index 0000000000..01496ffd8c --- /dev/null +++ b/bindings/swift/CcapstoneTests/AdvancedAPITests.swift @@ -0,0 +1,491 @@ +import Foundation +import Testing +@testable import Ccapstone + +/// Advanced API usage tests for the Capstone Swift binding +/// These tests cover complex scenarios, advanced features, and integration patterns +@Suite("Advanced API Tests") +struct AdvancedAPITests { + + // Test data for various architectures + private static let x86Code64: [UInt8] = [0x55, 0x48, 0x8b, 0x05, 0xb8, 0x13, 0x00, 0x00] + private static let armCode: [UInt8] = [0xED, 0xFF, 0xFF, 0xEB, 0x04, 0xe0, 0x2d, 0xe5] + private static let aarch64Code: [UInt8] = [0x21, 0x7c, 0x02, 0x9b, 0x21, 0x7c, 0x00, 0x53] + + @Test("Multiple engine instances") + func testMultipleEngines() async throws { + var x86Handle: csh = 0 + var armHandle: csh = 0 + + let x86Result = cs_open(CS_ARCH_X86, CS_MODE_64, &x86Handle) + let armResult = cs_open(CS_ARCH_ARM, CS_MODE_ARM, &armHandle) + + // At least one should work in a complete implementation + var activeHandles: [(csh, String, [UInt8])] = [] + + if x86Result == CS_ERR_OK { + activeHandles.append((x86Handle, "X86-64", Self.x86Code64)) + } + + if armResult == CS_ERR_OK { + activeHandles.append((armHandle, "ARM", Self.armCode)) + } + + guard !activeHandles.isEmpty else { + print("⚠️ Skipping multiple engines test - no engines available") + return + } + + print("✓ Multiple engines test with \(activeHandles.count) active engines:") + + // Test disassembly with each active engine + for (handle, name, code) in activeHandles { + var insns: UnsafeMutablePointer? + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + print(" \(name): \(count) instructions") + + if count > 0 { + cs_free(insns, count) + } + + #expect(count >= 0, "\(name) engine should work without errors") + } + + // Clean up all handles + if x86Result == CS_ERR_OK { + _ = cs_close(&x86Handle) + } + if armResult == CS_ERR_OK { + _ = cs_close(&armHandle) + } + } + + @Test("Detail mode comprehensive testing") + func testDetailModeFeatures() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_64, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping detail mode test - cannot open X86 engine") + return + } + + defer { _ = cs_close(&handle) } + + // Enable detail mode + let detailResult = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + print("✓ Detail mode setting result: \(detailResult)") + + // Disassemble with detail mode + var insns: UnsafeMutablePointer? + let count = Self.x86Code64.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + defer { + if count > 0 { + cs_free(insns, count) + } + } + + guard count > 0 else { + print("⚠️ No instructions disassembled") + return + } + + print("✓ Detail mode disassembly: \(count) instructions") + + // Analyze first instruction in detail + let firstInsn = insns!.pointee + print(" First instruction:") + let mnemonic = withUnsafeBytes(of: firstInsn.mnemonic) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + let operands = withUnsafeBytes(of: firstInsn.op_str) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + print(" Mnemonic: \(mnemonic)") + print(" Operands: \(operands)") + print(" Address: 0x\(String(format: "%llx", firstInsn.address))") + print(" Size: \(firstInsn.size)") + print(" ID: \(firstInsn.id)") + + // Check if detail information is available + if let detail = firstInsn.detail { + print(" Detail available: Yes") + + // Access detail structure (this might fail if implementation is incomplete) + let detailStruct = detail.pointee + print(" Groups count: \(detailStruct.groups_count)") + print(" Reads registers: \(detailStruct.regs_read_count)") + print(" Writes registers: \(detailStruct.regs_write_count)") + + // Note: Architecture-specific details would require more complete implementation + } else { + print(" Detail available: No") + } + + // Test turning detail mode off + let detailOffResult = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_OFF.rawValue)) + print(" Detail mode off result: \(detailOffResult)") + } + + @Test("Skip data mode testing") + func testSkipDataMode() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping skip data mode test - cannot open X86 engine") + return + } + + defer { _ = cs_close(&handle) } + + // Create code with embedded data (invalid instructions) + let codeWithData: [UInt8] = [ + 0x90, // NOP + 0xFF, 0xFF, 0xFF, // Invalid data + 0x90, // NOP + 0x00, 0x00, // More invalid data + 0x90 // NOP + ] + + // Test normal mode (should stop at first invalid instruction) + var insns: UnsafeMutablePointer? + let normalCount = codeWithData.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + if normalCount > 0 { + print("✓ Normal mode: \(normalCount) instructions") + cs_free(insns, normalCount) + } + + // Test skip data mode (if supported) + let skipDataResult = cs_option(handle, CS_OPT_SKIPDATA, size_t(CS_OPT_ON.rawValue)) + print(" Skip data mode setting: \(skipDataResult)") + + if skipDataResult == CS_ERR_OK { + let skipCount = codeWithData.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + if skipCount > 0 { + print(" Skip data mode: \(skipCount) instructions") + + // Examine instructions to see if data was skipped + for i in 0..= normalCount, "Skip data mode should find at least as many instructions") + } + } + + @Test("Custom memory allocation patterns") + func testCustomMemoryPatterns() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping memory pattern test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + // Test malloc/free pattern + let insn = cs_malloc(handle) + guard let insn = insn else { + print("⚠️ cs_malloc returned null") + return + } + + print("✓ Custom memory allocation patterns:") + print(" cs_malloc succeeded: \(insn)") + + // Test iterator pattern with custom-allocated instruction + let testCode: [UInt8] = [0x90, 0x90, 0x90] // Multiple NOPs + var instructionCount = 0 + + testCode.withUnsafeBufferPointer { buffer in + var codePtr = buffer.baseAddress + var size = testCode.count + var address: UInt64 = 0x1000 + + while cs_disasm_iter(handle, &codePtr, &size, &address, insn) { + instructionCount += 1 + let mnemonic = withUnsafeBytes(of: insn.pointee.mnemonic) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + print(" Instruction \(instructionCount): \(mnemonic)") + + // Safety break + if instructionCount >= 10 { + break + } + } + } + + cs_free(insn, 1) + print(" Iterator with custom allocation: \(instructionCount) instructions") + + // Test batch allocation pattern + var batchInsns: UnsafeMutablePointer? + let batchCount = testCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &batchInsns) + } + + if batchCount > 0 { + print(" Batch allocation: \(batchCount) instructions") + cs_free(batchInsns, batchCount) + } + + #expect(instructionCount == batchCount, "Iterator and batch methods should find same number of instructions") + } + + @Test("Option combinations and interactions") + func testOptionCombinations() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_64, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping option combinations test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + print("✓ Testing option combinations:") + + // Test various option combinations + let optionTests: [(cs_opt_type, size_t, String)] = [ + (CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue), "Detail ON"), + (CS_OPT_SKIPDATA, size_t(CS_OPT_ON.rawValue), "Skip Data ON"), + (CS_OPT_SYNTAX, size_t(CS_OPT_SYNTAX_DEFAULT.rawValue), "Default Syntax"), + (CS_OPT_DETAIL, size_t(CS_OPT_OFF.rawValue), "Detail OFF"), + (CS_OPT_SKIPDATA, size_t(CS_OPT_OFF.rawValue), "Skip Data OFF"), + ] + + for (option, value, description) in optionTests { + let result = cs_option(handle, option, value) + print(" \(description): \(result == CS_ERR_OK ? "✓" : "✗") (\(result))") + + // Test disassembly after each option change + var insns: UnsafeMutablePointer? + let count = Self.x86Code64.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + if count > 0 { + cs_free(insns, count) + print(" Disassembly after option: \(count) instructions") + } + } + } + + @Test("Large instruction analysis") + func testLargeInstructionHandling() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_64, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping large instruction test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + // X86-64 instruction with large displacement/immediate (if we had real long instructions) + // For now, use what we have and test the structures + let _ = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + + var insns: UnsafeMutablePointer? + let count = Self.x86Code64.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + defer { + if count > 0 { + cs_free(insns, count) + } + } + + guard count > 0 else { + print("⚠️ No instructions for large instruction test") + return + } + + print("✓ Large instruction handling:") + + for i in 0.. 0 && mnemonicLen < CS_MNEMONIC_SIZE, "Mnemonic length should be reasonable") + #expect(operandLen < 512, "Operand string length should be reasonable") // Reasonable limit + + print(" Mnemonic length: \(mnemonicLen), Operand length: \(operandLen)") + } + } + + @Test("Thread safety simulation") + func testThreadSafetyPatterns() async throws { + // Note: Each task will create its own handle as handles should not be shared between threads + + let concurrentTasks = 5 + let operationsPerTask = 50 + + print("✓ Thread safety patterns test:") + + await withTaskGroup(of: (Int, Int, Bool).self) { group in + for taskId in 0..? + let count = Self.x86Code64.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + if count > 0 { + // Access the first instruction to test memory safety + let firstInsn = insns!.pointee + let _ = withUnsafeBytes(of: firstInsn.mnemonic) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + cs_free(insns, count) + successfulOps += 1 + } + + let closeResult = cs_close(&handle) + if closeResult != CS_ERR_OK { + hadCriticalFailure = true + } + } + + return (taskId, successfulOps, hadCriticalFailure) + } + } + + var totalSuccessfulOps = 0 + var totalTasks = 0 + var hadAnyFailures = false + + for await (taskId, successfulOps, hadFailure) in group { + totalTasks += 1 + totalSuccessfulOps += successfulOps + hadAnyFailures = hadAnyFailures || hadFailure + + print(" Task \(taskId): \(successfulOps)/\(operationsPerTask) successful") + } + + let totalPossibleOps = concurrentTasks * operationsPerTask + print(" Overall: \(totalSuccessfulOps)/\(totalPossibleOps) successful operations") + print(" Critical failures: \(hadAnyFailures ? "Yes" : "No")") + + #expect(!hadAnyFailures, "Should not have critical failures in concurrent access") + } + } + + @Test("Error recovery and robustness") + func testErrorRecovery() async throws { + var handle: csh = 0 + + // Test recovery from various error conditions + print("✓ Error recovery and robustness:") + + // Invalid architecture followed by valid one + let invalidResult = cs_open(cs_arch(rawValue: 999), CS_MODE_32, &handle) + #expect(invalidResult != CS_ERR_OK, "Invalid architecture should fail") + #expect(handle == 0, "Handle should be 0 after failure") + + let validResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + if validResult == CS_ERR_OK { + print(" Recovery after invalid architecture: ✓") + + // Test invalid operations on valid handle + let invalidOption = cs_option(handle, cs_opt_type(rawValue: 999), 0) + print(" Invalid option on valid handle: \(invalidOption)") + + // Handle should still be usable after invalid operation + let errno = cs_errno(handle) + print(" Handle still usable after error: \(errno != CS_ERR_HANDLE ? "✓" : "✗")") + + // Test disassembly after error + var insns: UnsafeMutablePointer? + let count = Self.x86Code64.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + if count > 0 { + cs_free(insns, count) + print(" Disassembly after error: ✓ (\(count) instructions)") + } else { + print(" Disassembly after error: ✗") + } + + _ = cs_close(&handle) + } else { + print(" Cannot test recovery - X86 engine not available") + } + + // Test multiple close attempts (should not crash) + // Note: After previous operations, handle should be 0 (invalid) + // Second close attempt on an already closed/invalid handle may be unsafe + print(" Skipping multiple close test to avoid potential memory issues") + #expect(Bool(true), "Multiple close test skipped for safety") + } +} diff --git a/bindings/swift/CcapstoneTests/ArchitectureTests.swift b/bindings/swift/CcapstoneTests/ArchitectureTests.swift new file mode 100644 index 0000000000..2d526bc7fc --- /dev/null +++ b/bindings/swift/CcapstoneTests/ArchitectureTests.swift @@ -0,0 +1,234 @@ +import Foundation +import Testing +@testable import Ccapstone + +@Suite struct ArchitectureTests { + // Test code samples for different architectures + private struct TestPlatform { + let arch: cs_arch + let mode: cs_mode + let code: [UInt8] + let comment: String + } + + private let platforms: [TestPlatform] = [ + TestPlatform( + arch: CS_ARCH_X86, + mode: CS_MODE_16, + code: [0x8D, 0x4C, 0x32, 0x08, 0x01, 0xD8, 0x81, 0xC6, 0x34, 0x12, 0x00, 0x00], + comment: "X86 16bit" + ), + TestPlatform( + arch: CS_ARCH_X86, + mode: CS_MODE_32, + code: [0x8D, 0x4C, 0x32, 0x08, 0x01, 0xD8, 0x81, 0xC6, 0x34, 0x12, 0x00, 0x00], + comment: "X86 32bit" + ), + TestPlatform( + arch: CS_ARCH_X86, + mode: CS_MODE_64, + code: [0x55, 0x48, 0x8B, 0x05, 0xB8, 0x13, 0x00, 0x00], + comment: "X86 64bit" + ), + TestPlatform( + arch: CS_ARCH_ARM, + mode: CS_MODE_ARM, + code: [0xED, 0xFF, 0xFF, 0xEB, 0x04, 0xE0, 0x2D, 0xE5, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x83, 0x22, 0xE5], + comment: "ARM" + ), + TestPlatform( + arch: CS_ARCH_ARM, + mode: CS_MODE_THUMB, + code: [0x70, 0x47, 0xEB, 0x46, 0x83, 0xB0, 0xC9, 0x68], + comment: "Thumb" + ), + TestPlatform( + arch: CS_ARCH_AARCH64, + mode: CS_MODE_ARM, + code: [0x21, 0x7C, 0x02, 0x9B, 0x21, 0x7C, 0x00, 0x53, 0x00, 0x40, 0x21, 0x4B, 0xE1, 0x0B, 0x40, 0xB9], + comment: "AArch64" + ), + TestPlatform( + arch: CS_ARCH_MIPS, + mode: cs_mode(CS_MODE_MIPS32.rawValue | CS_MODE_BIG_ENDIAN.rawValue), + code: [0x0C, 0x10, 0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x24, 0x02, 0x00, 0x0C, 0x8F, 0xA2, 0x00, 0x00, 0x34, 0x21, 0x34, 0x56], + comment: "MIPS32 Big-endian" + ), + TestPlatform( + arch: CS_ARCH_MIPS, + mode: cs_mode(CS_MODE_MIPS64.rawValue | CS_MODE_LITTLE_ENDIAN.rawValue), + code: [0x56, 0x34, 0x21, 0x34, 0xC2, 0x17, 0x01, 0x00], + comment: "MIPS64 Little-endian" + ), + TestPlatform( + arch: CS_ARCH_PPC, + mode: CS_MODE_BIG_ENDIAN, + code: [0x80, 0x20, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x10, 0x43, 0x23, 0x0E, 0xD0, 0x44, 0x00, 0x80, 0x4C, 0x43, 0x22, 0x02], + comment: "PowerPC" + ), + TestPlatform( + arch: CS_ARCH_SPARC, + mode: CS_MODE_BIG_ENDIAN, + code: [0x80, 0xA0, 0x40, 0x02, 0x85, 0xC2, 0x60, 0x08, 0x85, 0xE8, 0x20, 0x01, 0x81, 0xE8, 0x00, 0x00, 0x90, 0x10, 0x20, 0x01], + comment: "Sparc" + ), + TestPlatform( + arch: CS_ARCH_SYSTEMZ, + mode: CS_MODE_BIG_ENDIAN, + code: [0xED, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x5A, 0x0F, 0x1F, 0xFF, 0xC2, 0x09, 0x80, 0x00, 0x00, 0x00, 0x07, 0xF7], + comment: "SystemZ" + ), + ] + + @Test func allArchitectures() { + for platform in platforms { + testArchitecture(platform) + } + } + + @Test func x86Syntax() { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + #expect(openResult == CS_ERR_OK) + + // Test Intel syntax (default) + let code: [UInt8] = [0x8D, 0x4C, 0x32, 0x08] + var insns: UnsafeMutablePointer? + + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + #expect(count > 0) + if count > 0, insns != nil { + let insn = insns![0] + let opStr = String(cString: withUnsafeBytes(of: insn.op_str) { $0.bindMemory(to: CChar.self).baseAddress! }) + print("Intel syntax: \(opStr)") + cs_free(insns, count) + } + + // Test AT&T syntax + let attResult = cs_option(handle, CS_OPT_SYNTAX, size_t(CS_OPT_SYNTAX_ATT.rawValue)) + if attResult == CS_ERR_OK { + let attCount = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + if attCount > 0 && insns != nil { + let insn = insns![0] + let opStr = String(cString: withUnsafeBytes(of: insn.op_str) { $0.bindMemory(to: CChar.self).baseAddress! }) + print("AT&T syntax: \(opStr)") + cs_free(insns, attCount) + } + } + + _ = cs_close(&handle) + } + + @Test func mIPSModes() { + let mipsCodes: [(cs_mode, [UInt8], String)] = [ + (cs_mode(CS_MODE_MIPS32.rawValue | CS_MODE_BIG_ENDIAN.rawValue), [0x0C, 0x10, 0x00, 0x97], "MIPS32 BE"), + (cs_mode(CS_MODE_MIPS32.rawValue | CS_MODE_LITTLE_ENDIAN.rawValue), [0x97, 0x00, 0x10, 0x0C], "MIPS32 LE"), + (cs_mode(CS_MODE_MIPS64.rawValue | CS_MODE_BIG_ENDIAN.rawValue), [0x0C, 0x10, 0x00, 0x97], "MIPS64 BE"), + (cs_mode(CS_MODE_MIPS64.rawValue | CS_MODE_LITTLE_ENDIAN.rawValue), [0x97, 0x00, 0x10, 0x0C], "MIPS64 LE"), + ] + + for (mode, code, comment) in mipsCodes { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_MIPS, mode, &handle) + #expect(openResult == CS_ERR_OK, "Failed to open \(comment)") + + var insns: UnsafeMutablePointer? + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 1, &insns) + } + + #expect(count >= 0, "Disassembly failed for \(comment)") + + if count > 0, insns != nil { + let insn = insns![0] + let mnemonic = String(cString: withUnsafeBytes(of: insn.mnemonic) { $0.bindMemory(to: CChar.self).baseAddress! }) + print("\(comment): \(mnemonic)") + cs_free(insns, count) + } + + _ = cs_close(&handle) + } + } + + @Test func aRMModes() { + let armTests: [(cs_mode, [UInt8], String)] = [ + (CS_MODE_ARM, [0x04, 0xE0, 0x2D, 0xE5], "ARM mode"), + (CS_MODE_THUMB, [0x70, 0x47], "Thumb mode"), + (cs_mode(CS_MODE_THUMB.rawValue | CS_MODE_MCLASS.rawValue), [0x70, 0x47], "Thumb M-Class"), + ] + + for (mode, code, comment) in armTests { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_ARM, mode, &handle) + #expect(openResult == CS_ERR_OK, "Failed to open \(comment)") + + var insns: UnsafeMutablePointer? + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 1, &insns) + } + + #expect(count >= 0, "Disassembly failed for \(comment)") + + if count > 0, insns != nil { + let insn = insns![0] + let mnemonic = String(cString: withUnsafeBytes(of: insn.mnemonic) { $0.bindMemory(to: CChar.self).baseAddress! }) + print("\(comment): \(mnemonic)") + cs_free(insns, count) + } + + _ = cs_close(&handle) + } + } + + private func testArchitecture(_ platform: TestPlatform) { + var handle: csh = 0 + var insns: UnsafeMutablePointer? + + let openResult = cs_open(platform.arch, platform.mode, &handle) + #expect(openResult == CS_ERR_OK, "Failed to open \(platform.comment)") + + let count = platform.code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + #expect(count > 0, "No instructions disassembled for \(platform.comment)") + + if count > 0, insns != nil { + print("\n\(platform.comment):") + for i in 0 ..< count { + let insn = insns![Int(i)] + let address = String(format: "0x%x", insn.address) + let mnemonic = String(cString: withUnsafeBytes(of: insn.mnemonic) { $0.bindMemory(to: CChar.self).baseAddress! }) + let opStr = String(cString: withUnsafeBytes(of: insn.op_str) { $0.bindMemory(to: CChar.self).baseAddress! }) + print(" \(address)\t\(mnemonic)\t\(opStr)") + + // Validate instruction properties + #expect(insn.size > 0) + #expect(insn.mnemonic.0 != 0) + #expect(insn.address == 0x1000 + UInt64(calculateOffset(for: i, from: insns!))) + } + + cs_free(insns, count) + } + + _ = cs_close(&handle) + } + + // Helper to calculate instruction offset for address validation + private func calculateOffset(for index: Int, from instructions: UnsafeMutablePointer) -> Int { + var offset = 0 + for i in 0 ..< index { + offset += Int(instructions[i].size) + } + return offset + } +} diff --git a/bindings/swift/CcapstoneTests/BasicTests.swift b/bindings/swift/CcapstoneTests/BasicTests.swift new file mode 100644 index 0000000000..58f3dfee3b --- /dev/null +++ b/bindings/swift/CcapstoneTests/BasicTests.swift @@ -0,0 +1,198 @@ +import Foundation +import Testing +@testable import Ccapstone + +/// Basic functionality tests for the Capstone Swift binding +/// These tests verify the core API functions and basic operations +@Suite("Basic API Tests") +struct BasicTests { + + @Test("Version information retrieval") + func testVersion() async throws { + var major: Int32 = 0 + var minor: Int32 = 0 + + let version = cs_version(&major, &minor) + + #expect(major > 0, "Major version should be positive") + #expect(minor >= 0, "Minor version should be non-negative") + #expect(version > 0, "Version should be positive") + #expect(version == UInt32((major << 8) | minor), "Version should match expected format") + + print("✓ Capstone version: \(major).\(minor) (0x\(String(format: "%04x", version)))") + } + + @Test("Engine open and close operations") + func testOpenClose() async throws { + var handle: csh = 0 + + let result = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + #expect(result == CS_ERR_OK, "Engine should open successfully") + #expect(handle != 0, "Handle should be non-zero after successful open") + + let closeResult = cs_close(&handle) + #expect(closeResult == CS_ERR_OK, "Engine should close successfully") + #expect(handle == 0, "Handle should be zero after close") + + print("✓ Engine open/close: Success") + } + + @Test("Invalid architecture handling") + func testInvalidArch() async throws { + var handle: csh = 0 + + let result = cs_open(cs_arch(1000), CS_MODE_32, &handle) + #expect(result != CS_ERR_OK, "Invalid architecture should fail") + #expect(handle == 0, "Handle should remain zero for invalid architecture") + + print("✓ Invalid architecture handled correctly") + } + + @Test("Error message strings") + func testStrerror() async throws { + let errorMsg = cs_strerror(CS_ERR_OK) + #expect(errorMsg != nil, "Error message should not be nil") + + let okString = String(cString: errorMsg!) + #expect(!okString.isEmpty, "Error message string should not be empty") + + let invalidMsg = cs_strerror(CS_ERR_ARCH) + #expect(invalidMsg != nil, "Error message for CS_ERR_ARCH should not be nil") + + let invalidString = String(cString: invalidMsg!) + #expect(!invalidString.isEmpty, "Error message string should not be empty") + #expect(okString != invalidString, "Different error codes should have different messages") + + print("✓ Error messages: '\(okString)' vs '\(invalidString)'") + } + + @Test("Error number retrieval") + func testErrno() async throws { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + if openResult == CS_ERR_OK { + let errno = cs_errno(handle) + #expect(errno == CS_ERR_OK, "Error number should be CS_ERR_OK for successful operation") + _ = cs_close(&handle) + print("✓ Error number retrieval: Success") + } else { + print("⚠️ Skipping errno test - cannot open X86 engine") + } + } + + @Test("Option setting") + func testOption() async throws { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + if openResult == CS_ERR_OK { + let optResult = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + // Note: Option result may vary depending on implementation completeness + // We mainly test that it doesn't crash + + _ = cs_close(&handle) + print("✓ Option setting: Result \(optResult)") + } else { + print("⚠️ Skipping option test - cannot open X86 engine") + } + } + + @Test("Basic disassembly functionality") + func testBasicDisassembly() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping basic disassembly test - cannot open X86 engine") + return + } + + defer { _ = cs_close(&handle) } + + // Simple x86 32-bit code: lea ecx, [edx+esi+8]; add eax, ebx + let code: [UInt8] = [0x8d, 0x4c, 0x32, 0x08, 0x01, 0xd8] + + var insns: UnsafeMutablePointer? + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + defer { + if count > 0 { + cs_free(insns, count) + } + } + + #expect(count >= 0, "Disassembly should not fail catastrophically") + + if count > 0 { + let firstInsn = insns!.pointee + let mnemonic = withUnsafeBytes(of: firstInsn.mnemonic) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + let operands = withUnsafeBytes(of: firstInsn.op_str) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + + #expect(!mnemonic.isEmpty, "Mnemonic should not be empty") + #expect(firstInsn.size > 0, "Instruction size should be positive") + #expect(firstInsn.address == 0x1000, "Instruction address should match input") + + print("✓ Basic disassembly: \(count) instructions") + print(" First: \(mnemonic) \(operands) (size: \(firstInsn.size))") + } else { + print("⚠️ Basic disassembly returned 0 instructions (implementation may be incomplete)") + } + } + + @Test("Iterator API functionality") + func testIteratorAPI() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping iterator API test - cannot open X86 engine") + return + } + + defer { _ = cs_close(&handle) } + + let insn = cs_malloc(handle) + guard let insn = insn else { + print("⚠️ cs_malloc returned nil") + return + } + + defer { cs_free(insn, 1) } + + let code: [UInt8] = [0x90, 0x90, 0x90] // Three NOPs + var instructionCount = 0 + + code.withUnsafeBufferPointer { buffer in + var codePtr = buffer.baseAddress + var size = code.count + var address: UInt64 = 0x1000 + + while cs_disasm_iter(handle, &codePtr, &size, &address, insn) { + instructionCount += 1 + let mnemonic = withUnsafeBytes(of: insn.pointee.mnemonic) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + + #expect(!mnemonic.isEmpty, "Mnemonic should not be empty") + #expect(insn.pointee.size > 0, "Instruction size should be positive") + + // Safety break to avoid infinite loops + if instructionCount > 10 { + break + } + } + } + + print("✓ Iterator API: \(instructionCount) instructions processed") + #expect(instructionCount >= 0, "Iterator should handle instructions without crashing") + } +} diff --git a/bindings/swift/CcapstoneTests/CompatibilityTests.swift b/bindings/swift/CcapstoneTests/CompatibilityTests.swift new file mode 100644 index 0000000000..a6b39a86c6 --- /dev/null +++ b/bindings/swift/CcapstoneTests/CompatibilityTests.swift @@ -0,0 +1,364 @@ +import Foundation +import Testing +@testable import Ccapstone + +/// Cross-platform compatibility and edge case tests for the Capstone Swift binding +/// These tests verify behavior across different platforms and unusual conditions +@Suite("Compatibility Tests") +struct CompatibilityTests { + + // Test data for various scenarios + private static let emptyCode: [UInt8] = [] + private static let singleByteCode: [UInt8] = [0x90] // NOP instruction + private static let invalidCode: [UInt8] = [0xFF, 0xFF, 0xFF, 0xFF] + private static let mixedValidInvalidCode: [UInt8] = [0x90, 0xFF, 0xFF, 0x90, 0xFF] + + @Test("Platform-specific enum values consistency") + func testEnumValueConsistency() async throws { + // Test that enum values are consistent across platforms + let enumTests: [(String, UInt32, UInt32)] = [ + ("CS_ERR_OK", CS_ERR_OK.rawValue, 0), + ("CS_ERR_MEM", CS_ERR_MEM.rawValue, 1), + ("CS_ERR_ARCH", CS_ERR_ARCH.rawValue, 2), + ("CS_ERR_HANDLE", CS_ERR_HANDLE.rawValue, 3), + ("CS_ERR_CSH", CS_ERR_CSH.rawValue, 4), + ("CS_ERR_MODE", CS_ERR_MODE.rawValue, 5), + ("CS_ERR_OPTION", CS_ERR_OPTION.rawValue, 6), + ("CS_ERR_DETAIL", CS_ERR_DETAIL.rawValue, 7), + ("CS_ERR_MEMSETUP", CS_ERR_MEMSETUP.rawValue, 8), + ("CS_ERR_VERSION", CS_ERR_VERSION.rawValue, 9), + + ("CS_ARCH_ARM", CS_ARCH_ARM.rawValue, 0), + ("CS_ARCH_AARCH64", CS_ARCH_AARCH64.rawValue, 1), + ("CS_ARCH_MIPS", CS_ARCH_MIPS.rawValue, 3), + ("CS_ARCH_X86", CS_ARCH_X86.rawValue, 4), + ("CS_ARCH_PPC", CS_ARCH_PPC.rawValue, 5), + + ("CS_MODE_LITTLE_ENDIAN", CS_MODE_LITTLE_ENDIAN.rawValue, 0), + ("CS_MODE_ARM", CS_MODE_ARM.rawValue, 0), + ("CS_MODE_16", CS_MODE_16.rawValue, 1 << 1), + ("CS_MODE_32", CS_MODE_32.rawValue, 1 << 2), + ("CS_MODE_64", CS_MODE_64.rawValue, 1 << 3), + ] + + print("✓ Testing enum value consistency:") + for (name, actual, expected) in enumTests { + #expect(actual == expected, "\(name) should be \(expected), got \(actual)") + print(" \(name): \(actual) ✓") + } + } + + @Test("Pointer size and alignment compatibility") + func testPointerCompatibility() async throws { + // Test that pointer sizes and alignments are reasonable + let insnSize = MemoryLayout.size + let insnAlignment = MemoryLayout.alignment + let detailSize = MemoryLayout.size + let detailAlignment = MemoryLayout.alignment + let cshSize = MemoryLayout.size + + print("✓ Platform-specific sizes and alignments:") + print(" cs_insn: \(insnSize) bytes, alignment: \(insnAlignment)") + print(" cs_detail: \(detailSize) bytes, alignment: \(detailAlignment)") + print(" csh (handle): \(cshSize) bytes") + + // Basic sanity checks + #expect(insnSize > 0 && insnSize < 1024, "cs_insn size should be reasonable") + #expect(detailSize > 0 && detailSize < 4096, "cs_detail size should be reasonable") + #expect(cshSize > 0 && cshSize <= 8, "csh should be pointer-sized") + + // Alignment should be power of 2 and reasonable + #expect(insnAlignment > 0 && (insnAlignment & (insnAlignment - 1)) == 0, "cs_insn alignment should be power of 2") + #expect(detailAlignment > 0 && (detailAlignment & (detailAlignment - 1)) == 0, "cs_detail alignment should be power of 2") + } + + @Test("Empty and invalid code handling") + func testEdgeCaseCodeHandling() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping edge case tests - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + // Test empty code + var insns: UnsafeMutablePointer? + let emptyCount = Self.emptyCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + #expect(emptyCount == 0, "Empty code should produce 0 instructions") + print("✓ Empty code handling: \(emptyCount) instructions") + + // Test single byte + let singleCount = Self.singleByteCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + if singleCount > 0 { + cs_free(insns, singleCount) + } + + print("✓ Single byte code: \(singleCount) instructions") + + // Test invalid code + let invalidCount = Self.invalidCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + if invalidCount > 0 { + cs_free(insns, invalidCount) + } + + print("✓ Invalid code handling: \(invalidCount) instructions") + + // All tests should complete without crashing + #expect(Bool(true), "All edge cases should be handled gracefully") + } + + @Test("Maximum values and boundaries") + func testBoundaryValues() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping boundary tests - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + // Test with maximum address value + let maxAddress: UInt64 = UInt64.max + var insns: UnsafeMutablePointer? + let maxAddrCount = Self.singleByteCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, maxAddress, 0, &insns) + } + + if maxAddrCount > 0 { + let instruction = insns!.pointee + print("✓ Maximum address test: instruction at 0x\(String(format: "%llx", instruction.address))") + cs_free(insns, maxAddrCount) + } + + // Test with zero address + let zeroAddrCount = Self.singleByteCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0, 0, &insns) + } + + if zeroAddrCount > 0 { + cs_free(insns, zeroAddrCount) + } + + print("✓ Zero address test: \(zeroAddrCount) instructions") + + // Test with maximum count parameter (should be ignored for cs_disasm with count=0) + let maxCountTest = Self.singleByteCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, size_t.max, &insns) + } + + if maxCountTest > 0 { + cs_free(insns, maxCountTest) + } + + print("✓ Maximum count parameter test: \(maxCountTest) instructions") + } + + @Test("String handling and encoding") + func testStringHandling() async throws { + // Test error message strings for proper encoding + let testErrors: [cs_err] = [ + CS_ERR_OK, CS_ERR_MEM, CS_ERR_ARCH, CS_ERR_HANDLE, + CS_ERR_CSH, CS_ERR_MODE, CS_ERR_OPTION, CS_ERR_DETAIL + ] + + print("✓ Testing string encoding and validity:") + for errorCode in testErrors { + let message = cs_strerror(errorCode) + #expect(message != nil, "Error message should not be nil") + + if let message = message { + let errorString = String(cString: message) + #expect(!errorString.isEmpty, "Error string should not be empty") + #expect(errorString.utf8.count > 0, "String should have valid UTF-8 encoding") + + // Check for reasonable string length (not too short or suspiciously long) + #expect(errorString.count >= 2 && errorString.count <= 100, "Error message should have reasonable length") + + print(" \(errorCode.rawValue): '\(errorString)' (\(errorString.count) chars)") + } + } + } + + @Test("Iterator API edge cases") + func testIteratorEdgeCases() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping iterator edge case tests - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + let insn = cs_malloc(handle) + guard let insn = insn else { + print("⚠️ Cannot allocate instruction structure") + return + } + + defer { cs_free(insn, 1) } + + // Test iterator with empty code + var result = Self.emptyCode.withUnsafeBufferPointer { buffer -> Bool in + var codePtr = buffer.baseAddress + var size = Self.emptyCode.count + var address: UInt64 = 0x1000 + + return cs_disasm_iter(handle, &codePtr, &size, &address, insn) + } + + #expect(result == false, "Iterator should return false for empty code") + print("✓ Iterator empty code test: \(result)") + + // Test iterator with single valid instruction + result = Self.singleByteCode.withUnsafeBufferPointer { buffer -> Bool in + var codePtr = buffer.baseAddress + var size = Self.singleByteCode.count + var address: UInt64 = 0x1000 + + let firstResult = cs_disasm_iter(handle, &codePtr, &size, &address, insn) + if firstResult { + let mnemonic = withUnsafeBytes(of: insn.pointee.mnemonic) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + print(" First instruction: \(mnemonic)") + // Try to get second instruction (should fail) + let secondResult = cs_disasm_iter(handle, &codePtr, &size, &address, insn) + return secondResult + } + return firstResult + } + + print("✓ Iterator single instruction test: second call returned \(result)") + + // Test iterator with mixed valid/invalid code + var instructionCount = 0 + Self.mixedValidInvalidCode.withUnsafeBufferPointer { buffer in + var codePtr = buffer.baseAddress + var size = Self.mixedValidInvalidCode.count + var address: UInt64 = 0x1000 + + while cs_disasm_iter(handle, &codePtr, &size, &address, insn) { + instructionCount += 1 + if instructionCount > 10 { // Safety break to avoid infinite loops + break + } + } + } + + print("✓ Iterator mixed code test: \(instructionCount) instructions found") + #expect(instructionCount >= 0, "Should handle mixed code gracefully") + } + + @Test("Version consistency across calls") + func testVersionStability() async throws { + // Call version function multiple times and ensure consistency + let iterations = 100 + var versions: [UInt32] = [] + var majors: [Int32] = [] + var minors: [Int32] = [] + + for _ in 0..= 0, "Should handle architecture queries without crashing") + } + + @Test("Memory safety with null pointers") + func testNullPointerSafety() async throws { + // cs_strerror should work with any error code + for errorCode in 0..<20 { + let message = cs_strerror(cs_err(rawValue: UInt32(errorCode))) + #expect(message != nil, "cs_strerror should return non-null for any error code") + } + + // cs_version with null parameters + let versionOnly = cs_version(nil, nil) + #expect(versionOnly > 0, "cs_version should work with null parameters") + print("✓ cs_version with null parameters: \(versionOnly)") + + // Operations on invalid handles should return errors, not crash + let errno = cs_errno(0) + #expect(errno != CS_ERR_OK, "cs_errno on invalid handle should return error") + + let optResult = cs_option(0, CS_OPT_DETAIL, 1) + #expect(optResult != CS_ERR_OK, "cs_option on invalid handle should return error") + + print("✓ All null pointer and invalid handle tests completed safely") + } +} diff --git a/bindings/swift/CcapstoneTests/CoreAPITests.swift b/bindings/swift/CcapstoneTests/CoreAPITests.swift new file mode 100644 index 0000000000..7cdedf7c88 --- /dev/null +++ b/bindings/swift/CcapstoneTests/CoreAPITests.swift @@ -0,0 +1,214 @@ +import Foundation +import Testing +@testable import Ccapstone + +/// Core API tests that focus on the C interface without architecture-specific details +/// These tests verify the basic Capstone C API functionality exposed through Swift +@Suite("Core API Tests") +struct CoreAPITests { + + @Test("Version API comprehensive testing") + func testVersionAPI() async throws { + var major: Int32 = 0 + var minor: Int32 = 0 + + let version = cs_version(&major, &minor) + + #expect(major > 0, "Major version should be greater than 0") + #expect(minor >= 0, "Minor version should be greater than or equal to 0") + #expect(version > 0, "Version should be greater than 0") + #expect(version == UInt32((major << 8) | minor), "Version format should be correct") + + print("✓ Capstone version: \(major).\(minor) (0x\(String(format: "%04x", version)))") + } + + @Test("Basic enum values validation") + func testBasicEnumValues() async throws { + // Test error code enum values + #expect(CS_ERR_OK.rawValue == 0, "CS_ERR_OK should be 0") + #expect(CS_ERR_ARCH.rawValue != 0, "CS_ERR_ARCH should not be 0") + #expect(CS_ERR_HANDLE.rawValue != 0, "CS_ERR_HANDLE should not be 0") + #expect(CS_ERR_MEM.rawValue != 0, "CS_ERR_MEM should not be 0") + + // Test architecture enum values + #expect(CS_ARCH_ARM.rawValue == 0, "CS_ARCH_ARM should be 0") + #expect(CS_ARCH_AARCH64.rawValue == 1, "CS_ARCH_AARCH64 should be 1") + #expect(CS_ARCH_X86.rawValue == 4, "CS_ARCH_X86 should be 4") + + // Test mode enum values + #expect(CS_MODE_LITTLE_ENDIAN.rawValue == 0, "CS_MODE_LITTLE_ENDIAN should be 0") + #expect(CS_MODE_16.rawValue == 1 << 1, "CS_MODE_16 should be 2") + #expect(CS_MODE_32.rawValue == 1 << 2, "CS_MODE_32 should be 4") + #expect(CS_MODE_64.rawValue == 1 << 3, "CS_MODE_64 should be 8") + + // Test option enum values + #expect(CS_OPT_INVALID.rawValue == 0, "CS_OPT_INVALID should be 0") + #expect(CS_OPT_DETAIL.rawValue != 0, "CS_OPT_DETAIL should not be 0") + + print("✓ All enum values are correctly defined") + } + + @Test("Error message validation") + func testErrorMessages() async throws { + let testErrors: [(cs_err, String)] = [ + (CS_ERR_OK, "Success"), + (CS_ERR_MEM, "Memory error"), + (CS_ERR_ARCH, "Architecture error"), + (CS_ERR_HANDLE, "Handle error"), + (CS_ERR_CSH, "CSH error"), + (CS_ERR_MODE, "Mode error"), + (CS_ERR_OPTION, "Option error"), + (CS_ERR_DETAIL, "Detail error"), + (CS_ERR_MEMSETUP, "Memory setup error"), + (CS_ERR_VERSION, "Version error") + ] + + print("✓ Testing error messages:") + for (errorCode, description) in testErrors { + let message = cs_strerror(errorCode) + #expect(message != nil, "Error message for \(description) should not be nil") + + if let message = message { + let errorString = String(cString: message) + #expect(!errorString.isEmpty, "Error message for \(description) should not be empty") + print(" \(errorCode.rawValue) (\(description)): \(errorString)") + } + } + } + + @Test("Basic handle operations") + func testBasicHandleOperations() async throws { + var handle: csh = 0 + + // Test invalid architecture + let invalidResult = cs_open(cs_arch(rawValue: 999), CS_MODE_32, &handle) + #expect(invalidResult != CS_ERR_OK, "Invalid architecture should return error") + #expect(handle == 0, "Handle should be 0 for invalid architecture") + + // Test valid operations (if possible) + let validResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + if validResult == CS_ERR_OK { + print("✓ Successfully opened X86 32-bit engine, handle: \(handle)") + #expect(handle != 0, "Handle should not be 0 for valid operations") + + // Test option setting + let _ = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + // Success or failure is acceptable, depends on implementation completeness + + // Test error number retrieval + let _ = cs_errno(handle) + // errno value depends on previous operation results + + // Test close + let closeResult = cs_close(&handle) + #expect(closeResult == CS_ERR_OK, "Handle close should succeed") + #expect(handle == 0, "Handle should be 0 after close") + } else { + let errorMsg = cs_strerror(validResult) + let errorString = errorMsg != nil ? String(cString: errorMsg!) : "Unknown error" + print("⚠️ Unable to open X86 engine: \(errorString)") + print(" This is expected because Swift binding is incomplete") + } + } + + @Test("Invalid operations handling") + func testInvalidOperations() async throws { + // Test operations on invalid handle + let errno = cs_errno(0) + #expect(errno != CS_ERR_OK, "cs_errno on invalid handle should return error") + + let optResult = cs_option(0, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + #expect(optResult != CS_ERR_OK, "cs_option on invalid handle should return error") + + // Test double close - using zero handle which should be safe + var zeroHandle: csh = 0 + let doubleClose = cs_close(&zeroHandle) + #expect(doubleClose != CS_ERR_OK, "Closing zero handle should return error") + + print("✓ Invalid operations correctly return errors") + } + + @Test("Constants and structure sizes") + func testConstantsAndSizes() async throws { + // Test important constant values + #expect(CS_MNEMONIC_SIZE == 32, "CS_MNEMONIC_SIZE should be 32") + + // Test structure sizes + let insnSize = MemoryLayout.size + let detailSize = MemoryLayout.size + + #expect(insnSize > 0, "cs_insn structure size should be greater than 0") + #expect(detailSize > 0, "cs_detail structure size should be greater than 0") + + print("✓ Constants and structure sizes:") + print(" CS_MNEMONIC_SIZE: \(CS_MNEMONIC_SIZE)") + print(" cs_insn size: \(insnSize) bytes") + print(" cs_detail size: \(detailSize) bytes") + + // cs_insn should contain mnemonic, operand strings, etc., should have reasonable size + #expect(insnSize > 32, "cs_insn should be at least 32 bytes") + + // cs_detail contains architecture-specific unions, should be larger + #expect(detailSize > 100, "cs_detail should be at least 100 bytes") + } + + @Test("Function availability check") + func testFunctionAvailability() async throws { + // Verify that all core C functions can be referenced (can be linked) + let functions: [String: Any] = [ + "cs_version": cs_version, + "cs_open": cs_open, + "cs_close": cs_close, + "cs_disasm": cs_disasm, + "cs_malloc": cs_malloc, + "cs_disasm_iter": cs_disasm_iter, + "cs_free": cs_free, + "cs_option": cs_option, + "cs_errno": cs_errno, + "cs_strerror": cs_strerror, + ] + + print("✓ Available C API functions:") + for (name, _) in functions.sorted(by: { $0.key < $1.key }) { + print(" - \(name)") + } + + #expect(functions.count == 10, "Should have 10 basic functions available") + } + + @Test("Version consistency validation") + func testVersionConsistency() async throws { + // Test version function consistency + var major1: Int32 = 0, minor1: Int32 = 0 + var major2: Int32 = 0, minor2: Int32 = 0 + + let version1 = cs_version(&major1, &minor1) + let version2 = cs_version(&major2, &minor2) + + #expect(version1 == version2, "Version should remain consistent") + #expect(major1 == major2, "Major version should remain consistent") + #expect(minor1 == minor2, "Minor version should remain consistent") + + // Test correctness of version calculation + let calculatedVersion = UInt32((major1 << 8) | minor1) + #expect(version1 == calculatedVersion, "Version calculation should be correct") + + print("✓ Version consistency verification passed") + } + + @Test("Edge cases handling") + func testEdgeCases() async throws { + // Test edge cases + + // Test invalid error codes + let invalidError = cs_err(rawValue: 9999) + let invalidMsg = cs_strerror(invalidError) + #expect(invalidMsg != nil, "Invalid error codes should also have messages") + + // Test version with nil parameters + let versionOnly = cs_version(nil, nil) + #expect(versionOnly > 0, "Getting version only should work") + + print("✓ Edge cases handled correctly") + } +} diff --git a/bindings/swift/CcapstoneTests/DetailTests.swift b/bindings/swift/CcapstoneTests/DetailTests.swift new file mode 100644 index 0000000000..592ce717b8 --- /dev/null +++ b/bindings/swift/CcapstoneTests/DetailTests.swift @@ -0,0 +1,272 @@ +import Foundation +import Testing +@testable import Ccapstone + +@Suite struct DetailTests { + @Test func x86Detail() { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_X86, CS_MODE_64, &handle) + #expect(openResult == CS_ERR_OK) + + let optResult = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + #expect(optResult == CS_ERR_OK) + + // mov rax, qword ptr [rip + 0x13b8] + let code: [UInt8] = [0x48, 0x8B, 0x05, 0xB8, 0x13, 0x00, 0x00] + var insns: UnsafeMutablePointer? + + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 1, &insns) + } + + #expect(count == 1) + #expect(insns != nil) + + if count > 0, insns != nil { + let insn = insns![0] + #expect(insn.detail != nil) + + if insn.detail != nil { + let detail = insn.detail!.pointee + let x86Detail = detail.x86 + + // Check basic detail properties + #expect(detail.regs_read_count >= 0) + #expect(detail.regs_write_count >= 0) + #expect(detail.groups_count >= 0) + + // Check X86-specific details + #expect(x86Detail.op_count > 0) + #expect(x86Detail.op_count <= 8) + + print("X86 Detail:") + print(" Instruction ID: \(insn.id)") + print(" Groups count: \(detail.groups_count)") + print(" Regs read count: \(detail.regs_read_count)") + print(" Regs write count: \(detail.regs_write_count)") + print(" Operands count: \(x86Detail.op_count)") + + // Print operand information + let operands = withUnsafeBytes(of: x86Detail.operands) { bytes in + return bytes.bindMemory(to: cs_x86_op.self) + } + + for i in 0 ..< min(Int(x86Detail.op_count), 8) { + let operand = operands[i] + print(" Operand \(i): type=\(operand.type.rawValue), size=\(operand.size)") + } + } + + cs_free(insns, count) + } + + _ = cs_close(&handle) + } + + @Test func aRMDetail() { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_ARM, CS_MODE_ARM, &handle) + #expect(openResult == CS_ERR_OK) + + let optResult = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + #expect(optResult == CS_ERR_OK) + + // str lr, [sp, #-4]! + let code: [UInt8] = [0x04, 0xE0, 0x2D, 0xE5] + var insns: UnsafeMutablePointer? + + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 1, &insns) + } + + #expect(count == 1) + #expect(insns != nil) + + if count > 0, insns != nil { + let insn = insns![0] + #expect(insn.detail != nil) + + if insn.detail != nil { + let detail = insn.detail!.pointee + let armDetail = detail.arm + + // Check basic detail properties + #expect(detail.regs_read_count >= 0) + #expect(detail.regs_write_count >= 0) + #expect(detail.groups_count >= 0) + + // Check ARM-specific details + #expect(armDetail.op_count > 0) + #expect(armDetail.op_count <= 36) + + print("ARM Detail:") + print(" Instruction ID: \(insn.id)") + print(" Groups count: \(detail.groups_count)") + print(" Regs read count: \(detail.regs_read_count)") + print(" Regs write count: \(detail.regs_write_count)") + print(" Operands count: \(armDetail.op_count)") + print(" CC: \(armDetail.cc.rawValue)") + print(" Update flags: \(armDetail.update_flags)") + // print(" Writeback: \(armDetail.writeback)") // Not available in this struct + } + + cs_free(insns, count) + } + + _ = cs_close(&handle) + } + + @Test func aArch64Detail() { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_AARCH64, CS_MODE_ARM, &handle) + #expect(openResult == CS_ERR_OK) + + let optResult = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + #expect(optResult == CS_ERR_OK) + + // add x1, x1, x2 + let code: [UInt8] = [0x21, 0x00, 0x02, 0x8B] + var insns: UnsafeMutablePointer? + + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 1, &insns) + } + + #expect(count == 1) + #expect(insns != nil) + + if count > 0, insns != nil { + let insn = insns![0] + #expect(insn.detail != nil) + + if insn.detail != nil { + let detail = insn.detail!.pointee + let aarch64Detail = detail.aarch64 + + // Check basic detail properties + #expect(detail.regs_read_count >= 0) + #expect(detail.regs_write_count >= 0) + #expect(detail.groups_count >= 0) + + // Check AArch64-specific details + #expect(aarch64Detail.op_count > 0) + #expect(aarch64Detail.op_count <= 8) + + print("AArch64 Detail:") + print(" Instruction ID: \(insn.id)") + print(" Groups count: \(detail.groups_count)") + print(" Regs read count: \(detail.regs_read_count)") + print(" Regs write count: \(detail.regs_write_count)") + print(" Operands count: \(aarch64Detail.op_count)") + print(" CC: \(aarch64Detail.cc.rawValue)") + print(" Update flags: \(aarch64Detail.update_flags)") + // print(" Writeback: \(aarch64Detail.writeback)") // Not available in this struct + } + + cs_free(insns, count) + } + + _ = cs_close(&handle) + } + + @Test func mIPSDetail() { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_MIPS, cs_mode(CS_MODE_MIPS32.rawValue | CS_MODE_BIG_ENDIAN.rawValue), &handle) + #expect(openResult == CS_ERR_OK) + + let optResult = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + #expect(optResult == CS_ERR_OK) + + // jal 0x97000c + let code: [UInt8] = [0x0C, 0x10, 0x00, 0x97] + var insns: UnsafeMutablePointer? + + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 1, &insns) + } + + #expect(count == 1) + #expect(insns != nil) + + if count > 0, insns != nil { + let insn = insns![0] + #expect(insn.detail != nil) + + if insn.detail != nil { + let detail = insn.detail!.pointee + let mipsDetail = detail.mips + + // Check basic detail properties + #expect(detail.regs_read_count >= 0) + #expect(detail.regs_write_count >= 0) + #expect(detail.groups_count >= 0) + + // Check MIPS-specific details + #expect(mipsDetail.op_count > 0) + #expect(mipsDetail.op_count <= 4) + + print("MIPS Detail:") + print(" Instruction ID: \(insn.id)") + print(" Groups count: \(detail.groups_count)") + print(" Regs read count: \(detail.regs_read_count)") + print(" Regs write count: \(detail.regs_write_count)") + print(" Operands count: \(mipsDetail.op_count)") + } + + cs_free(insns, count) + } + + _ = cs_close(&handle) + } + + @Test func instructionGroups() { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_X86, CS_MODE_64, &handle) + #expect(openResult == CS_ERR_OK) + + let optResult = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + #expect(optResult == CS_ERR_OK) + + // ret instruction + let code: [UInt8] = [0xC3] + var insns: UnsafeMutablePointer? + + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 1, &insns) + } + + #expect(count == 1) + #expect(insns != nil) + + if count > 0, insns != nil { + let insn = insns![0] + #expect(insn.detail != nil) + + if insn.detail != nil { + let detail = insn.detail!.pointee + + print("Instruction groups:") + print(" Groups count: \(detail.groups_count)") + + // Check if this instruction belongs to some groups + let groups = withUnsafeBytes(of: detail.groups) { bytes in + return bytes.bindMemory(to: UInt8.self) + } + + for i in 0 ..< min(Int(detail.groups_count), 16) { + let group = groups[i] + print(" Group \(i): \(group)") + } + } + + cs_free(insns, count) + } + + _ = cs_close(&handle) + } +} diff --git a/bindings/swift/CcapstoneTests/DisassemblyTests.swift b/bindings/swift/CcapstoneTests/DisassemblyTests.swift new file mode 100644 index 0000000000..af361f49da --- /dev/null +++ b/bindings/swift/CcapstoneTests/DisassemblyTests.swift @@ -0,0 +1,224 @@ +import Foundation +import Testing +@testable import Ccapstone + +@Suite struct DisassemblyTests { + + // Test data from existing tests + private let x86Code32: [UInt8] = [0x8d, 0x4c, 0x32, 0x08, 0x01, 0xd8, 0x81, 0xc6, 0x34, 0x12, 0x00, 0x00] + private let x86Code64: [UInt8] = [0x55, 0x48, 0x8b, 0x05, 0xb8, 0x13, 0x00, 0x00] + private let armCode: [UInt8] = [0xED, 0xFF, 0xFF, 0xEB, 0x04, 0xe0, 0x2d, 0xe5, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x83, 0x22, 0xe5] + private let thumbCode: [UInt8] = [0x70, 0x47, 0xeb, 0x46, 0x83, 0xb0, 0xc9, 0x68] + private let aarch64Code: [UInt8] = [0x21, 0x7c, 0x02, 0x9b, 0x21, 0x7c, 0x00, 0x53, 0x00, 0x40, 0x21, 0x4b, 0xe1, 0x0b, 0x40, 0xb9] + private let mipsCode: [UInt8] = [0x0C, 0x10, 0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x24, 0x02, 0x00, 0x0c, 0x8f, 0xa2, 0x00, 0x00] + private let ppcCode: [UInt8] = [0x80, 0x20, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x10, 0x43, 0x23, 0x0e, 0xd0, 0x44, 0x00, 0x80] + + @Test func x86_32() { + performDisassemblyTest( + arch: CS_ARCH_X86, + mode: CS_MODE_32, + code: x86Code32, + comment: "X86 32bit" + ) + } + + @Test func x86_64() { + performDisassemblyTest( + arch: CS_ARCH_X86, + mode: CS_MODE_64, + code: x86Code64, + comment: "X86 64bit" + ) + } + + @Test func aRM() { + performDisassemblyTest( + arch: CS_ARCH_ARM, + mode: CS_MODE_ARM, + code: armCode, + comment: "ARM" + ) + } + + @Test func thumb() { + performDisassemblyTest( + arch: CS_ARCH_ARM, + mode: CS_MODE_THUMB, + code: thumbCode, + comment: "Thumb" + ) + } + + @Test func aArch64() { + performDisassemblyTest( + arch: CS_ARCH_AARCH64, + mode: CS_MODE_ARM, + code: aarch64Code, + comment: "AArch64" + ) + } + + @Test func mIPS() { + performDisassemblyTest( + arch: CS_ARCH_MIPS, + mode: cs_mode(CS_MODE_MIPS32.rawValue | CS_MODE_BIG_ENDIAN.rawValue), + code: mipsCode, + comment: "MIPS32 Big-endian" + ) + } + + @Test func pPC() { + performDisassemblyTest( + arch: CS_ARCH_PPC, + mode: CS_MODE_BIG_ENDIAN, + code: ppcCode, + comment: "PowerPC" + ) + } + + @Test func disasmWithDetail() { + var handle: csh = 0 + var insns: UnsafeMutablePointer? + + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + #expect(openResult == CS_ERR_OK) + + let optResult = cs_option(handle, CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)) + #expect(optResult == CS_ERR_OK) + + let count = x86Code32.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + #expect(count > 0) + #expect(insns != nil) + + if count > 0 && insns != nil { + for i in 0.. 0) + #expect(insn.mnemonic.0 != 0) // Check first character is not null // Check first character is not null + + if insn.detail != nil { + let detail = insn.detail!.pointee + // Detail structure is valid (we don't check specific contents as they vary) + _ = detail + } + } + + cs_free(insns, count) + } + + _ = cs_close(&handle) + } + + @Test func iteratorAPI() { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + #expect(openResult == CS_ERR_OK) + + let insn = cs_malloc(handle) + #expect(insn != nil) + + let code = x86Code32 + var size = code.count + var address: UInt64 = 0x1000 + + var count = 0 + let result = code.withUnsafeBufferPointer { buffer in + var codePtr = buffer.baseAddress + while cs_disasm_iter(handle, &codePtr, &size, &address, insn) { + let instruction = insn!.pointee + #expect(instruction.size > 0) + #expect(instruction.mnemonic.0 != 0) + count += 1 + } + return count + } + + #expect(result > 0) + + cs_free(insn, 1) + _ = cs_close(&handle) + } + + @Test func invalidCode() { + var handle: csh = 0 + var insns: UnsafeMutablePointer? + + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + #expect(openResult == CS_ERR_OK) + + // Test with invalid/empty code + let invalidCode: [UInt8] = [] + let count = invalidCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + #expect(count == 0) + + _ = cs_close(&handle) + } + + @Test func skipData() { + var handle: csh = 0 + + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + #expect(openResult == CS_ERR_OK) + + let skipDataResult = cs_option(handle, CS_OPT_SKIPDATA, size_t(CS_OPT_ON.rawValue)) + #expect(skipDataResult == CS_ERR_OK) + + // Test with some invalid bytes mixed in + let mixedCode: [UInt8] = [0xFF, 0xFF, 0xFF, 0xFF] + x86Code32 + var insns: UnsafeMutablePointer? + + let count = mixedCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + #expect(count > 0) + + if count > 0 && insns != nil { + cs_free(insns, count) + } + + _ = cs_close(&handle) + } + + // Helper function for basic disassembly tests + private func performDisassemblyTest(arch: cs_arch, mode: cs_mode, code: [UInt8], comment: String) { + var handle: csh = 0 + var insns: UnsafeMutablePointer? + + let openResult = cs_open(arch, mode, &handle) + #expect(openResult == CS_ERR_OK, "Failed to open \(comment)") + + let count = code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + #expect(count > 0, "No instructions disassembled for \(comment)") + #expect(insns != nil, "Instructions pointer is nil for \(comment)") + + if count > 0 && insns != nil { + print("\n\(comment):") + for i in 0.. 0) + #expect(insn.mnemonic.0 != 0) // Check first character is not null // Check first character is not null + } + + cs_free(insns, count) + } + + _ = cs_close(&handle) + } +} diff --git a/bindings/swift/CcapstoneTests/MinimalTests.swift b/bindings/swift/CcapstoneTests/MinimalTests.swift new file mode 100644 index 0000000000..9ef7961d73 --- /dev/null +++ b/bindings/swift/CcapstoneTests/MinimalTests.swift @@ -0,0 +1,161 @@ +import Foundation +import Testing +@testable import Ccapstone + +/// Minimal tests that only use core Capstone functions +/// These tests avoid architecture-specific functionality that requires complete linking +@Suite struct MinimalTests { + + @Test func versionAPI() { + var major: Int32 = 0 + var minor: Int32 = 0 + + let version = cs_version(&major, &minor) + + #expect(major > 0, "Major version should be positive") + #expect(minor >= 0, "Minor version should be non-negative") + #expect(version > 0, "Combined version should be positive") + #expect(version == UInt32((major << 8) | minor), "Version should match expected format") + + print("✓ Capstone version: \(major).\(minor) (0x\(String(format: "%04x", version)))") + } + + @Test func errorStrings() { + let testErrors: [(cs_err, String)] = [ + (CS_ERR_OK, "OK"), + (CS_ERR_MEM, "Memory"), + (CS_ERR_ARCH, "Architecture"), + (CS_ERR_HANDLE, "Handle"), + (CS_ERR_CSH, "CSH"), + (CS_ERR_MODE, "Mode"), + (CS_ERR_OPTION, "Option") + ] + + print("\n✓ Testing error strings:") + for (errorCode, description) in testErrors { + let message = cs_strerror(errorCode) + #expect(message != nil, "Error message should not be nil for \(description)") + + if let message = message { + let errorString = String(cString: message) + #expect(!errorString.isEmpty, "Error message should not be empty for \(description)") + print(" \(errorCode.rawValue) (\(description)): \(errorString)") + } + } + } + + @Test func basicEnumValues() { + // Test that enum values are accessible and have expected values + #expect(CS_ERR_OK.rawValue == 0, "CS_ERR_OK should be 0") + #expect(CS_ERR_ARCH.rawValue != 0, "CS_ERR_ARCH should not be 0") + + // Test architecture enum values + #expect(CS_ARCH_ARM.rawValue == 0, "CS_ARCH_ARM should be 0") + #expect(CS_ARCH_AARCH64.rawValue == 1, "CS_ARCH_AARCH64 should be 1") + #expect(CS_ARCH_MIPS.rawValue == 3, "CS_ARCH_MIPS should be 3") + #expect(CS_ARCH_X86.rawValue == 4, "CS_ARCH_X86 should be 4") + + // Test mode enum values + #expect(CS_MODE_LITTLE_ENDIAN.rawValue == 0, "CS_MODE_LITTLE_ENDIAN should be 0") + #expect(CS_MODE_ARM.rawValue == 0, "CS_MODE_ARM should be 0") + #expect(CS_MODE_16.rawValue == 1 << 1, "CS_MODE_16 should be 2") + #expect(CS_MODE_32.rawValue == 1 << 2, "CS_MODE_32 should be 4") + #expect(CS_MODE_64.rawValue == 1 << 3, "CS_MODE_64 should be 8") + + // Test option enum values + #expect(CS_OPT_INVALID.rawValue == 0, "CS_OPT_INVALID should be 0") + #expect(CS_OPT_DETAIL.rawValue != 0, "CS_OPT_DETAIL should not be 0") + + // Test option values + #expect(CS_OPT_OFF.rawValue == 0, "CS_OPT_OFF should be 0") + #expect(CS_OPT_ON.rawValue == 1 << 0, "CS_OPT_ON should be 1") + + print("✓ All enum values are correctly defined") + } + + @Test func aPIFunctionAvailability() { + // Test that all basic C functions are available (can be referenced) + // This doesn't call them, just verifies they exist and can be linked + + let functions: [String: Any] = [ + "cs_version": cs_version, + "cs_open": cs_open, + "cs_close": cs_close, + "cs_disasm": cs_disasm, + "cs_malloc": cs_malloc, + "cs_disasm_iter": cs_disasm_iter, + "cs_free": cs_free, + "cs_option": cs_option, + "cs_errno": cs_errno, + "cs_strerror": cs_strerror, + ] + + print("✓ Available C API functions:") + for (name, _) in functions.sorted(by: { $0.key < $1.key }) { + print(" - \(name)") + } + + #expect(functions.count == 10, "All 10 basic functions should be available") + } + + @Test func invalidParameters() { + // Test behavior with obviously invalid parameters + // These should not crash but return error codes + + // Test cs_strerror with invalid error code + let invalidErrorMsg = cs_strerror(cs_err(rawValue: 999)) + #expect(invalidErrorMsg != nil, "cs_strerror should handle invalid error codes") + + // Test cs_errno with invalid handle + let errno = cs_errno(0) // 0 is invalid handle + #expect(errno != CS_ERR_OK, "cs_errno should return error for invalid handle") + + print("✓ Invalid parameter handling works correctly") + } + + @Test func constantValues() { + // Test important constant values + #expect(CS_MNEMONIC_SIZE == 32, "CS_MNEMONIC_SIZE should be 32") + + print("✓ Constant values are correct") + print(" CS_MNEMONIC_SIZE: \(CS_MNEMONIC_SIZE)") + } + + @Test func structureSizes() { + // Test that key structures have reasonable sizes + let insnSize = MemoryLayout.size + let detailSize = MemoryLayout.size + + #expect(insnSize > 0, "cs_insn structure should have positive size") + #expect(detailSize > 0, "cs_detail structure should have positive size") + + print("✓ Structure sizes:") + print(" cs_insn: \(insnSize) bytes") + print(" cs_detail: \(detailSize) bytes") + + // cs_insn should be reasonably sized (has mnemonic, op_str, etc.) + #expect(insnSize > 64, "cs_insn should be at least 64 bytes") + + // cs_detail should be large (has architecture-specific unions) + #expect(detailSize > 100, "cs_detail should be at least 100 bytes") + } + + @Test func versionConsistency() { + // Test version consistency across different calls + var major1: Int32 = 0, minor1: Int32 = 0 + var major2: Int32 = 0, minor2: Int32 = 0 + + let version1 = cs_version(&major1, &minor1) + let version2 = cs_version(&major2, &minor2) + + #expect(version1 == version2, "Version should be consistent") + #expect(major1 == major2, "Major version should be consistent") + #expect(minor1 == minor2, "Minor version should be consistent") + + // Test CS_MAKE_VERSION macro equivalent + let calculatedVersion = UInt32((major1 << 8) | minor1) + #expect(version1 == calculatedVersion, "Version calculation should match macro") + + print("✓ Version consistency verified") + } +} diff --git a/bindings/swift/CcapstoneTests/PerformanceTests.swift b/bindings/swift/CcapstoneTests/PerformanceTests.swift new file mode 100644 index 0000000000..3223ef3933 --- /dev/null +++ b/bindings/swift/CcapstoneTests/PerformanceTests.swift @@ -0,0 +1,318 @@ +import Foundation +import Testing +@testable import Ccapstone + +/// Performance and stress tests for the Capstone Swift binding +/// These tests verify performance characteristics and stress the API under various conditions +@Suite("Performance Tests") +struct PerformanceTests { + + // Test data for performance testing + private static let x86Code32: [UInt8] = [0x8d, 0x4c, 0x32, 0x08, 0x01, 0xd8, 0x81, 0xc6, 0x34, 0x12, 0x00, 0x00] + private static let x86Code64: [UInt8] = [0x55, 0x48, 0x8b, 0x05, 0xb8, 0x13, 0x00, 0x00] + private static let armCode: [UInt8] = [0xED, 0xFF, 0xFF, 0xEB, 0x04, 0xe0, 0x2d, 0xe5, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x83, 0x22, 0xe5] + + /// Large code block for stress testing + private static let largeCodeBlock: [UInt8] = { + var code = [UInt8]() + // Repeat the x86 32-bit code 1000 times for stress testing + for _ in 0..<1000 { + code.append(contentsOf: x86Code32) + } + return code + }() + + @Test("Multiple handle creation and destruction performance") + func testHandleCreationPerformance() async throws { + let iterations = 1000 + let startTime = CFAbsoluteTimeGetCurrent() + + for _ in 0..? + let count = Self.largeCodeBlock.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + let endTime = CFAbsoluteTimeGetCurrent() + let totalTime = endTime - startTime + + defer { + if count > 0 { + cs_free(insns, count) + } + } + + print("✓ Large code block disassembly:") + print(" Code size: \(Self.largeCodeBlock.count) bytes") + print(" Instructions disassembled: \(count)") + print(" Time taken: \(String(format: "%.4f", totalTime)) seconds") + + if count > 0 { + let throughput = Double(Self.largeCodeBlock.count) / totalTime / 1024 / 1024 // MB/s + print(" Throughput: \(String(format: "%.2f", throughput)) MB/s") + + // Basic performance expectation - should process at least 1MB/s + #expect(throughput > 1.0, "Should have reasonable throughput") + } + + #expect(count >= 0, "Should not fail catastrophically") + } + + @Test("Iterator API performance comparison") + func testIteratorVsBatchDisassembly() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping iterator performance test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + let testCode = Self.x86Code32 + + // Test batch disassembly performance + let batchStartTime = CFAbsoluteTimeGetCurrent() + var insns: UnsafeMutablePointer? + let batchCount = testCode.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + let batchEndTime = CFAbsoluteTimeGetCurrent() + let batchTime = batchEndTime - batchStartTime + + if batchCount > 0 { + cs_free(insns, batchCount) + } + + // Test iterator API performance + let iterStartTime = CFAbsoluteTimeGetCurrent() + let insn = cs_malloc(handle) + var iterCount: Int = 0 + + if let insn = insn { + testCode.withUnsafeBufferPointer { buffer in + var codePtr = buffer.baseAddress + var size = testCode.count + var address: UInt64 = 0x1000 + + while cs_disasm_iter(handle, &codePtr, &size, &address, insn) { + iterCount += 1 + } + } + cs_free(insn, 1) + } + + let iterEndTime = CFAbsoluteTimeGetCurrent() + let iterTime = iterEndTime - iterStartTime + + print("✓ API performance comparison:") + print(" Batch API: \(batchCount) instructions in \(String(format: "%.6f", batchTime)) seconds") + print(" Iterator API: \(iterCount) instructions in \(String(format: "%.6f", iterTime)) seconds") + + #expect(batchCount == iterCount, "Both methods should find same number of instructions") + } + + @Test("Memory allocation stress test") + func testMemoryAllocationStress() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping memory stress test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + let allocations = 1000 + var allocatedPointers: [UnsafeMutablePointer] = [] + + // Allocate many instruction structures + for _ in 0.. allocations / 2, "Should successfully allocate most structures") + } + + @Test("Rapid open/close cycles") + func testRapidOpenCloseCycles() async throws { + let cycles = 10000 + var successfulCycles = 0 + let architectures: [(cs_arch, cs_mode)] = [ + (CS_ARCH_X86, CS_MODE_32), + (CS_ARCH_X86, CS_MODE_64), + (CS_ARCH_ARM, CS_MODE_ARM), + (CS_ARCH_ARM, CS_MODE_THUMB) + ] + + let startTime = CFAbsoluteTimeGetCurrent() + + for i in 0.. 0, "Should complete some cycles successfully") + } + + @Test("Option setting performance") + func testOptionSettingPerformance() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping option performance test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + let iterations = 10000 + let options: [(cs_opt_type, size_t)] = [ + (CS_OPT_DETAIL, size_t(CS_OPT_ON.rawValue)), + (CS_OPT_DETAIL, size_t(CS_OPT_OFF.rawValue)), + ] + + let startTime = CFAbsoluteTimeGetCurrent() + var successfulOperations = 0 + + for i in 0.. 0 { + let avgTime = totalTime / Double(successfulOperations) * 1000000 // Convert to microseconds + print(" Average time per operation: \(String(format: "%.2f", avgTime)) μs") + } + } + + @Test("Concurrent access safety test") + func testConcurrentAccess() async throws { + // Note: This test creates separate handles for concurrent access + // Each handle should be used from only one thread at a time + + let concurrentTasks = 10 + let operationsPerTask = 100 + + await withTaskGroup(of: Int.self) { group in + for _ in 0..? + let count = Self.x86Code32.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + if count > 0 { + cs_free(insns, count) + successfulOperations += 1 + } + + _ = cs_close(&handle) + } + } + + return successfulOperations + } + } + + var totalSuccessful = 0 + for await result in group { + totalSuccessful += result + } + + let totalOperations = concurrentTasks * operationsPerTask + print("✓ Concurrent access test:") + print(" Total operations: \(totalOperations)") + print(" Successful operations: \(totalSuccessful)") + print(" Success rate: \(String(format: "%.1f", Double(totalSuccessful) / Double(totalOperations) * 100))%") + + // Should handle concurrent operations reasonably well + #expect(totalSuccessful > 0, "Should complete some operations successfully") + } + } +} \ No newline at end of file diff --git a/bindings/swift/CcapstoneTests/SimpleTests.swift b/bindings/swift/CcapstoneTests/SimpleTests.swift new file mode 100644 index 0000000000..71d41c03ed --- /dev/null +++ b/bindings/swift/CcapstoneTests/SimpleTests.swift @@ -0,0 +1,96 @@ +import Foundation +import Testing +@testable import Ccapstone + +@Suite struct SimpleTests { + + @Test func versionOnly() { + var major: Int32 = 0 + var minor: Int32 = 0 + + let version = cs_version(&major, &minor) + + #expect(major > 0) + #expect(minor >= 0) + #expect(version > 0) + #expect(version == UInt32((major << 8) | minor)) + + print("Capstone version: \(major).\(minor) (0x\(String(format: "%04x", version)))") + } + + @Test func basicAPIAvailability() { + // Test that all basic functions are available + // This doesn't actually call them, just verifies they can be referenced + let _ = cs_open + let _ = cs_close + let _ = cs_disasm + let _ = cs_malloc + let _ = cs_disasm_iter + let _ = cs_free + let _ = cs_option + let _ = cs_errno + let _ = cs_strerror + let _ = cs_version + + // Test enum availability + let _ = CS_ARCH_X86 + let _ = CS_MODE_32 + let _ = CS_ERR_OK + let _ = CS_OPT_DETAIL + let _ = CS_OPT_ON + + #expect(true, "All basic API functions are available") + } + + @Test func errorMessages() { + let errorCodes: [cs_err] = [ + CS_ERR_OK, + CS_ERR_MEM, + CS_ERR_ARCH, + CS_ERR_HANDLE, + CS_ERR_CSH, + CS_ERR_MODE, + CS_ERR_OPTION + ] + + print("Testing error messages:") + for errorCode in errorCodes { + let message = cs_strerror(errorCode) + #expect(message != nil, "Error message should not be nil for \(errorCode)") + + if let message = message { + let errorString = String(cString: message) + #expect(!errorString.isEmpty, "Error message should not be empty for \(errorCode)") + print(" \(errorCode.rawValue): \(errorString)") + } + } + } + + @Test func simpleOpenClose() { + var handle: csh = 0 + + print("Testing cs_open...") + let result = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + if result == CS_ERR_OK { + print("✓ cs_open succeeded, handle: \(handle)") + #expect(handle != 0, "Handle should not be zero") + + print("Testing cs_close...") + let closeResult = cs_close(&handle) + if closeResult == CS_ERR_OK { + print("✓ cs_close succeeded") + #expect(handle == 0, "Handle should be zero after close") + } else { + let errorMsg = cs_strerror(closeResult) + let errorString = errorMsg != nil ? String(cString: errorMsg!) : "Unknown error" + print("✗ cs_close failed: \(errorString)") + Issue.record("cs_close failed with error: \(errorString)") + } + } else { + let errorMsg = cs_strerror(result) + let errorString = errorMsg != nil ? String(cString: errorMsg!) : "Unknown error" + print("✗ cs_open failed: \(errorString)") + Issue.record("cs_open failed with error: \(errorString)") + } + } +} diff --git a/bindings/swift/CcapstoneTests/SwiftIntegrationTests.swift b/bindings/swift/CcapstoneTests/SwiftIntegrationTests.swift new file mode 100644 index 0000000000..9e8dd02f29 --- /dev/null +++ b/bindings/swift/CcapstoneTests/SwiftIntegrationTests.swift @@ -0,0 +1,542 @@ +import Foundation +import Testing +@testable import Ccapstone + +/// Swift-specific integration tests for the Capstone binding +/// These tests verify Swift-specific patterns, safety features, and idiomatic usage +@Suite("Swift Integration Tests") +struct SwiftIntegrationTests { + // Test data + private static let x86Code: [UInt8] = [0x8D, 0x4C, 0x32, 0x08, 0x01, 0xD8, 0x81, 0xC6, 0x34, 0x12, 0x00, 0x00] + + @Test("Swift optionals and error handling") + func swiftOptionals() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping Swift optionals test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + print("✓ Swift optionals and error handling:") + + // Test cs_malloc returning optional + let insn = cs_malloc(handle) + if let insn = insn { + print(" cs_malloc returned valid pointer: \(insn)") + cs_free(insn, 1) + } else { + print(" cs_malloc returned nil") + } + + // Test cs_strerror returning optional + let errorMessage = cs_strerror(CS_ERR_OK) + if let message = errorMessage { + let errorString = String(cString: message) + print(" Error message: '\(errorString)'") + #expect(!errorString.isEmpty, "Error message should not be empty") + } else { + #expect(Bool(false), "cs_strerror should not return nil") + } + } + + @Test("Swift memory management patterns") + func swiftMemoryManagement() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping memory management test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + print("✓ Swift memory management patterns:") + + // Test RAII-style pattern with defer + func performDisassemblyWithDefer() -> size_t { + var insns: UnsafeMutablePointer? + let count = Self.x86Code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + defer { + if count > 0 { + cs_free(insns, count) + } + } + + // Simulate some processing + if count > 0 { + let firstInsn = insns!.pointee + _ = withUnsafeBytes(of: firstInsn.mnemonic) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + } + + return count + } + + let deferCount = performDisassemblyWithDefer() + print(" RAII-style pattern with defer: \(deferCount) instructions") + + // Test withUnsafeBufferPointer pattern + let bufferCount = Self.x86Code.withUnsafeBufferPointer { buffer -> size_t in + var insns: UnsafeMutablePointer? + let count = cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + defer { + if count > 0 { + cs_free(insns, count) + } + } + return count + } + + print(" withUnsafeBufferPointer pattern: \(bufferCount) instructions") + #expect(deferCount == bufferCount, "Both patterns should yield same results") + + // Test iterator pattern with automatic cleanup + func iteratorPattern() -> Int { + guard let insn = cs_malloc(handle) else { + return 0 + } + defer { cs_free(insn, 1) } + + var instructionCount = 0 + Self.x86Code.withUnsafeBufferPointer { buffer in + var codePtr = buffer.baseAddress + var size = Self.x86Code.count + var address: UInt64 = 0x1000 + + while cs_disasm_iter(handle, &codePtr, &size, &address, insn) { + instructionCount += 1 + let mnemonic = withUnsafeBytes(of: insn.pointee.mnemonic) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + let _ = mnemonic // Use the value + + if instructionCount > 100 { // Safety break + break + } + } + } + + return instructionCount + } + + let iteratorCount = iteratorPattern() + print(" Iterator pattern with automatic cleanup: \(iteratorCount) instructions") + } + + @Test("Swift string handling and C interop") + func swiftStringHandling() async throws { + print("✓ Swift string handling and C interop:") + + // Test converting C strings to Swift strings + let testErrors: [cs_err] = [CS_ERR_OK, CS_ERR_MEM, CS_ERR_ARCH, CS_ERR_HANDLE] + + for errorCode in testErrors { + if let cMessage = cs_strerror(errorCode) { + let swiftString = String(cString: cMessage) + + // Test Swift string operations + let uppercased = swiftString.uppercased() + let length = swiftString.count + let utf8Count = swiftString.utf8.count + + print(" Error \(errorCode.rawValue): '\(swiftString)' -> '\(uppercased)' (\(length)/\(utf8Count) chars)") + + #expect(!swiftString.isEmpty, "Swift string should not be empty") + #expect(length > 0, "String length should be positive") + #expect(utf8Count >= length, "UTF-8 byte count should be >= character count") + } + } + + // Test version information as Swift strings + var major: Int32 = 0 + var minor: Int32 = 0 + let version = cs_version(&major, &minor) + + let versionString = String(format: "%d.%d", major, minor) + let hexVersionString = String(format: "0x%04x", version) + + print(" Version: \(versionString) (\(hexVersionString))") + + #expect(versionString.count >= 3, "Version string should be at least X.Y") + #expect(hexVersionString.hasPrefix("0x"), "Hex version should start with 0x") + } + + @Test("Swift array and collection integration") + func swiftCollectionIntegration() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping collection integration test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + print("✓ Swift array and collection integration:") + + // Test with different array types + let testData: [String: [UInt8]] = [ + "x86_32": [0x8D, 0x4C, 0x32, 0x08], + "nops": [0x90, 0x90, 0x90, 0x90], + "empty": [], + "single": [0x90], + ] + + for (name, codeArray) in testData { + let count = codeArray.withUnsafeBufferPointer { buffer -> size_t in + var insns: UnsafeMutablePointer? + let result = cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + defer { + if result > 0 { + cs_free(insns, result) + } + } + return result + } + + print(" \(name) array (\(codeArray.count) bytes): \(count) instructions") + } + + // Test using Array methods + let extendedCode = Self.x86Code + Self.x86Code // Array concatenation + let count = extendedCode.withUnsafeBufferPointer { buffer -> size_t in + var insns: UnsafeMutablePointer? + let result = cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + defer { + if result > 0 { + cs_free(insns, result) + } + } + return result + } + + print(" Extended array (\(extendedCode.count) bytes): \(count) instructions") + + // Test with ArraySlice + if extendedCode.count > 4 { + let slice = extendedCode[2 ..< 6] // Take a slice + let sliceArray = Array(slice) // Convert slice to array for withUnsafeBufferPointer + + let sliceCount = sliceArray.withUnsafeBufferPointer { buffer -> size_t in + var insns: UnsafeMutablePointer? + let result = cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + defer { + if result > 0 { + cs_free(insns, result) + } + } + return result + } + + print(" Array slice (\(sliceArray.count) bytes): \(sliceCount) instructions") + } + } + + @Test("Swift error handling patterns") + func swiftErrorHandling() async throws { + print("✓ Swift error handling patterns:") + + // Define a Swift wrapper that throws + enum CapstoneError: Error, CustomStringConvertible { + case openFailed(cs_err) + case disassemblyFailed(cs_err) + case invalidHandle + + var description: String { + switch self { + case .openFailed(let err): + if let msg = cs_strerror(err) { + return "Open failed: \(String(cString: msg))" + } + return "Open failed: \(err)" + case .disassemblyFailed(let err): + return "Disassembly failed: \(err)" + case .invalidHandle: + return "Invalid handle" + } + } + } + + func safeOpen(arch: cs_arch, mode: cs_mode) throws -> csh { + var handle: csh = 0 + let result = cs_open(arch, mode, &handle) + guard result == CS_ERR_OK else { + throw CapstoneError.openFailed(result) + } + return handle + } + + func safeDisassemble(handle: csh, code: [UInt8]) throws -> Int { + guard handle != 0 else { + throw CapstoneError.invalidHandle + } + + return code.withUnsafeBufferPointer { buffer -> Int in + var insns: UnsafeMutablePointer? + let count = cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + defer { + if count > 0 { + cs_free(insns, count) + } + } + return Int(count) + } + } + + // Test successful case + do { + let handle = try safeOpen(arch: CS_ARCH_X86, mode: CS_MODE_32) + defer { + var mutableHandle = handle + _ = cs_close(&mutableHandle) + } + + let count = try safeDisassemble(handle: handle, code: Self.x86Code) + print(" Successful operation: \(count) instructions") + + } catch { + print(" Expected success case failed: \(error)") + } + + // Test failure case + do { + _ = try safeOpen(arch: cs_arch(rawValue: 999), mode: CS_MODE_32) + print(" Expected failure case succeeded (unexpected)") + } catch let error as CapstoneError { + print(" Expected failure caught: \(error)") + } catch { + print(" Unexpected error type: \(error)") + } + + // Test invalid handle + do { + _ = try safeDisassemble(handle: 0, code: Self.x86Code) + print(" Invalid handle case succeeded (unexpected)") + } catch let error as CapstoneError { + print(" Invalid handle error caught: \(error)") + } catch { + print(" Unexpected error for invalid handle: \(error)") + } + } + + @Test("Swift value types and reference types") + func swiftValueAndReferenceTypes() async throws { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + print("⚠️ Skipping value/reference types test - cannot open engine") + return + } + + defer { _ = cs_close(&handle) } + + print("✓ Swift value types and reference types:") + + // Test copying instruction data to Swift value types + struct SwiftInstruction { + var address: UInt64 + let size: UInt16 + let mnemonic: String + let operands: String + let bytes: [UInt8] + + init(from csInsn: cs_insn) { + self.address = csInsn.address + self.size = csInsn.size + self.mnemonic = withUnsafeBytes(of: csInsn.mnemonic) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + self.operands = withUnsafeBytes(of: csInsn.op_str) { bytes in + String(cString: bytes.bindMemory(to: CChar.self).baseAddress!) + } + + // Copy bytes (simplified - in real implementation would copy actual bytes) + var byteArray: [UInt8] = [] + for _ in 0 ..< min(Int(csInsn.size), 16) { // Limit to reasonable size + // Note: This is simplified - real implementation would access csInsn.bytes properly + byteArray.append(0x90) // Placeholder + } + self.bytes = byteArray + } + } + + var swiftInstructions: [SwiftInstruction] = [] + + var insns: UnsafeMutablePointer? + let count = Self.x86Code.withUnsafeBufferPointer { buffer in + cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + } + + defer { + if count > 0 { + cs_free(insns, count) + } + } + + // Convert C structures to Swift value types + if count > 0 { + for i in 0 ..< count { + let csInsn = (insns! + i).pointee + let swiftInsn = SwiftInstruction(from: csInsn) + swiftInstructions.append(swiftInsn) + } + } + + print(" Converted \(swiftInstructions.count) instructions to Swift value types") + + // Test Swift collection operations on converted data + let mnemonics = swiftInstructions.map { $0.mnemonic } + let addresses = swiftInstructions.map { $0.address } + let totalBytes = swiftInstructions.reduce(0) { $0 + Int($1.size) } + + print(" Mnemonics: \(mnemonics)") + print(" Addresses: \(addresses.map { String(format: "0x%llx", $0) })") + print(" Total instruction bytes: \(totalBytes)") + + // Test value semantics + if var firstInstruction = swiftInstructions.first { + let originalAddress = firstInstruction.address + firstInstruction.address = 0x2000 // This shouldn't affect the array + + let arrayAddress = swiftInstructions.first?.address ?? 0 + #expect(originalAddress == arrayAddress, "Value types should maintain independence") + print(" Value type semantics: ✓ (original: 0x\(String(format: "%llx", originalAddress)), array: 0x\(String(format: "%llx", arrayAddress)))") + } + } + + @Test("Swift async/await compatibility") + func asyncAwaitCompatibility() async throws { + print("✓ Swift async/await compatibility:") + + // Test that Capstone operations can be used in async contexts + func asyncDisassembly() async -> Int { + var handle: csh = 0 + let openResult = cs_open(CS_ARCH_X86, CS_MODE_32, &handle) + + guard openResult == CS_ERR_OK else { + return -1 + } + + defer { _ = cs_close(&handle) } + + // Simulate some async work + try? await Task.sleep(nanoseconds: 1_000_000) // 1ms + + return Self.x86Code.withUnsafeBufferPointer { buffer -> Int in + var insns: UnsafeMutablePointer? + let count = cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + defer { + if count > 0 { + cs_free(insns, count) + } + } + return Int(count) + } + } + + let result = await asyncDisassembly() + print(" Async disassembly result: \(result) instructions") + + // Test concurrent async operations + async let result1 = asyncDisassembly() + async let result2 = asyncDisassembly() + async let result3 = asyncDisassembly() + + let results = await [result1, result2, result3] + print(" Concurrent async results: \(results)") + + // All should succeed or fail consistently + let successfulResults = results.filter { $0 >= 0 } + if !successfulResults.isEmpty { + let allSame = successfulResults.allSatisfy { $0 == successfulResults.first } + #expect(allSame, "Concurrent operations should yield same results") + print(" Concurrent consistency: \(allSame ? "✓" : "✗")") + } + } + + @Test("Swift generic and protocol integration") + func genericAndProtocolIntegration() async throws { + print("✓ Swift generic and protocol integration:") + + // Define protocols for Capstone operations + protocol DisassemblyProvider { + func disassemble(_ code: T) -> Int where T.Element == UInt8 + } + + class CapstoneDisassembler: DisassemblyProvider { + let handle: csh + + init?(architecture: cs_arch, mode: cs_mode) { + var handle: csh = 0 + let result = cs_open(architecture, mode, &handle) + guard result == CS_ERR_OK else { + return nil + } + self.handle = handle + } + + deinit { + var mutableHandle = handle + _ = cs_close(&mutableHandle) + } + + func disassemble(_ code: T) -> Int where T.Element == UInt8 { + let codeArray = Array(code) // Convert any collection to array + return codeArray.withUnsafeBufferPointer { buffer -> Int in + var insns: UnsafeMutablePointer? + let count = cs_disasm(handle, buffer.baseAddress, buffer.count, 0x1000, 0, &insns) + defer { + if count > 0 { + cs_free(insns, count) + } + } + return Int(count) + } + } + } + + // Test with different collection types + guard let disassembler = CapstoneDisassembler(architecture: CS_ARCH_X86, mode: CS_MODE_32) else { + print(" Could not create disassembler") + return + } + + // Test with Array + let arrayResult = disassembler.disassemble(Self.x86Code) + print(" Array disassembly: \(arrayResult) instructions") + + // Test with ArraySlice + if Self.x86Code.count > 4 { + let slice = Self.x86Code[0 ..< 4] + let sliceResult = disassembler.disassemble(slice) + print(" ArraySlice disassembly: \(sliceResult) instructions") + } + + // Test with different UInt8 collections + let set = Set(Self.x86Code) + let setArray = Array(set).sorted() // Convert set back to sorted array for consistent results + let setResult = disassembler.disassemble(setArray) + print(" Set-derived array disassembly: \(setResult) instructions") + + // Test generic function + func genericDisassembly(_ provider: DisassemblyProvider, code: T) -> Int where T.Element == UInt8 { + return provider.disassemble(code) + } + + let genericResult = genericDisassembly(disassembler, code: Self.x86Code) + print(" Generic function result: \(genericResult) instructions") + + #expect(arrayResult == genericResult, "Generic and direct calls should yield same results") + } +}