Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Sources/Fuzzilli/Core/CodeGenerators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,32 @@ public let CodeGenerators: [CodeGenerator] = [
b.reassign(target, to: val)
},

CodeGenerator("DestructArrayGenerator", input: .iterable) { b, arr in
// Fuzzilli generated arrays can have a length ranging from 0 to 10 elements,
// We want to ensure that 1) when destructing arrays we are usually within this length range
// and 2) The probability with which we select indices allows defining atleast 2-3 variables.
var indices: [Int] = []
for idx in 0..<Int.random(in: 0..<5) {
Comment thread
amarekano marked this conversation as resolved.
withProbability(0.7) {
indices.append(idx)
}
}

b.destruct(arr, selecting: indices, hasRestElement: probability(0.2))
},

CodeGenerator("DestructArrayAndReassignGenerator", input: .iterable) {b, arr in
var candidates: [Variable] = []
var indices: [Int] = []
for idx in 0..<Int.random(in: 0..<5) {
withProbability(0.7) {
indices.append(idx)
candidates.append(b.randVar())
}
}
b.destruct(arr, selecting: indices, into: candidates, hasRestElement: probability(0.2))
},

CodeGenerator("ComparisonGenerator", inputs: (.anything, .anything)) { b, lhs, rhs in
b.compare(lhs, rhs, with: chooseUniform(from: allComparators))
},
Expand Down
10 changes: 10 additions & 0 deletions Sources/Fuzzilli/Core/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,16 @@ public class ProgramBuilder {
perform(Reassign(), withInputs: [output, input])
}

@discardableResult
public func destruct(_ input: Variable, selecting indices: [Int], hasRestElement: Bool = false) -> [Variable] {
let outputs = perform(DestructArray(indices: indices, hasRestElement: hasRestElement), withInputs: [input]).outputs
return Array(outputs)
}

public func destruct(_ input: Variable, selecting indices: [Int], into outputs: [Variable], hasRestElement: Bool) {
perform(DestructArrayAndReassign(indices: indices, hasRestElement: hasRestElement), withInputs: [input] + outputs)
}

@discardableResult
public func compare(_ lhs: Variable, _ rhs: Variable, with comparator: Comparator) -> Variable {
return perform(Compare(comparator), withInputs: [lhs, rhs]).output
Expand Down
6 changes: 6 additions & 0 deletions Sources/Fuzzilli/FuzzIL/AbstractInterpreter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,12 @@ public struct AbstractInterpreter {
case is Reassign:
set(instr.input(0), type(ofInput: 1))

case is DestructArray:
instr.outputs.forEach{set($0, .unknown)}

case is DestructArrayAndReassign:
instr.inputs.dropFirst().forEach{set($0, .unknown)}

case is Compare:
set(instr.output, .boolean)

Expand Down
14 changes: 14 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,16 @@ extension Instruction: ProtobufConvertible {
$0.dup = Fuzzilli_Protobuf_Dup()
case is Reassign:
$0.reassign = Fuzzilli_Protobuf_Reassign()
case let op as DestructArray:
$0.destructArray = Fuzzilli_Protobuf_DestructArray.with {
$0.indices = op.indices.map({ Int32($0) })
$0.hasRestElement_p = op.hasRestElement
}
case let op as DestructArrayAndReassign:
$0.destructArrayAndReassign = Fuzzilli_Protobuf_DestructArrayAndReassign.with {
$0.indices = op.indices.map({ Int32($0) })
$0.hasRestElement_p = op.hasRestElement
}
case let op as Compare:
$0.compare = Fuzzilli_Protobuf_Compare.with { $0.op = convertEnum(op.op, allComparators) }
case is ConditionalOperation:
Expand Down Expand Up @@ -658,6 +668,10 @@ extension Instruction: ProtobufConvertible {
op = Dup()
case .reassign(_):
op = Reassign()
case .destructArray(let p):
op = DestructArray(indices: p.indices.map({ Int($0) }), hasRestElement: p.hasRestElement_p)
case .destructArrayAndReassign(let p):
op = DestructArrayAndReassign(indices: p.indices.map({ Int($0) }), hasRestElement: p.hasRestElement_p)
case .compare(let p):
op = Compare(try convertEnum(p.op, allComparators))
case .conditionalOperation(_):
Expand Down
29 changes: 29 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Operations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,35 @@ class Reassign: Operation {
}
}

/// Destructs an array into n output variables
class DestructArray: Operation {
let indices: [Int]
Comment thread
amarekano marked this conversation as resolved.
let hasRestElement: Bool

init(indices: [Int], hasRestElement: Bool) {
assert(indices == indices.sorted(), "Indices must be sorted in ascending order")
assert(indices.count == Set(indices).count, "Indices must not have duplicates")
self.hasRestElement = hasRestElement
Comment thread
amarekano marked this conversation as resolved.
self.indices = indices
super.init(numInputs: 1, numOutputs: indices.count)
}
}

/// Destructs an array and reassigns the output to n existing variables
class DestructArrayAndReassign: Operation {
let indices: [Int]
let hasRestElement: Bool

init(indices: [Int], hasRestElement:Bool) {
assert(indices == indices.sorted(), "Indices must be sorted in ascending order")
assert(indices.count == Set(indices).count, "Indices must not have duplicates")
self.indices = indices
self.hasRestElement = hasRestElement
// The first input is the array being destructed
super.init(numInputs: 1 + indices.count, numOutputs: 0)
}
}

// This array must be kept in sync with the Comparator Enum in operations.proto
public enum Comparator: String {
case equal = "=="
Expand Down
2 changes: 2 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Semantics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ extension Operation {
return inputIdx == 0
case let op as UnaryOperation:
return op.op.reassignsInput
case is DestructArrayAndReassign:
return inputIdx != 0
default:
return false
}
Expand Down
24 changes: 24 additions & 0 deletions Sources/Fuzzilli/Lifting/FuzzILLifter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ public class FuzzILLifter: Lifter {
return instr.input(n)
}

// Helper function to lift destruct array operations
func liftArrayPattern(indices: [Int], outputs: [String], hasRestElement: Bool) -> String {
assert(indices.count == outputs.count)

var arrayPattern = ""
var lastIndex = 0
for (index, output) in zip(indices, outputs) {
let skipped = index - lastIndex
lastIndex = index
let dots = index == indices.last! && hasRestElement ? "..." : ""
arrayPattern += String(repeating: ",", count: skipped) + dots + output
}

return arrayPattern
}

switch instr.op {
case let op as LoadInteger:
w.emit("\(instr.output) <- LoadInteger '\(op.value)'")
Expand Down Expand Up @@ -229,6 +245,14 @@ public class FuzzILLifter: Lifter {
case is Reassign:
w.emit("Reassign \(input(0)), \(input(1))")

case let op as DestructArray:
let outputs = instr.outputs.map({ $0.identifier })
w.emit("[\(liftArrayPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement))] <- DestructArray \(input(0))")

case let op as DestructArrayAndReassign:
let outputs = instr.inputs.dropFirst().map({ $0.identifier })
w.emit("[\(liftArrayPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement))] <- DestructArrayAndReassign \(input(0))")

case let op as Compare:
w.emit("\(instr.output) <- Compare \(input(0)), '\(op.op.token)', \(input(1))")

Expand Down
24 changes: 24 additions & 0 deletions Sources/Fuzzilli/Lifting/JavaScriptLifter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@ public class JavaScriptLifter: Lifter {
return expr(for: instr.input(idx))
}

// Helper function to lift destruct array operations
func liftArrayPattern(indices: [Int], outputs: [String], hasRestElement: Bool) -> String {
assert(indices.count == outputs.count)
Comment thread
amarekano marked this conversation as resolved.

var arrayPattern = ""
var lastIndex = 0
for (index, output) in zip(indices, outputs) {
let skipped = index - lastIndex
lastIndex = index
let dots = index == indices.last! && hasRestElement ? "..." : ""
arrayPattern += String(repeating: ",", count: skipped) + dots + output
}

return arrayPattern
}

// Helper functions to lift a function definition
func liftFunctionDefinitionParameters(_ op: BeginAnyFunctionDefinition) -> String {
assert(instr.op === op)
Expand Down Expand Up @@ -419,6 +435,14 @@ public class JavaScriptLifter: Lifter {
let expr = AssignmentExpression.new() <> input(0) <> " = " <> input(1)
w.emit(expr)

case let op as DestructArray:
let outputs = instr.outputs.map({ $0.identifier })
w.emit("\(varDecl) [\(liftArrayPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement))] = \(input(0));")

case let op as DestructArrayAndReassign:
let outputs = instr.inputs.dropFirst().map({ $0.identifier })
w.emit("[\(liftArrayPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement))] = \(input(0));")

case let op as Compare:
output = BinaryExpression.new() <> input(0) <> " " <> op.op.token <> " " <> input(1)

Expand Down
18 changes: 18 additions & 0 deletions Sources/Fuzzilli/Mutators/OperationMutator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,24 @@ public class OperationMutator: BaseInstructionMutator {
newOp = BinaryOperation(chooseUniform(from: allBinaryOperators))
case is ReassignWithBinop:
newOp = ReassignWithBinop(chooseUniform(from: allBinaryOperators))
case let op as DestructArray:
var newIndices = Set(op.indices)
if newIndices.count > 0 {
newIndices.remove(newIndices.randomElement()!)
while newIndices.count != op.indices.count {
newIndices.insert(Int.random(in: 0..<10))
}
}
newOp = DestructArray(indices: newIndices.sorted(), hasRestElement: !op.hasRestElement)
case let op as DestructArrayAndReassign:
var newIndices = Set(op.indices)
if newIndices.count > 0 {
newIndices.remove(newIndices.randomElement()!)
while newIndices.count != op.indices.count {
newIndices.insert(Int.random(in: 0..<10))
}
}
newOp = DestructArrayAndReassign(indices: newIndices.sorted(), hasRestElement: !op.hasRestElement)
case is Compare:
newOp = Compare(chooseUniform(from: allComparators))
case is LoadFromScope:
Expand Down
104 changes: 104 additions & 0 deletions Sources/Fuzzilli/Protobuf/operations.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,34 @@ public struct Fuzzilli_Protobuf_Reassign {
public init() {}
}

public struct Fuzzilli_Protobuf_DestructArray {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.

public var indices: [Int32] = []

public var hasRestElement_p: Bool = false
Comment thread
saelo marked this conversation as resolved.

public var unknownFields = SwiftProtobuf.UnknownStorage()

public init() {}
}

public struct Fuzzilli_Protobuf_DestructArrayAndReassign {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.

public var indices: [Int32] = []

public var hasRestElement_p: Bool = false

public var unknownFields = SwiftProtobuf.UnknownStorage()

public init() {}
}

public struct Fuzzilli_Protobuf_Compare {
// SwiftProtobuf.Message conformance is added in an extension below. See the
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
Expand Down Expand Up @@ -3046,6 +3074,82 @@ extension Fuzzilli_Protobuf_Reassign: SwiftProtobuf.Message, SwiftProtobuf._Mess
}
}

extension Fuzzilli_Protobuf_DestructArray: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".DestructArray"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "indices"),
2: .same(proto: "hasRestElement"),
]

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeRepeatedInt32Field(value: &self.indices) }()
case 2: try { try decoder.decodeSingularBoolField(value: &self.hasRestElement_p) }()
default: break
}
}
}

public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.indices.isEmpty {
try visitor.visitPackedInt32Field(value: self.indices, fieldNumber: 1)
}
if self.hasRestElement_p != false {
try visitor.visitSingularBoolField(value: self.hasRestElement_p, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}

public static func ==(lhs: Fuzzilli_Protobuf_DestructArray, rhs: Fuzzilli_Protobuf_DestructArray) -> Bool {
if lhs.indices != rhs.indices {return false}
if lhs.hasRestElement_p != rhs.hasRestElement_p {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

extension Fuzzilli_Protobuf_DestructArrayAndReassign: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".DestructArrayAndReassign"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "indices"),
2: .same(proto: "hasRestElement"),
]

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let fieldNumber = try decoder.nextFieldNumber() {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every case branch when no optimizations are
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeRepeatedInt32Field(value: &self.indices) }()
case 2: try { try decoder.decodeSingularBoolField(value: &self.hasRestElement_p) }()
default: break
}
}
}

public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
if !self.indices.isEmpty {
try visitor.visitPackedInt32Field(value: self.indices, fieldNumber: 1)
}
if self.hasRestElement_p != false {
try visitor.visitSingularBoolField(value: self.hasRestElement_p, fieldNumber: 2)
}
try unknownFields.traverse(visitor: &visitor)
}

public static func ==(lhs: Fuzzilli_Protobuf_DestructArrayAndReassign, rhs: Fuzzilli_Protobuf_DestructArrayAndReassign) -> Bool {
if lhs.indices != rhs.indices {return false}
if lhs.hasRestElement_p != rhs.hasRestElement_p {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
}

extension Fuzzilli_Protobuf_Compare: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
public static let protoMessageName: String = _protobuf_package + ".Compare"
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
Expand Down
10 changes: 10 additions & 0 deletions Sources/Fuzzilli/Protobuf/operations.proto
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,16 @@ message Dup {
message Reassign {
}

message DestructArray {
repeated int32 indices = 1;
bool hasRestElement = 2;
}

message DestructArrayAndReassign {
repeated int32 indices = 1;
bool hasRestElement = 2;
}

enum Comparator {
EQUAL = 0;
STRICT_EQUAL = 1;
Expand Down
Loading