Skip to content

Commit c41b205

Browse files
authored
Add DestructArray and DestructArrayAndReassign operations (#300)
1 parent 7324bf6 commit c41b205

File tree

15 files changed

+394
-2
lines changed

15 files changed

+394
-2
lines changed

Sources/Fuzzilli/Core/CodeGenerators.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,32 @@ public let CodeGenerators: [CodeGenerator] = [
393393
b.reassign(target, to: val)
394394
},
395395

396+
CodeGenerator("DestructArrayGenerator", input: .iterable) { b, arr in
397+
// Fuzzilli generated arrays can have a length ranging from 0 to 10 elements,
398+
// We want to ensure that 1) when destructing arrays we are usually within this length range
399+
// and 2) The probability with which we select indices allows defining atleast 2-3 variables.
400+
var indices: [Int] = []
401+
for idx in 0..<Int.random(in: 0..<5) {
402+
withProbability(0.7) {
403+
indices.append(idx)
404+
}
405+
}
406+
407+
b.destruct(arr, selecting: indices, hasRestElement: probability(0.2))
408+
},
409+
410+
CodeGenerator("DestructArrayAndReassignGenerator", input: .iterable) {b, arr in
411+
var candidates: [Variable] = []
412+
var indices: [Int] = []
413+
for idx in 0..<Int.random(in: 0..<5) {
414+
withProbability(0.7) {
415+
indices.append(idx)
416+
candidates.append(b.randVar())
417+
}
418+
}
419+
b.destruct(arr, selecting: indices, into: candidates, hasRestElement: probability(0.2))
420+
},
421+
396422
CodeGenerator("ComparisonGenerator", inputs: (.anything, .anything)) { b, lhs, rhs in
397423
b.compare(lhs, rhs, with: chooseUniform(from: allComparators))
398424
},

Sources/Fuzzilli/Core/ProgramBuilder.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,16 @@ public class ProgramBuilder {
13091309
perform(Reassign(), withInputs: [output, input])
13101310
}
13111311

1312+
@discardableResult
1313+
public func destruct(_ input: Variable, selecting indices: [Int], hasRestElement: Bool = false) -> [Variable] {
1314+
let outputs = perform(DestructArray(indices: indices, hasRestElement: hasRestElement), withInputs: [input]).outputs
1315+
return Array(outputs)
1316+
}
1317+
1318+
public func destruct(_ input: Variable, selecting indices: [Int], into outputs: [Variable], hasRestElement: Bool) {
1319+
perform(DestructArrayAndReassign(indices: indices, hasRestElement: hasRestElement), withInputs: [input] + outputs)
1320+
}
1321+
13121322
@discardableResult
13131323
public func compare(_ lhs: Variable, _ rhs: Variable, with comparator: Comparator) -> Variable {
13141324
return perform(Compare(comparator), withInputs: [lhs, rhs]).output

Sources/Fuzzilli/FuzzIL/AbstractInterpreter.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,12 @@ public struct AbstractInterpreter {
476476
case is Reassign:
477477
set(instr.input(0), type(ofInput: 1))
478478

479+
case is DestructArray:
480+
instr.outputs.forEach{set($0, .unknown)}
481+
482+
case is DestructArrayAndReassign:
483+
instr.inputs.dropFirst().forEach{set($0, .unknown)}
484+
479485
case is Compare:
480486
set(instr.output, .boolean)
481487

Sources/Fuzzilli/FuzzIL/Instruction.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,16 @@ extension Instruction: ProtobufConvertible {
411411
$0.dup = Fuzzilli_Protobuf_Dup()
412412
case is Reassign:
413413
$0.reassign = Fuzzilli_Protobuf_Reassign()
414+
case let op as DestructArray:
415+
$0.destructArray = Fuzzilli_Protobuf_DestructArray.with {
416+
$0.indices = op.indices.map({ Int32($0) })
417+
$0.hasRestElement_p = op.hasRestElement
418+
}
419+
case let op as DestructArrayAndReassign:
420+
$0.destructArrayAndReassign = Fuzzilli_Protobuf_DestructArrayAndReassign.with {
421+
$0.indices = op.indices.map({ Int32($0) })
422+
$0.hasRestElement_p = op.hasRestElement
423+
}
414424
case let op as Compare:
415425
$0.compare = Fuzzilli_Protobuf_Compare.with { $0.op = convertEnum(op.op, allComparators) }
416426
case is ConditionalOperation:
@@ -658,6 +668,10 @@ extension Instruction: ProtobufConvertible {
658668
op = Dup()
659669
case .reassign(_):
660670
op = Reassign()
671+
case .destructArray(let p):
672+
op = DestructArray(indices: p.indices.map({ Int($0) }), hasRestElement: p.hasRestElement_p)
673+
case .destructArrayAndReassign(let p):
674+
op = DestructArrayAndReassign(indices: p.indices.map({ Int($0) }), hasRestElement: p.hasRestElement_p)
661675
case .compare(let p):
662676
op = Compare(try convertEnum(p.op, allComparators))
663677
case .conditionalOperation(_):

Sources/Fuzzilli/FuzzIL/Operations.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,35 @@ class Reassign: Operation {
653653
}
654654
}
655655

656+
/// Destructs an array into n output variables
657+
class DestructArray: Operation {
658+
let indices: [Int]
659+
let hasRestElement: Bool
660+
661+
init(indices: [Int], hasRestElement: Bool) {
662+
assert(indices == indices.sorted(), "Indices must be sorted in ascending order")
663+
assert(indices.count == Set(indices).count, "Indices must not have duplicates")
664+
self.hasRestElement = hasRestElement
665+
self.indices = indices
666+
super.init(numInputs: 1, numOutputs: indices.count)
667+
}
668+
}
669+
670+
/// Destructs an array and reassigns the output to n existing variables
671+
class DestructArrayAndReassign: Operation {
672+
let indices: [Int]
673+
let hasRestElement: Bool
674+
675+
init(indices: [Int], hasRestElement:Bool) {
676+
assert(indices == indices.sorted(), "Indices must be sorted in ascending order")
677+
assert(indices.count == Set(indices).count, "Indices must not have duplicates")
678+
self.indices = indices
679+
self.hasRestElement = hasRestElement
680+
// The first input is the array being destructed
681+
super.init(numInputs: 1 + indices.count, numOutputs: 0)
682+
}
683+
}
684+
656685
// This array must be kept in sync with the Comparator Enum in operations.proto
657686
public enum Comparator: String {
658687
case equal = "=="

Sources/Fuzzilli/FuzzIL/Semantics.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ extension Operation {
4949
return inputIdx == 0
5050
case let op as UnaryOperation:
5151
return op.op.reassignsInput
52+
case is DestructArrayAndReassign:
53+
return inputIdx != 0
5254
default:
5355
return false
5456
}

Sources/Fuzzilli/Lifting/FuzzILLifter.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@ public class FuzzILLifter: Lifter {
2424
return instr.input(n)
2525
}
2626

27+
// Helper function to lift destruct array operations
28+
func liftArrayPattern(indices: [Int], outputs: [String], hasRestElement: Bool) -> String {
29+
assert(indices.count == outputs.count)
30+
31+
var arrayPattern = ""
32+
var lastIndex = 0
33+
for (index, output) in zip(indices, outputs) {
34+
let skipped = index - lastIndex
35+
lastIndex = index
36+
let dots = index == indices.last! && hasRestElement ? "..." : ""
37+
arrayPattern += String(repeating: ",", count: skipped) + dots + output
38+
}
39+
40+
return arrayPattern
41+
}
42+
2743
switch instr.op {
2844
case let op as LoadInteger:
2945
w.emit("\(instr.output) <- LoadInteger '\(op.value)'")
@@ -229,6 +245,14 @@ public class FuzzILLifter: Lifter {
229245
case is Reassign:
230246
w.emit("Reassign \(input(0)), \(input(1))")
231247

248+
case let op as DestructArray:
249+
let outputs = instr.outputs.map({ $0.identifier })
250+
w.emit("[\(liftArrayPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement))] <- DestructArray \(input(0))")
251+
252+
case let op as DestructArrayAndReassign:
253+
let outputs = instr.inputs.dropFirst().map({ $0.identifier })
254+
w.emit("[\(liftArrayPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement))] <- DestructArrayAndReassign \(input(0))")
255+
232256
case let op as Compare:
233257
w.emit("\(instr.output) <- Compare \(input(0)), '\(op.op.token)', \(input(1))")
234258

Sources/Fuzzilli/Lifting/JavaScriptLifter.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,22 @@ public class JavaScriptLifter: Lifter {
115115
return expr(for: instr.input(idx))
116116
}
117117

118+
// Helper function to lift destruct array operations
119+
func liftArrayPattern(indices: [Int], outputs: [String], hasRestElement: Bool) -> String {
120+
assert(indices.count == outputs.count)
121+
122+
var arrayPattern = ""
123+
var lastIndex = 0
124+
for (index, output) in zip(indices, outputs) {
125+
let skipped = index - lastIndex
126+
lastIndex = index
127+
let dots = index == indices.last! && hasRestElement ? "..." : ""
128+
arrayPattern += String(repeating: ",", count: skipped) + dots + output
129+
}
130+
131+
return arrayPattern
132+
}
133+
118134
// Helper functions to lift a function definition
119135
func liftFunctionDefinitionParameters(_ op: BeginAnyFunctionDefinition) -> String {
120136
assert(instr.op === op)
@@ -419,6 +435,14 @@ public class JavaScriptLifter: Lifter {
419435
let expr = AssignmentExpression.new() <> input(0) <> " = " <> input(1)
420436
w.emit(expr)
421437

438+
case let op as DestructArray:
439+
let outputs = instr.outputs.map({ $0.identifier })
440+
w.emit("\(varDecl) [\(liftArrayPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement))] = \(input(0));")
441+
442+
case let op as DestructArrayAndReassign:
443+
let outputs = instr.inputs.dropFirst().map({ $0.identifier })
444+
w.emit("[\(liftArrayPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement))] = \(input(0));")
445+
422446
case let op as Compare:
423447
output = BinaryExpression.new() <> input(0) <> " " <> op.op.token <> " " <> input(1)
424448

Sources/Fuzzilli/Mutators/OperationMutator.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,24 @@ public class OperationMutator: BaseInstructionMutator {
117117
newOp = BinaryOperation(chooseUniform(from: allBinaryOperators))
118118
case is ReassignWithBinop:
119119
newOp = ReassignWithBinop(chooseUniform(from: allBinaryOperators))
120+
case let op as DestructArray:
121+
var newIndices = Set(op.indices)
122+
if newIndices.count > 0 {
123+
newIndices.remove(newIndices.randomElement()!)
124+
while newIndices.count != op.indices.count {
125+
newIndices.insert(Int.random(in: 0..<10))
126+
}
127+
}
128+
newOp = DestructArray(indices: newIndices.sorted(), hasRestElement: !op.hasRestElement)
129+
case let op as DestructArrayAndReassign:
130+
var newIndices = Set(op.indices)
131+
if newIndices.count > 0 {
132+
newIndices.remove(newIndices.randomElement()!)
133+
while newIndices.count != op.indices.count {
134+
newIndices.insert(Int.random(in: 0..<10))
135+
}
136+
}
137+
newOp = DestructArrayAndReassign(indices: newIndices.sorted(), hasRestElement: !op.hasRestElement)
120138
case is Compare:
121139
newOp = Compare(chooseUniform(from: allComparators))
122140
case is LoadFromScope:

Sources/Fuzzilli/Protobuf/operations.pb.swift

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,34 @@ public struct Fuzzilli_Protobuf_Reassign {
950950
public init() {}
951951
}
952952

953+
public struct Fuzzilli_Protobuf_DestructArray {
954+
// SwiftProtobuf.Message conformance is added in an extension below. See the
955+
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
956+
// methods supported on all messages.
957+
958+
public var indices: [Int32] = []
959+
960+
public var hasRestElement_p: Bool = false
961+
962+
public var unknownFields = SwiftProtobuf.UnknownStorage()
963+
964+
public init() {}
965+
}
966+
967+
public struct Fuzzilli_Protobuf_DestructArrayAndReassign {
968+
// SwiftProtobuf.Message conformance is added in an extension below. See the
969+
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
970+
// methods supported on all messages.
971+
972+
public var indices: [Int32] = []
973+
974+
public var hasRestElement_p: Bool = false
975+
976+
public var unknownFields = SwiftProtobuf.UnknownStorage()
977+
978+
public init() {}
979+
}
980+
953981
public struct Fuzzilli_Protobuf_Compare {
954982
// SwiftProtobuf.Message conformance is added in an extension below. See the
955983
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
@@ -3046,6 +3074,82 @@ extension Fuzzilli_Protobuf_Reassign: SwiftProtobuf.Message, SwiftProtobuf._Mess
30463074
}
30473075
}
30483076

3077+
extension Fuzzilli_Protobuf_DestructArray: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
3078+
public static let protoMessageName: String = _protobuf_package + ".DestructArray"
3079+
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
3080+
1: .same(proto: "indices"),
3081+
2: .same(proto: "hasRestElement"),
3082+
]
3083+
3084+
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
3085+
while let fieldNumber = try decoder.nextFieldNumber() {
3086+
// The use of inline closures is to circumvent an issue where the compiler
3087+
// allocates stack space for every case branch when no optimizations are
3088+
// enabled. https://github.com/apple/swift-protobuf/issues/1034
3089+
switch fieldNumber {
3090+
case 1: try { try decoder.decodeRepeatedInt32Field(value: &self.indices) }()
3091+
case 2: try { try decoder.decodeSingularBoolField(value: &self.hasRestElement_p) }()
3092+
default: break
3093+
}
3094+
}
3095+
}
3096+
3097+
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
3098+
if !self.indices.isEmpty {
3099+
try visitor.visitPackedInt32Field(value: self.indices, fieldNumber: 1)
3100+
}
3101+
if self.hasRestElement_p != false {
3102+
try visitor.visitSingularBoolField(value: self.hasRestElement_p, fieldNumber: 2)
3103+
}
3104+
try unknownFields.traverse(visitor: &visitor)
3105+
}
3106+
3107+
public static func ==(lhs: Fuzzilli_Protobuf_DestructArray, rhs: Fuzzilli_Protobuf_DestructArray) -> Bool {
3108+
if lhs.indices != rhs.indices {return false}
3109+
if lhs.hasRestElement_p != rhs.hasRestElement_p {return false}
3110+
if lhs.unknownFields != rhs.unknownFields {return false}
3111+
return true
3112+
}
3113+
}
3114+
3115+
extension Fuzzilli_Protobuf_DestructArrayAndReassign: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
3116+
public static let protoMessageName: String = _protobuf_package + ".DestructArrayAndReassign"
3117+
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
3118+
1: .same(proto: "indices"),
3119+
2: .same(proto: "hasRestElement"),
3120+
]
3121+
3122+
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
3123+
while let fieldNumber = try decoder.nextFieldNumber() {
3124+
// The use of inline closures is to circumvent an issue where the compiler
3125+
// allocates stack space for every case branch when no optimizations are
3126+
// enabled. https://github.com/apple/swift-protobuf/issues/1034
3127+
switch fieldNumber {
3128+
case 1: try { try decoder.decodeRepeatedInt32Field(value: &self.indices) }()
3129+
case 2: try { try decoder.decodeSingularBoolField(value: &self.hasRestElement_p) }()
3130+
default: break
3131+
}
3132+
}
3133+
}
3134+
3135+
public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
3136+
if !self.indices.isEmpty {
3137+
try visitor.visitPackedInt32Field(value: self.indices, fieldNumber: 1)
3138+
}
3139+
if self.hasRestElement_p != false {
3140+
try visitor.visitSingularBoolField(value: self.hasRestElement_p, fieldNumber: 2)
3141+
}
3142+
try unknownFields.traverse(visitor: &visitor)
3143+
}
3144+
3145+
public static func ==(lhs: Fuzzilli_Protobuf_DestructArrayAndReassign, rhs: Fuzzilli_Protobuf_DestructArrayAndReassign) -> Bool {
3146+
if lhs.indices != rhs.indices {return false}
3147+
if lhs.hasRestElement_p != rhs.hasRestElement_p {return false}
3148+
if lhs.unknownFields != rhs.unknownFields {return false}
3149+
return true
3150+
}
3151+
}
3152+
30493153
extension Fuzzilli_Protobuf_Compare: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
30503154
public static let protoMessageName: String = _protobuf_package + ".Compare"
30513155
public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [

0 commit comments

Comments
 (0)