Skip to content

Commit

Permalink
Redesign named variables in FuzzIL
Browse files Browse the repository at this point in the history
This change replaces the three operations LoadNamedVariable,
StoreNamedVariable, and DefineNamedVariable with a single new operation:
CreateNamedVariable. This operation now simply creates a new FuzzIL
variable that will be assigned a specific identifier during lifting.
Optionally, the named variable can be declared using any of the
available variable declaration modes: global, var, let, or const.

Below is a small example of how CreateNamedVariable can be used:

   // Make an existing named variable (e.g. a builtin) available
   v0 <- CreateNamedVariable 'print', declarationMode: .none

   // Overwrite an existing named variable
   v1 <- CreateNamedVariable 'foo', declarationMode: .none
   v2 <- CallFunction v0, v1
   v3 <- LoadString 'bar'
   Reassign v1, v3

   // Declare a new named variable
   v4 <- CreateNamedVariable 'baz', declarationMode: .var, v1
   v5 <- LoadString 'bla'
   Update v4 '+' v5
   v5 <- CallFunction v0, v4

This will lift to JavaScript code similar to the following:

   print(foo);
   foo = "bar";
   var baz = foo;
   baz += "bla";
   print(baz);

With this, we now have a single, flexible way of creating variables that
have a specific name. We now use this:

* For builtins, which are effectively just existing named variables in
  the global scope. This also now makes it (easily) possible to
  overwrite builtins. As such, The LoadBuiltin operation was removed.
* During code generation, to sometimes create variables with specific
  names in generated code (we use random property names).
* In the compiler. This is the main user of named variables and this is
  where this change has the most impact: we now compiler _every_
  variable declaration to a CreateNamedVariable operation. This now
  makes it possible to correctly compiler any code that relies on
  variable names, for example due to using `eval`, with statements, or
  similar constructs. See some of the added/modified tests for examples.
  The consequence of this is that compiled code will now often have a
  lot of CreateNamedVariable operations. However, as these are now just
  regular FuzzIL variables, this change should not significantly affect
  mutability of the programs. In the future, we could consider
  implementing a specific minimizer (that we could also run during
  corpus import) to remove unneeded CreateNamedVariable operations.
  However, it will likely be somewhat difficult to determine when such
  an operation is not needed.
  • Loading branch information
Samuel Groß authored and Samuel Groß committed Dec 25, 2024
1 parent d64827b commit 3e40cbe
Show file tree
Hide file tree
Showing 35 changed files with 1,128 additions and 1,237 deletions.
24 changes: 9 additions & 15 deletions Sources/Fuzzilli/Base/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ public class ProgramBuilder {

// We can be sure that we have such a builtin with a signature because the Environment checks this during initialization.
let signature = fuzzer.environment.type(ofBuiltin: group).signature!
let constructor = loadBuiltin(group)
let constructor = createNamedVariableForBuiltin(group)
let arguments = findOrGenerateArgumentsInternal(forSignature: signature)
let constructed = construct(constructor, withArgs: arguments)

Expand Down Expand Up @@ -1939,11 +1939,6 @@ public class ProgramBuilder {
return emit(CreateTemplateString(parts: parts), withInputs: interpolatedValues).output
}

@discardableResult
public func loadBuiltin(_ name: String) -> Variable {
return emit(LoadBuiltin(builtinName: name)).output
}

@discardableResult
public func getProperty(_ name: String, of object: Variable, guard isGuarded: Bool = false) -> Variable {
return emit(GetProperty(propertyName: name, isGuarded: isGuarded), withInputs: [object]).output
Expand Down Expand Up @@ -2325,16 +2320,15 @@ public class ProgramBuilder {
}

@discardableResult
public func loadNamedVariable(_ name: String) -> Variable {
return emit(LoadNamedVariable(name)).output
public func createNamedVariable(_ name: String, declarationMode: NamedVariableDeclarationMode, initialValue: Variable? = nil) -> Variable {
assert((declarationMode == .none) == (initialValue == nil))
let inputs = initialValue != nil ? [initialValue!] : []
return emit(CreateNamedVariable(name, declarationMode: declarationMode), withInputs: inputs).output
}

public func storeNamedVariable(_ name: String, _ value: Variable) {
emit(StoreNamedVariable(name), withInputs: [value])
}

public func defineNamedVariable(_ name: String, _ value: Variable) {
emit(DefineNamedVariable(name), withInputs: [value])

@discardableResult
public func createNamedVariableForBuiltin(_ builtinName: String) -> Variable {
return createNamedVariable(builtinName, declarationMode: .none)
}

@discardableResult
Expand Down
4 changes: 1 addition & 3 deletions Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public let codeGeneratorWeights = [
"BooleanGenerator": 2,
"UndefinedGenerator": 1,
"NullGenerator": 1,
"NamedVariableGenerator": 10,
"ArrayGenerator": 10,
"FloatArrayGenerator": 10,
"IntArrayGenerator": 10,
Expand Down Expand Up @@ -133,9 +134,6 @@ public let codeGeneratorWeights = [
"DestructObjectAndReassignGenerator": 5,
"WithStatementGenerator": 3,
"ComparisonGenerator": 10,
"NamedVariableLoadGenerator": 3,
"NamedVariableStoreGenerator": 3,
"NamedVariableDefinitionGenerator": 3,
"SuperMethodCallGenerator": 20,

// These will only be used inside class methods, and only if private properties were previously declared in that class.
Expand Down
85 changes: 39 additions & 46 deletions Sources/Fuzzilli/CodeGen/CodeGenerators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public let CodeGenerators: [CodeGenerator] = [

ValueGenerator("BuiltinObjectInstanceGenerator") { b, n in
let builtin = chooseUniform(from: ["Array", "Map", "WeakMap", "Set", "WeakSet", "Date"])
let constructor = b.loadBuiltin(builtin)
let constructor = b.createNamedVariableForBuiltin(builtin)
if builtin == "Array" {
let size = b.loadInt(b.randomSize(upTo: 0x1000))
b.construct(constructor, withArgs: [size])
Expand All @@ -113,7 +113,7 @@ public let CodeGenerators: [CodeGenerator] = [
ValueGenerator("TypedArrayGenerator") { b, n in
for _ in 0..<n {
let size = b.loadInt(b.randomSize(upTo: 0x1000))
let constructor = b.loadBuiltin(
let constructor = b.createNamedVariableForBuiltin(
chooseUniform(
from: ["Uint8Array", "Int8Array", "Uint16Array", "Int16Array", "Uint32Array", "Int32Array", "Float32Array", "Float64Array", "Uint8ClampedArray", "BigInt64Array", "BigUint64Array"]
)
Expand Down Expand Up @@ -277,7 +277,7 @@ public let CodeGenerators: [CodeGenerator] = [

CodeGenerator("DisposableVariableGenerator", inContext: .subroutine, inputs: .one) { b, val in
assert(b.context.contains(.subroutine))
let dispose = b.getProperty("dispose", of: b.loadBuiltin("Symbol"));
let dispose = b.getProperty("dispose", of: b.createNamedVariableForBuiltin("Symbol"));
let disposableVariable = b.buildObjectLiteral { obj in
obj.addProperty("value", as: val)
obj.addComputedMethod(dispose, with: .parameters(n:0)) { args in
Expand All @@ -289,7 +289,7 @@ public let CodeGenerators: [CodeGenerator] = [

CodeGenerator("AsyncDisposableVariableGenerator", inContext: .asyncFunction, inputs: .one) { b, val in
assert(b.context.contains(.asyncFunction))
let asyncDispose = b.getProperty("asyncDispose", of: b.loadBuiltin("Symbol"))
let asyncDispose = b.getProperty("asyncDispose", of: b.createNamedVariableForBuiltin("Symbol"))
let asyncDisposableVariable = b.buildObjectLiteral { obj in
obj.addProperty("value", as: val)
obj.addComputedMethod(asyncDispose, with: .parameters(n:0)) { args in
Expand Down Expand Up @@ -770,11 +770,26 @@ public let CodeGenerators: [CodeGenerator] = [

// We don't treat this as a ValueGenerator since it doesn't create a new value, it only accesses an existing one.
CodeGenerator("BuiltinGenerator") { b in
b.loadBuiltin(b.randomBuiltin())
b.createNamedVariableForBuiltin(b.randomBuiltin())
},

CodeGenerator("NamedVariableGenerator") { b in
// We're using the custom property names set from the environment for named variables.
// It's not clear if there's something better since that set should be relatively small
// (increasing the probability that named variables will be reused), and it also makes
// sense to use property names if we're inside a `with` statement.
let name = b.randomCustomPropertyName()
let declarationMode = chooseUniform(from: NamedVariableDeclarationMode.allCases)
if declarationMode != .none {
b.createNamedVariable(name, declarationMode: declarationMode, initialValue: b.randomVariable())
} else {
b.createNamedVariable(name, declarationMode: declarationMode)
}
},

CodeGenerator("BuiltinOverwriteGenerator", inputs: .one) { b, value in
b.storeNamedVariable(b.randomBuiltin(), value)
let builtin = b.createNamedVariable(b.randomBuiltin(), declarationMode: .none)
b.reassign(builtin, to: value)
},

RecursiveCodeGenerator("PlainFunctionGenerator") { b in
Expand Down Expand Up @@ -1477,7 +1492,7 @@ public let CodeGenerators: [CodeGenerator] = [
//

CodeGenerator("WellKnownPropertyLoadGenerator", inputs: .preferred(.object())) { b, obj in
let Symbol = b.loadBuiltin("Symbol")
let Symbol = b.createNamedVariableForBuiltin("Symbol")
// The Symbol constructor is just a "side effect" of this generator and probably shouldn't be used by following generators.
b.hide(Symbol)
let name = chooseUniform(from: JavaScriptEnvironment.wellKnownSymbols)
Expand All @@ -1486,7 +1501,7 @@ public let CodeGenerators: [CodeGenerator] = [
},

CodeGenerator("WellKnownPropertyStoreGenerator", inputs: .preferred(.object())) { b, obj in
let Symbol = b.loadBuiltin("Symbol")
let Symbol = b.createNamedVariableForBuiltin("Symbol")
b.hide(Symbol)
let name = chooseUniform(from: JavaScriptEnvironment.wellKnownSymbols)
let propertyName = b.getProperty(name, of: Symbol)
Expand All @@ -1511,13 +1526,13 @@ public let CodeGenerators: [CodeGenerator] = [
CodeGenerator("MethodCallWithDifferentThisGenerator", inputs: .preferred(.object(), .object())) { b, obj, this in
guard let methodName = b.type(of: obj).randomMethod() else { return }
let arguments = b.randomArguments(forCallingMethod: methodName, on: obj)
let Reflect = b.loadBuiltin("Reflect")
let Reflect = b.createNamedVariableForBuiltin("Reflect")
let args = b.createArray(with: arguments)
b.callMethod("apply", on: Reflect, withArgs: [b.getProperty(methodName, of: obj), this, args])
},

CodeGenerator("ConstructWithDifferentNewTargetGenerator", inputs: .preferred(.constructor(), .constructor())) { b, newTarget, constructor in
let reflect = b.loadBuiltin("Reflect")
let reflect = b.createNamedVariableForBuiltin("Reflect")
let arguments = [constructor, b.createArray(with: b.randomArguments(forCalling: constructor)), newTarget]
b.callMethod("construct", on: reflect, withArgs: arguments)
},
Expand All @@ -1543,7 +1558,7 @@ public let CodeGenerators: [CodeGenerator] = [
}
let handler = b.createObject(with: handlerProperties)

let Proxy = b.loadBuiltin("Proxy")
let Proxy = b.createNamedVariableForBuiltin("Proxy")
b.hide(Proxy)// We want the proxy to be used by following code generators, not the Proxy constructor
b.construct(Proxy, withArgs: [target, handler])
},
Expand All @@ -1553,7 +1568,7 @@ public let CodeGenerators: [CodeGenerator] = [
// TODO could provide type hints here for the parameters.
b.buildRecursive()
}
let Promise = b.loadBuiltin("Promise")
let Promise = b.createNamedVariableForBuiltin("Promise")
b.hide(Promise) // We want the promise to be used by following code generators, not the Promise constructor
b.construct(Promise, withArgs: [handler])
},
Expand All @@ -1580,41 +1595,19 @@ public let CodeGenerators: [CodeGenerator] = [
// Generates a JavaScript 'with' statement
RecursiveCodeGenerator("WithStatementGenerator", inputs: .preferred(.object())) { b, obj in
b.buildWith(obj) {
withProbability(0.5, do: { () -> Void in
let propertyName = b.type(of: obj).randomProperty() ?? b.randomCustomPropertyName()
b.loadNamedVariable(propertyName)
}, else: { () -> Void in
for i in 1...3 {
let propertyName = b.type(of: obj).randomProperty() ?? b.randomCustomPropertyName()
let value = b.randomVariable()
b.storeNamedVariable(propertyName, value)
})
b.createNamedVariable(propertyName, declarationMode: .none)
}
b.buildRecursive()
}
},

CodeGenerator("NamedVariableLoadGenerator") { b in
// We're using the custom property names set from the environment for named variables.
// It's not clear if there's something better since that set should be relatively small
// (increasing the probability that named variables will be reused), and it also makes
// sense to use property names if we're inside a `with` statement.
b.loadNamedVariable(b.randomCustomPropertyName())
},

CodeGenerator("NamedVariableStoreGenerator") { b in
let value = b.randomVariable()
b.storeNamedVariable(b.randomCustomPropertyName(), value)
},

CodeGenerator("NamedVariableDefinitionGenerator") { b in
let value = b.randomVariable()
b.defineNamedVariable(b.randomCustomPropertyName(), value)
},

RecursiveCodeGenerator("EvalGenerator") { b in
let code = b.buildCodeString() {
b.buildRecursive()
}
let eval = b.loadBuiltin("eval")
let eval = b.createNamedVariableForBuiltin("eval")
b.callFunction(eval, withArgs: [code])
},

Expand All @@ -1629,7 +1622,7 @@ public let CodeGenerators: [CodeGenerator] = [
let numComputations = Int.random(in: 3...7)

// Common mathematical operations are exposed through the Math builtin in JavaScript.
let Math = b.loadBuiltin("Math")
let Math = b.createNamedVariableForBuiltin("Math")
b.hide(Math) // Following code generators should use the numbers generated below, not the Math object.

var values = b.randomVariables(upTo: Int.random(in: 1...3))
Expand Down Expand Up @@ -1676,7 +1669,7 @@ public let CodeGenerators: [CodeGenerator] = [
}
}
} else {
let toPrimitive = b.getProperty("toPrimitive", of: b.loadBuiltin("Symbol"))
let toPrimitive = b.getProperty("toPrimitive", of: b.createNamedVariableForBuiltin("Symbol"))
imitation = b.buildObjectLiteral { obj in
obj.addComputedMethod(toPrimitive, with: .parameters(n: 0)) { _ in
b.buildRecursive(n: 3)
Expand All @@ -1689,7 +1682,7 @@ public let CodeGenerators: [CodeGenerator] = [
// A lot of functions are also objects, so we could handle them either way. However, it probably makes more sense to handle
// them as a function since they would otherwise no longer be callable.
let handler = b.createObject(with: [:])
let Proxy = b.loadBuiltin("Proxy")
let Proxy = b.createNamedVariableForBuiltin("Proxy")
imitation = b.construct(Proxy, withArgs: [orig, handler])
} else if b.type(of: orig).Is(.object()) {
// Either make a class that extends that object's constructor or make a new object with the original object as prototype.
Expand Down Expand Up @@ -1724,12 +1717,12 @@ public let CodeGenerators: [CodeGenerator] = [
if maxSize < size {
maxSize = size
}
let ArrayBuffer = b.loadBuiltin("ArrayBuffer")
let ArrayBuffer = b.createNamedVariableForBuiltin("ArrayBuffer")
b.hide(ArrayBuffer)
let options = b.createObject(with: ["maxByteLength": b.loadInt(maxSize)])
let ab = b.construct(ArrayBuffer, withArgs: [b.loadInt(size), options])

let View = b.loadBuiltin(
let View = b.createNamedVariableForBuiltin(
chooseUniform(
from: ["Uint8Array", "Int8Array", "Uint16Array", "Int16Array", "Uint32Array", "Int32Array", "Float32Array", "Float64Array", "Uint8ClampedArray", "BigInt64Array", "BigUint64Array", "DataView"]
)
Expand All @@ -1743,12 +1736,12 @@ public let CodeGenerators: [CodeGenerator] = [
if maxSize < size {
maxSize = size
}
let ArrayBuffer = b.loadBuiltin("SharedArrayBuffer")
let ArrayBuffer = b.createNamedVariableForBuiltin("SharedArrayBuffer")
b.hide(ArrayBuffer)
let options = b.createObject(with: ["maxByteLength": b.loadInt(maxSize)])
let ab = b.construct(ArrayBuffer, withArgs: [b.loadInt(size), options])

let View = b.loadBuiltin(
let View = b.createNamedVariableForBuiltin(
chooseUniform(
from: ["Uint8Array", "Int8Array", "Uint16Array", "Int16Array", "Uint32Array", "Int32Array", "Float32Array", "Float64Array", "Uint8ClampedArray", "BigInt64Array", "BigUint64Array", "DataView"]
)
Expand Down Expand Up @@ -1802,7 +1795,7 @@ public let CodeGenerators: [CodeGenerator] = [
},

CodeGenerator("IteratorGenerator") { b in
let Symbol = b.loadBuiltin("Symbol")
let Symbol = b.createNamedVariableForBuiltin("Symbol")
b.hide(Symbol)
let iteratorSymbol = b.getProperty("iterator", of: Symbol)
b.hide(iteratorSymbol)
Expand Down
6 changes: 3 additions & 3 deletions Sources/Fuzzilli/CodeGen/ProgramTemplates.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public let ProgramTemplates = [
b.build(n: 25)

// Generate random JSON payloads by stringifying random values
let JSON = b.loadBuiltin("JSON")
let JSON = b.createNamedVariableForBuiltin("JSON")
var jsonPayloads = [Variable]()
for _ in 0..<Int.random(in: 1...5) {
let json = b.callMethod("stringify", on: JSON, withArgs: [b.randomVariable()])
Expand All @@ -242,7 +242,7 @@ public let ProgramTemplates = [
// Helper function to pick a random index in the json string.
let randIndex = b.buildPlainFunction(with: .parameters(.integer)) { args in
let max = args[0]
let Math = b.loadBuiltin("Math")
let Math = b.createNamedVariableForBuiltin("Math")
// We "hardcode" the random value here (instead of calling `Math.random()` in JS) so that testcases behave deterministically.
var random = b.loadFloat(Double.random(in: 0..<1))
random = b.binary(random, max, with: .Mul)
Expand All @@ -252,7 +252,7 @@ public let ProgramTemplates = [

// Flip a random character of the JSON string:
// Select a random index at which to flip the character.
let String = b.loadBuiltin("String")
let String = b.createNamedVariableForBuiltin("String")
let length = b.getProperty("length", of: json)
let index = b.callFunction(randIndex, withArgs: [length])

Expand Down
Loading

0 comments on commit 3e40cbe

Please sign in to comment.