THRIFT-5855: Add swift fuzzers

Add fuzzers for Swift support, to improve the reliability/robustness of the implementation
diff --git a/lib/swift/FuzzTesting/Package.swift b/lib/swift/FuzzTesting/Package.swift
new file mode 100644
index 0000000..81ce5b4
--- /dev/null
+++ b/lib/swift/FuzzTesting/Package.swift
@@ -0,0 +1,88 @@
+// swift-tools-version:5.5
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import PackageDescription
+
+let package = Package(
+    name: "ThriftFuzzTesting",
+    dependencies: [
+        .package(name: "Thrift", path: "../")
+    ],
+    targets: [
+        // Generated code from Thrift definitions
+        .target(
+            name: "Fuzz",
+            dependencies: ["Thrift"],
+            path: "Sources/Fuzz"
+        ),
+        // Common utilities for fuzzing
+        .target(
+            name: "FuzzCommon",
+            dependencies: ["Thrift", "Fuzz"],
+            path: "Sources/FuzzCommon"
+        ),
+        .executableTarget(
+            name: "FuzzParseBinary",
+            dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
+            path: "Sources/FuzzParseBinary",
+            linkerSettings: [
+                .unsafeFlags(["-sanitize=fuzzer"])
+            ]
+        ),
+        .executableTarget(
+            name: "FuzzRoundtripBinary",
+            dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
+            path: "Sources/FuzzRoundtripBinary",
+            linkerSettings: [
+                .unsafeFlags(["-sanitize=fuzzer"])
+            ]
+        ),
+        .executableTarget(
+            name: "FuzzParseCompact",
+            dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
+            path: "Sources/FuzzParseCompact",
+            linkerSettings: [
+                .unsafeFlags(["-sanitize=fuzzer"])
+            ]
+        ),
+        .executableTarget(
+            name: "FuzzRoundtripCompact",
+            dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
+            path: "Sources/FuzzRoundtripCompact",
+            linkerSettings: [
+                .unsafeFlags(["-sanitize=fuzzer"])
+            ]
+        ),
+        .executableTarget(
+            name: "FuzzParseJSON",
+            dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
+            path: "Sources/FuzzParseJSON",
+            linkerSettings: [
+                .unsafeFlags(["-sanitize=fuzzer"])
+            ]
+        ),
+        .executableTarget(
+            name: "FuzzRoundtripJSON",
+            dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
+            path: "Sources/FuzzRoundtripJSON",
+            linkerSettings: [
+                .unsafeFlags(["-sanitize=fuzzer"])
+            ]
+        )
+    ]
+) 
\ No newline at end of file
diff --git a/lib/swift/FuzzTesting/README.md b/lib/swift/FuzzTesting/README.md
new file mode 100644
index 0000000..3ab5a16
--- /dev/null
+++ b/lib/swift/FuzzTesting/README.md
@@ -0,0 +1,16 @@
+# Swift Fuzzing README
+
+The Swift Thrift implementation uses LLVM's libFuzzer for fuzzing.
+
+## Fuzzer Structure
+
+We currently have several fuzz targets that test different aspects of the Thrift implementation:
+
+* FuzzParseBinary -- Tries to deserialize the code-generated FuzzTest struct from arbitrary input data using the binary protocol
+* FuzzRoundtripBinary -- Tries to deserialize a FuzzTest struct and then tests roundtrip serialization/deserialization with the binary protocol
+* FuzzParseCompact
+* FuzzRoundtripCompact
+* FuzzParseJSON
+* FuzzRoundtripJSON
+
+The fuzzers need a dummy main() to ensure that compilation in non-fuzzer modes doesn't regress.
\ No newline at end of file
diff --git a/lib/swift/FuzzTesting/Sources/FuzzCommon/FuzzUtils.swift b/lib/swift/FuzzTesting/Sources/FuzzCommon/FuzzUtils.swift
new file mode 100644
index 0000000..a81d634
--- /dev/null
+++ b/lib/swift/FuzzTesting/Sources/FuzzCommon/FuzzUtils.swift
@@ -0,0 +1,108 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import Foundation
+import Thrift
+import Fuzz
+
+/// Generic parser that returns a parsed object from binary data - for use as a converter
+public func parseObjectWithProtocol<P: TProtocol>(
+    start: UnsafeRawPointer,
+    count: Int,
+    protocolType: P.Type) -> Fuzz.FuzzTest? {
+    let data = Data(bytes: start, count: count)
+    let transport = TMemoryBufferTransport(readBuffer: data)
+    let proto = P(on: transport)
+    
+    do {
+        return try Fuzz.FuzzTest.read(from: proto)
+    } catch {
+        return nil
+    }
+}
+
+/// Test roundtrip serialization/deserialization with the specified protocol and conversion function
+public func roundtripWithProtocol<P: TProtocol>(
+    start: UnsafeRawPointer,
+    count: Int,
+    protocolType: P.Type
+) -> Int32 {    
+    // Try to convert data to a test object
+    guard let testObj = parseObjectWithProtocol(start: start, count: count, protocolType: protocolType) else {
+        return 0
+    }
+    
+    // Now do a roundtrip test with the converted object
+    do {
+        // Serialize
+        let writeTransport = TMemoryBufferTransport()
+        let writeProto = P(on: writeTransport)
+        
+        try testObj.write(to: writeProto)
+        try writeTransport.flush()
+        
+        // Deserialize
+        let readTransport = TMemoryBufferTransport(readBuffer: writeTransport.writeBuffer)
+        let readProto = P(on: readTransport)
+        
+        let deserialized = try Fuzz.FuzzTest.read(from: readProto)
+        
+        // This should always be true, but we check just to be sure
+        guard deserialized == testObj else {
+            fatalError("Roundtrip test failed: objects not equal after serialization/deserialization")
+        }
+        
+    } catch {
+        // Catch expected exceptions
+    }
+    
+    return 0
+}
+
+/// Typedef for the fuzzer function signature required by libFuzzer
+public typealias FuzzTarget = @convention(c) (UnsafeRawPointer, Int) -> Int32
+
+// Import the libFuzzer driver function
+@_silgen_name("LLVMFuzzerRunDriver")
+public func LLVMFuzzerRunDriver(
+    _ argc: UnsafeMutablePointer<Int32>, 
+    _ argv: UnsafeMutablePointer<UnsafeMutablePointer<UnsafeMutablePointer<CChar>>>,
+    _ userCb: @escaping @convention(c) (UnsafeRawPointer, Int) -> Int32) -> Int32
+
+// Run the libFuzzer driver with the given test function
+// We use this to get around swift compilation issues, which create main functions that otherwise
+// conflict with the libfuzzer main.
+// See more documentation here: https://llvm.org/docs/LibFuzzer.html#using-libfuzzer-as-a-library 
+public func runLibFuzzerDriver(testOneInput: @escaping FuzzTarget) -> Never {
+    // Create C-style arguments to pass to LLVMFuzzerRunDriver
+    var args = CommandLine.arguments.map { strdup($0) }
+    var argc = Int32(args.count)
+    var argv = args.map { UnsafeMutablePointer<CChar>($0!) }
+    let argvPtr = UnsafeMutablePointer<UnsafeMutablePointer<CChar>>.allocate(capacity: args.count)
+    argvPtr.initialize(from: &argv, count: args.count)
+    let argvPtrPtr = UnsafeMutablePointer<UnsafeMutablePointer<UnsafeMutablePointer<CChar>>>.allocate(capacity: 1)
+    argvPtrPtr.pointee = argvPtr
+    
+    // Start the fuzzer engine with our test function
+    let result = LLVMFuzzerRunDriver(&argc, argvPtrPtr, testOneInput)
+    
+    // Clean up
+    argvPtrPtr.deallocate()
+    argvPtr.deallocate()
+    
+    exit(Int32(result))
+} 
\ No newline at end of file
diff --git a/lib/swift/FuzzTesting/Sources/FuzzParseBinary/main.swift b/lib/swift/FuzzTesting/Sources/FuzzParseBinary/main.swift
new file mode 100644
index 0000000..50107b9
--- /dev/null
+++ b/lib/swift/FuzzTesting/Sources/FuzzParseBinary/main.swift
@@ -0,0 +1,32 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import FuzzCommon
+import Thrift
+
+@_cdecl("LLVMFuzzerTestOneInput")
+public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
+    _ = parseObjectWithProtocol(start: start, count: count, protocolType: TBinaryProtocol.self)
+    return 0
+}
+
+@main
+public struct FuzzParseBinary {
+    public static func main() {
+        runLibFuzzerDriver(testOneInput: testOneInput)
+    }
+}
\ No newline at end of file
diff --git a/lib/swift/FuzzTesting/Sources/FuzzParseCompact/main.swift b/lib/swift/FuzzTesting/Sources/FuzzParseCompact/main.swift
new file mode 100644
index 0000000..e8708c9
--- /dev/null
+++ b/lib/swift/FuzzTesting/Sources/FuzzParseCompact/main.swift
@@ -0,0 +1,32 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import FuzzCommon
+import Thrift
+
+@_cdecl("LLVMFuzzerTestOneInput")
+public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
+    _ = parseObjectWithProtocol(start: start, count: count, protocolType: TCompactProtocol.self)
+    return 0
+}
+
+@main
+public struct FuzzParseCompact {
+    public static func main() {
+        runLibFuzzerDriver(testOneInput: testOneInput)
+    }
+} 
\ No newline at end of file
diff --git a/lib/swift/FuzzTesting/Sources/FuzzParseJSON/main.swift b/lib/swift/FuzzTesting/Sources/FuzzParseJSON/main.swift
new file mode 100644
index 0000000..48126fb
--- /dev/null
+++ b/lib/swift/FuzzTesting/Sources/FuzzParseJSON/main.swift
@@ -0,0 +1,32 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import FuzzCommon
+import Thrift
+
+@_cdecl("LLVMFuzzerTestOneInput")
+public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
+    _ = parseObjectWithProtocol(start: start, count: count, protocolType: TJSONProtocol.self)
+    return 0
+}
+
+@main
+public struct FuzzParseJSON {
+    public static func main() {
+        runLibFuzzerDriver(testOneInput: testOneInput)
+    }
+} 
\ No newline at end of file
diff --git a/lib/swift/FuzzTesting/Sources/FuzzRoundtripBinary/main.swift b/lib/swift/FuzzTesting/Sources/FuzzRoundtripBinary/main.swift
new file mode 100644
index 0000000..700708f
--- /dev/null
+++ b/lib/swift/FuzzTesting/Sources/FuzzRoundtripBinary/main.swift
@@ -0,0 +1,35 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import FuzzCommon
+import Thrift
+
+@_cdecl("LLVMFuzzerTestOneInput")
+public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
+    return roundtripWithProtocol(
+        start: start,
+        count: count,
+        protocolType: TBinaryProtocol.self
+    )
+}
+
+@main
+public struct FuzzRoundtripBinary {
+    public static func main() {
+        runLibFuzzerDriver(testOneInput: testOneInput)
+    }
+}
\ No newline at end of file
diff --git a/lib/swift/FuzzTesting/Sources/FuzzRoundtripCompact/main.swift b/lib/swift/FuzzTesting/Sources/FuzzRoundtripCompact/main.swift
new file mode 100644
index 0000000..d089513
--- /dev/null
+++ b/lib/swift/FuzzTesting/Sources/FuzzRoundtripCompact/main.swift
@@ -0,0 +1,35 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import FuzzCommon
+import Thrift
+
+@_cdecl("LLVMFuzzerTestOneInput")
+public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
+    return roundtripWithProtocol(
+        start: start,
+        count: count,
+        protocolType: TCompactProtocol.self
+    )
+}
+
+@main
+public struct FuzzRoundtripCompact {
+    public static func main() {
+        runLibFuzzerDriver(testOneInput: testOneInput)
+    }
+}
\ No newline at end of file
diff --git a/lib/swift/FuzzTesting/Sources/FuzzRoundtripJSON/main.swift b/lib/swift/FuzzTesting/Sources/FuzzRoundtripJSON/main.swift
new file mode 100644
index 0000000..2b7accc
--- /dev/null
+++ b/lib/swift/FuzzTesting/Sources/FuzzRoundtripJSON/main.swift
@@ -0,0 +1,35 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import FuzzCommon
+import Thrift
+
+@_cdecl("LLVMFuzzerTestOneInput")
+public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
+    return roundtripWithProtocol(
+        start: start,
+        count: count,
+        protocolType: TJSONProtocol.self
+    )
+} 
+
+@main
+public struct FuzzRoundtripJSON {
+    public static func main() {
+        runLibFuzzerDriver(testOneInput: testOneInput)
+    }
+}
diff --git a/lib/swift/Makefile.am b/lib/swift/Makefile.am
index ac61716..c681186 100644
--- a/lib/swift/Makefile.am
+++ b/lib/swift/Makefile.am
@@ -28,6 +28,8 @@
 clean-local:
 	swift package clean
 	rm -rf .build
+	rm -rf FuzzTesting/.build
+	rm -rf FuzzTesting/Sources/Fuzz/*
 
 precross:
 	swift build
@@ -35,6 +37,18 @@
 check-local:
 	swift test
 
+FuzzTesting/Sources/Fuzz:
+	mkdir -p FuzzTesting/Sources/Fuzz
+
+fuzz-gen: FuzzTesting/Sources/Fuzz
+	$(top_builddir)/compiler/cpp/thrift --gen swift -r -out FuzzTesting/Sources/Fuzz $(top_srcdir)/test/FuzzTest.thrift
+
+fuzz-local: fuzz-gen
+	cd FuzzTesting && swift build --configuration release ${SWIFTFLAGS}
+
+fuzz: all-local fuzz-local
+	@echo "Built fuzzers successfully"
+
 distdir:
 	$(MAKE) $(AM_MAKEFLAGS) distdir-am
 
@@ -42,6 +56,7 @@
 	Package.swift \
 	Sources \
 	Tests \
+	FuzzTesting \
 	README.md
 
 MAINTAINERCLEANFILES = \