Skip to content

Commit

Permalink
Refactor class definitions in FuzzIL
Browse files Browse the repository at this point in the history
They are now modelled similar to object literals:

    v0 <- BeginClassDefinition [optional superclass]
        ClassAddInstanceProperty
        ClassAddInstanceElement
        ClassAddInstanceComputedProperty
        BeginClassConstructor -> v1, v2
            // v1 is the |this| object
            ...
        EndClassConstructor
        BeginClassInstanceMethod -> v6, v7, v8
            // v6 is the |this| object
            ...
        EndClassInstanceMethod

        BeginClassInstanceGetter -> v12
            // v12 is the |this| object
            ...
        EndClassInstanceGetter
        BeginClassInstanceSetter -> v18, v19
            // v18 is |this|, v19 the new value
            ...
        EndClassInstanceSetter

        ClassAddStaticProperty
        ClassAddStaticElement
        ClassAddStaticComputedProperty
        BeginClassStaticMethod -> v24, v25
            ...
        EndClassStaticMethod
        BeginClassStaticInitializer
        EndClassStaticInitializer
    EndClassDefinition

This commit only adds support for instance properties and methods.
Support for other types of fields will be added in a subsequent commit.
  • Loading branch information
Samuel Groß committed Jan 16, 2023
1 parent 332499b commit afdfcf9
Show file tree
Hide file tree
Showing 28 changed files with 1,359 additions and 838 deletions.
121 changes: 66 additions & 55 deletions Sources/Fuzzilli/Base/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,28 @@ public class ProgramBuilder {
/// Type inference for JavaScript variables.
private var jsTyper: JSTyper

/// Stack of active object literals.
///
/// This needs to be a stack as object literals can be nested, for example if an object
/// literals is created inside a method/getter/setter of another object literals.
private var activeObjectLiterals = Stack<ObjectLiteral>()

/// When building object literals, the state for the current literal is exposed through this member and
/// can be used to add fields to the literal or to determine if some field already exists.
public var currentObjectLiteral: ObjectLiteral {
return activeObjectLiterals.top
}

/// Stack of active object literals.
/// Stack of active class definitions.
///
/// This needs to be a stack as object literals can be nested, for example if an object
/// literals is created inside a method/getter/setter of another object literals.
private var activeObjectLiterals = Stack<ObjectLiteral>()
/// Similar to object literals, class definitions can be nested so this needs to be a stack.
private var activeClassDefinitions = Stack<ClassDefinition>()

/// When building class definitions, the state for the current definition is exposed through this member and
/// can be used to add fields to the class or to determine if some field already exists.
public var currentClassDefinition: ClassDefinition {
return activeClassDefinitions.top
}

/// How many variables are currently in scope.
public var numVisibleVariables: Int {
Expand Down Expand Up @@ -547,6 +558,9 @@ public class ProgramBuilder {
if type.Is(.string) || type.Is(fuzzer.environment.stringType) {
return loadString(randString())
}
if type.Is(.regexp) || type.Is(fuzzer.environment.regExpType) {
return loadRegExp(randRegExpPattern(), RegExpFlags.random())
}
if type.Is(.boolean) || type.Is(fuzzer.environment.booleanType) {
return loadBool(Bool.random())
}
Expand All @@ -556,13 +570,9 @@ public class ProgramBuilder {
if type.Is(.function()) {
let signature = type.signature ?? Signature(withParameterCount: Int.random(in: 2...5), hasRestParam: probability(0.1))
return buildPlainFunction(with: .signature(signature), isStrict: probability(0.1)) { _ in
buildRecursive()
doReturn(randVar())
}
}
if type.Is(.regexp) || type.Is(fuzzer.environment.regExpType) {
return loadRegExp(randRegExpPattern(), RegExpFlags.random())
}

assert(type.Is(.object()), "Unexpected type encountered \(type)")

Expand Down Expand Up @@ -1803,64 +1813,54 @@ public class ProgramBuilder {
emit(Nop(numOutputs: numOutputs), withInputs: [])
}

public struct ClassBuilder {
public typealias MethodBodyGenerator = ([Variable]) -> ()
public typealias ConstructorBodyGenerator = MethodBodyGenerator

fileprivate var constructor: (descriptor: SubroutineDescriptor, generator: ConstructorBodyGenerator)? = nil
fileprivate var methods: [(name: String, descriptor: SubroutineDescriptor, generator: ConstructorBodyGenerator)] = []
fileprivate var properties: [String] = []
/// Represents a currently active class definition. Used to add fields to it and to query which fields already exist.
public class ClassDefinition {
private let b: ProgramBuilder

// This struct is only created by defineClass below
fileprivate init() {}
public fileprivate(set) var hasConstructor = false
fileprivate var existingInstanceProperties: [String] = []
fileprivate var existingInstanceMethods: [String] = []

public mutating func defineConstructor(with descriptor: SubroutineDescriptor, _ generator: @escaping ConstructorBodyGenerator) {
constructor = (descriptor, generator)
fileprivate init(in b: ProgramBuilder) {
assert(b.context.contains(.classDefinition))
self.b = b
}

public mutating func defineProperty(_ name: String) {
properties.append(name)
public func hasInstanceProperty(_ name: String) -> Bool {
return existingInstanceProperties.contains(name)
}

public mutating func defineMethod(_ name: String, with descriptor: SubroutineDescriptor, _ generator: @escaping MethodBodyGenerator) {
methods.append((name, descriptor, generator))
public func hasInstanceMethod(_ name: String) -> Bool {
return existingInstanceMethods.contains(name)
}
}

public typealias ClassBodyGenerator = (inout ClassBuilder) -> ()

@discardableResult
public func buildClass(withSuperclass superclass: Variable? = nil,
_ body: ClassBodyGenerator) -> Variable {
// First collect all information about the class and the generators for constructor and method bodies
var builder = ClassBuilder()
body(&builder)

// Now compute the instance type and define the class
let properties = builder.properties
let methods = builder.methods.map({ ($0.name, $0.descriptor.parameters )})
let constructorDescriptor = builder.constructor?.descriptor ?? .parameters(n: 0)
let hasSuperclass = superclass != nil
setSignatureForNextFunction(builder.constructor?.descriptor.signature)
let classDefinition = emit(BeginClass(hasSuperclass: hasSuperclass,
constructorParameters: constructorDescriptor.parameters,
instanceProperties: properties,
instanceMethods: methods),
withInputs: hasSuperclass ? [superclass!] : [])

// The code directly following the BeginClass is the body of the constructor
builder.constructor?.generator(Array(classDefinition.innerOutputs))
public func addConstructor(with descriptor: SubroutineDescriptor, _ body: ([Variable]) -> ()) {
b.setSignatureForNextFunction(descriptor.signature)
let instr = b.emit(BeginClassConstructor(parameters: descriptor.parameters))
body(Array(instr.innerOutputs))
b.emit(EndClassConstructor())
}

// Next are the bodies of the methods
for method in builder.methods {
setSignatureForNextFunction(method.descriptor.signature)
let methodDefinition = emit(BeginClassMethod(numParameters: method.descriptor.parameters.count), withInputs: [])
method.generator(Array(methodDefinition.innerOutputs))
public func addInstanceProperty(_ name: String, value: Variable? = nil) {
let inputs = value != nil ? [value!] : []
b.emit(ClassAddInstanceProperty(propertyName: name, hasValue: value != nil), withInputs: inputs)
}

emit(EndClass())
public func addInstanceMethod(_ name: String, with descriptor: SubroutineDescriptor, _ body: ([Variable]) -> ()) {
b.setSignatureForNextFunction(descriptor.signature)
let instr = b.emit(BeginClassInstanceMethod(methodName: name, parameters: descriptor.parameters))
body(Array(instr.innerOutputs))
b.emit(EndClassInstanceMethod())
}
}

return classDefinition.output
@discardableResult
public func buildClassDefinition(withSuperclass superclass: Variable? = nil, _ body: (ClassDefinition) -> ()) -> Variable {
let inputs = superclass != nil ? [superclass!] : []
let output = emit(BeginClassDefinition(hasSuperclass: superclass != nil), withInputs: inputs).output
body(currentClassDefinition)
emit(EndClassDefinition())
return output
}

public func callSuperConstructor(withArgs arguments: [Variable]) {
Expand Down Expand Up @@ -2096,7 +2096,7 @@ public class ProgramBuilder {
}
}

// Update object literal state.
// Update object literal and class definition state.
switch instr.op.opcode {
case .beginObjectLiteral:
activeObjectLiterals.push(ObjectLiteral(in: self))
Expand All @@ -2121,6 +2121,17 @@ public class ProgramBuilder {
break
case .endObjectLiteral:
activeObjectLiterals.pop()

case .beginClassDefinition:
activeClassDefinitions.push(ClassDefinition(in: self))
case .beginClassConstructor:
activeClassDefinitions.top.hasConstructor = true
case .classAddInstanceProperty(let op):
activeClassDefinitions.top.existingInstanceProperties.append(op.propertyName)
case .beginClassInstanceMethod(let op):
activeClassDefinitions.top.existingInstanceMethods.append(op.methodName)
case .endClassDefinition:
activeClassDefinitions.pop()
default:
assert(!instr.op.requiredContext.contains(.objectLiteral))
break
Expand Down
80 changes: 56 additions & 24 deletions Sources/Fuzzilli/CodeGen/CodeGenerators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -660,40 +660,72 @@ public let CodeGenerators: [CodeGenerator] = [
b.compare(lhs, with: rhs, using: chooseUniform(from: Comparator.allCases))
},

RecursiveCodeGenerator("ClassGenerator") { b in
RecursiveCodeGenerator("ClassDefinitionGenerator") { b in
// Possibly pick a superclass
var superclass: Variable? = nil
if probability(0.5) {
superclass = b.randVar(ofConservativeType: .constructor())
}

let numProperties = Int.random(in: 1...3)
let numMethods = Int.random(in: 1...3)
b.buildClassDefinition(withSuperclass: superclass) { cls in
b.buildRecursive()
}
},

b.buildClass(withSuperclass: superclass) { cls in
cls.defineConstructor(with: b.generateFunctionParameters()) { _ in
// Must call the super constructor if there is a superclass
if let superConstructor = superclass {
let arguments = b.randCallArguments(for: superConstructor) ?? []
b.callSuperConstructor(withArgs: arguments)
}
RecursiveCodeGenerator("ClassConstructorGenerator", inContext: .classDefinition) { b in
assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript))

b.buildRecursive(block: 1, of: numMethods + 1)
}
guard !b.currentClassDefinition.hasConstructor else {
// There must only be one constructor
return
}

for _ in 0..<numProperties {
cls.defineProperty(b.randPropertyForWriting())
}
b.currentClassDefinition.addConstructor(with: b.generateFunctionParameters()) { _ in
b.buildRecursive()
}
},

for i in 0..<numMethods {
cls.defineMethod(b.randMethod(), with: b.generateFunctionParameters()) { _ in
b.buildRecursive(block: 2 + i, of: numMethods + 1)
}
}
CodeGenerator("ClassInstancePropertyGenerator", inContext: .classDefinition) { b in
assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript))

// Try to find a property that hasn't already been added to this literal.
var propertyName: String
var attempts = 0
repeat {
guard attempts < 10 else { return }
propertyName = b.randPropertyForDefining()
attempts += 1
} while b.currentClassDefinition.hasInstanceProperty(propertyName)

var value: Variable? = nil
if probability(0.5) {
// If the selected property has type requirements, satisfy those.
let type = b.type(ofProperty: propertyName)
value = b.randVar(ofType: type)
}

b.currentClassDefinition.addInstanceProperty(propertyName, value: value)
},

RecursiveCodeGenerator("ClassInstanceMethodGenerator", inContext: .classDefinition) { b in
assert(b.context.contains(.classDefinition) && !b.context.contains(.javascript))

// Try to find a method that hasn't already been added to this class.
var methodName: String
var attempts = 0
repeat {
guard attempts < 10 else { return }
methodName = b.randMethodForDefining()
attempts += 1
} while b.currentClassDefinition.hasInstanceMethod(methodName)

b.currentClassDefinition.addInstanceMethod(methodName, with: b.generateFunctionParameters()) { args in
b.buildRecursive()
b.doReturn(b.randVar())
}
},

CodeGenerator("SuperMethodCallGenerator", inContext: .classDefinition) { b in
CodeGenerator("SuperMethodCallGenerator", inContext: [.classDefinition, .javascript]) { b in
let superType = b.currentSuperType()
if let methodName = superType.randomMethod() {
guard let arguments = b.randCallArguments(forMethod: methodName, on: superType) else { return }
Expand All @@ -709,15 +741,15 @@ public let CodeGenerators: [CodeGenerator] = [
},

// Loads a property on the super object
CodeGenerator("LoadSuperPropertyGenerator", inContext: .classDefinition) { b in
CodeGenerator("LoadSuperPropertyGenerator", inContext: [.classDefinition, .javascript]) { b in
let superType = b.currentSuperType()
// Emit a property load
let propertyName = superType.randomProperty() ?? b.randPropertyForReading()
b.loadSuperProperty(propertyName)
},

// Stores a property on the super object
CodeGenerator("StoreSuperPropertyGenerator", inContext: .classDefinition) { b in
CodeGenerator("StoreSuperPropertyGenerator", inContext: [.classDefinition, .javascript]) { b in
let superType = b.currentSuperType()
// Emit a property store
let propertyName: String
Expand All @@ -737,7 +769,7 @@ public let CodeGenerators: [CodeGenerator] = [
},

// Stores a property with a binary operation on the super object
CodeGenerator("StoreSuperPropertyWithBinopGenerator", inContext: .classDefinition) { b in
CodeGenerator("StoreSuperPropertyWithBinopGenerator", inContext: [.classDefinition, .javascript]) { b in
let superType = b.currentSuperType()
// Emit a property store
let propertyName = superType.randomProperty() ?? b.randPropertyForWriting()
Expand Down
Loading

0 comments on commit afdfcf9

Please sign in to comment.