mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
* Eradicate IndexDistance associated type, replacing with Int everywhere * Consistently use Int for ExistentialCollection’s IndexDistance type. * Fix test for IndexDistance removal * Remove a handful of no-longer-needed explicit types * Add compatibility shims for non-Int index distances * Test compatibility shim * Move IndexDistance typealias into the Collection protocol
196 lines
5.5 KiB
Swift
196 lines
5.5 KiB
Swift
// --- sourcekit_fuzzer.swift - a simple code completion fuzzer ---------------
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// The idea here is we start with a source file and proceed to place the cursor
|
|
// at random locations in the file, eventually visiting all locations exactly
|
|
// once in a shuffled random order.
|
|
//
|
|
// If completion at a location crashes, we run the test case through 'creduce'
|
|
// to find a minimal reproducer that also crashes (possibly with a different
|
|
// crash, but in practice all the examples I've seen continue to crash in the
|
|
// same way as creduce performs its reduction).
|
|
//
|
|
// Once creduce fully reduces a test case, we save it to a file named
|
|
// 'crash-NNN.swift', with a RUN: line suitable for placing the test case in
|
|
// 'validation-tests/IDE/crashers_2'.
|
|
//
|
|
// The overall script execution stops once all source locations in the file
|
|
// have been tested.
|
|
//
|
|
// You must first install creduce <https://embed.cs.utah.edu/creduce/>
|
|
// somewhere in your $PATH. Then, run this script as follows:
|
|
//
|
|
// swift utils/sourcekit_fuzzer/sourcekit_fuzzer.swift <build dir> <source file>
|
|
//
|
|
// - <build dir> is your Swift build directory (the one with subdirectories
|
|
// named swift-macosx-x86_64 and llvm-macosx-x86_64).
|
|
//
|
|
// - <source file> is the source file to fuzz. Try any complex but
|
|
// self-contained Swift file that exercises a variety of language features;
|
|
// for example, I've had good results with the files in test/Prototypes/.
|
|
//
|
|
// TODO:
|
|
// - Add fuzzing for CursorInfo and RangeInfo
|
|
// - Get it running on Linux
|
|
// - Better error handling
|
|
// - More user-friendly output
|
|
|
|
import Darwin
|
|
import Foundation
|
|
|
|
// https://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift/24029847
|
|
extension MutableCollection {
|
|
/// Shuffles the contents of this collection.
|
|
mutating func shuffle() {
|
|
let c = count
|
|
guard c > 1 else { return }
|
|
|
|
for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
|
|
let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
|
|
guard d != 0 else { continue }
|
|
let i = index(firstUnshuffled, offsetBy: d)
|
|
swapAt(firstUnshuffled, i)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension String {
|
|
func write(to path: String) throws {
|
|
try write(to: URL(fileURLWithPath: path), atomically: true, encoding: String.Encoding.utf8)
|
|
}
|
|
}
|
|
|
|
// Gross
|
|
enum ProcessError : Error {
|
|
case failed
|
|
}
|
|
|
|
func run(_ args: [String]) throws -> Int32 {
|
|
var pid: pid_t = 0
|
|
|
|
let argv = args.map {
|
|
$0.withCString(strdup)
|
|
}
|
|
defer { argv.forEach { free($0) } }
|
|
|
|
let envp = ProcessInfo.processInfo.environment.map {
|
|
"\($0.0)=\($0.1)".withCString(strdup)
|
|
}
|
|
defer { envp.forEach { free($0) } }
|
|
|
|
let result = posix_spawn(&pid, argv[0], nil, nil, argv + [nil], envp + [nil])
|
|
if result != 0 { throw ProcessError.failed }
|
|
|
|
var stat: Int32 = 0
|
|
waitpid(pid, &stat, 0)
|
|
|
|
return stat
|
|
}
|
|
|
|
var arguments = CommandLine.arguments
|
|
|
|
// Workaround for behavior of CommandLine in script mode, where we don't drop
|
|
// the filename argument from the list.
|
|
if arguments.first == "sourcekit_fuzzer.swift" {
|
|
arguments = Array(arguments[1...])
|
|
}
|
|
|
|
if arguments.count != 2 {
|
|
print("Usage: sourcekit_fuzzer <build directory> <file>")
|
|
exit(1)
|
|
}
|
|
|
|
let buildDir = arguments[0]
|
|
|
|
let notPath = "\(buildDir)/llvm-macosx-x86_64/bin/not"
|
|
let swiftIdeTestPath = "\(buildDir)/swift-macosx-x86_64/bin/swift-ide-test"
|
|
let creducePath = "/usr/local/bin/creduce"
|
|
|
|
let file = arguments[1]
|
|
|
|
let contents = try! String(contentsOfFile: file)
|
|
|
|
var offsets = Array(0...contents.count)
|
|
offsets.shuffle()
|
|
|
|
var good = 0
|
|
var bad = 0
|
|
|
|
for offset in offsets {
|
|
print("TOTAL FAILURES: \(bad) out of \(bad + good)")
|
|
|
|
let index = contents.index(contents.startIndex, offsetBy: offset)
|
|
let prefix = contents[..<index]
|
|
let suffix = contents[index...]
|
|
let newContents = String(prefix + "#^A^#" + suffix)
|
|
|
|
let sourcePath = "out\(offset).swift"
|
|
try! newContents.write(to: sourcePath)
|
|
|
|
let shellScriptPath = "out\(offset).sh"
|
|
let shellScript = """
|
|
#!/bin/sh
|
|
\(notPath) --crash \(swiftIdeTestPath) -code-completion -code-completion-token=A -source-filename=\(sourcePath)
|
|
"""
|
|
try! shellScript.write(to: shellScriptPath)
|
|
|
|
defer {
|
|
unlink(shellScriptPath)
|
|
unlink(sourcePath)
|
|
}
|
|
|
|
do {
|
|
let result = chmod(shellScriptPath, 0o700)
|
|
if result != 0 {
|
|
print("chmod failed")
|
|
exit(1)
|
|
}
|
|
}
|
|
|
|
do {
|
|
let result = try! run(["./\(shellScriptPath)"])
|
|
if result != 0 {
|
|
good += 1
|
|
continue
|
|
}
|
|
}
|
|
|
|
do {
|
|
// Because we invert the exit code with 'not', an exit code for 0 actually
|
|
// indicates failure
|
|
print("Failed at offset \(offset)")
|
|
print("Reducing...")
|
|
|
|
let result = try! run([creducePath, shellScriptPath, sourcePath])
|
|
if result != 0 {
|
|
print("creduce failed")
|
|
exit(1)
|
|
}
|
|
|
|
bad += 1
|
|
}
|
|
|
|
do {
|
|
let reduction = try! String(contentsOfFile: sourcePath)
|
|
|
|
let testcasePath = "crash-\(bad).swift"
|
|
let testcase = """
|
|
// RUN: \(notPath) --crash \(swiftIdeTestPath) -code-completion -code-completion-token=A -source-filename=%s
|
|
// REQUIRES: asserts
|
|
|
|
\(reduction)
|
|
"""
|
|
|
|
try! testcase.write(to: testcasePath)
|
|
}
|
|
}
|