Skip to content

Commit f7dfd45

Browse files
author
Samuel Groß
committed
Implement Named Variables
This commit adds three new FuzzIL operations: v3 <- LoadNamedVariable 'foo' StoreNamedVariable 'bar' <- v1 DefineNamedVariable 'baz' <- v2 These are lifted, respectively, to: const v3 = foo; bar = v1; var baz = v2; This now makes a few things possible: - The use of the 'var' keyword - Triggerring variable hoisting (if a LoadNamedVariable happens before a DefineNamedVariable) - Use of global variables (if a named variable is loaded/stored but never defined) - Access to object properties inside a `with` statement (this previously required LoadFromScope/StoreToScope, which are now gone)
1 parent 2d98f7a commit f7dfd45

File tree

16 files changed

+397
-235
lines changed

16 files changed

+397
-235
lines changed

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,11 @@ public class ProgramBuilder {
19951995
return emit(BinaryOperation(op), withInputs: [lhs, rhs]).output
19961996
}
19971997

1998+
@discardableResult
1999+
public func ternary(_ condition: Variable, _ lhs: Variable, _ rhs: Variable) -> Variable {
2000+
return emit(TernaryOperation(), withInputs: [condition, lhs, rhs]).output
2001+
}
2002+
19982003
public func reassign(_ output: Variable, to input: Variable, with op: BinaryOperator) {
19992004
emit(Update(op), withInputs: [output, input])
20002005
}
@@ -2034,8 +2039,16 @@ public class ProgramBuilder {
20342039
}
20352040

20362041
@discardableResult
2037-
public func ternary(_ condition: Variable, _ lhs: Variable, _ rhs: Variable) -> Variable {
2038-
return emit(TernaryOperation(), withInputs: [condition, lhs, rhs]).output
2042+
public func loadNamedVariable(_ name: String) -> Variable {
2043+
return emit(LoadNamedVariable(name)).output
2044+
}
2045+
2046+
public func storeNamedVariable(_ name: String, _ value: Variable) {
2047+
emit(StoreNamedVariable(name), withInputs: [value])
2048+
}
2049+
2050+
public func defineNamedVariable(_ name: String, _ value: Variable) {
2051+
emit(DefineNamedVariable(name), withInputs: [value])
20392052
}
20402053

20412054
public func eval(_ string: String, with arguments: [Variable] = []) {
@@ -2048,15 +2061,6 @@ public class ProgramBuilder {
20482061
emit(EndWith())
20492062
}
20502063

2051-
@discardableResult
2052-
public func loadFromScope(id: String) -> Variable {
2053-
return emit(LoadFromScope(id: id)).output
2054-
}
2055-
2056-
public func storeToScope(_ value: Variable, as id: String) {
2057-
emit(StoreToScope(id: id), withInputs: [value])
2058-
}
2059-
20602064
public func nop(numOutputs: Int = 0) {
20612065
emit(Nop(numOutputs: numOutputs), withInputs: [])
20622066
}

Sources/Fuzzilli/CodeGen/CodeGenerators.swift

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,25 +1368,32 @@ public let CodeGenerators: [CodeGenerator] = [
13681368
b.buildWith(obj) {
13691369
withProbability(0.5, do: { () -> Void in
13701370
let propertyName = b.type(of: obj).randomProperty() ?? b.randomCustomPropertyName()
1371-
b.loadFromScope(id: propertyName)
1371+
b.loadNamedVariable(propertyName)
13721372
}, else: { () -> Void in
13731373
let propertyName = b.type(of: obj).randomProperty() ?? b.randomCustomPropertyName()
13741374
let value = b.randomVariable()
1375-
b.storeToScope(value, as: propertyName)
1375+
b.storeNamedVariable(propertyName, value)
13761376
})
13771377
b.buildRecursive()
13781378
}
13791379
},
13801380

1381-
CodeGenerator("LoadFromScopeGenerator", inContext: .with) { b in
1382-
assert(b.context.contains(.with))
1383-
b.loadFromScope(id: b.randomPropertyName())
1381+
CodeGenerator("NamedVariableLoadGenerator") { b in
1382+
// We're using the custom property names set from the environment for named variables.
1383+
// It's not clear if there's something better since that set should be relatively small
1384+
// (increasing the probability that named variables will be reused), and it also makes
1385+
// sense to use property names if we're inside a `with` statement.
1386+
b.loadNamedVariable(b.randomCustomPropertyName())
13841387
},
13851388

1386-
CodeGenerator("StoreToScopeGenerator", inContext: .with) { b in
1387-
assert(b.context.contains(.with))
1389+
CodeGenerator("NamedVariableStoreGenerator") { b in
13881390
let value = b.randomVariable()
1389-
b.storeToScope(value, as: b.randomPropertyName())
1391+
b.storeNamedVariable(b.randomCustomPropertyName(), value)
1392+
},
1393+
1394+
CodeGenerator("NamedVariableDefinitionGenerator") { b in
1395+
let value = b.randomVariable()
1396+
b.defineNamedVariable(b.randomCustomPropertyName(), value)
13901397
},
13911398

13921399
RecursiveCodeGenerator("EvalGenerator") { b in

Sources/Fuzzilli/FuzzIL/Instruction.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,12 @@ extension Instruction: ProtobufConvertible {
632632
}
633633
case .compare(let op):
634634
$0.compare = Fuzzilli_Protobuf_Compare.with { $0.op = convertEnum(op.op, Comparator.allCases) }
635+
case .loadNamedVariable(let op):
636+
$0.loadNamedVariable = Fuzzilli_Protobuf_LoadNamedVariable.with { $0.variableName = op.variableName }
637+
case .storeNamedVariable(let op):
638+
$0.storeNamedVariable = Fuzzilli_Protobuf_StoreNamedVariable.with { $0.variableName = op.variableName }
639+
case .defineNamedVariable(let op):
640+
$0.defineNamedVariable = Fuzzilli_Protobuf_DefineNamedVariable.with { $0.variableName = op.variableName }
635641
case .eval(let op):
636642
$0.eval = Fuzzilli_Protobuf_Eval.with { $0.code = op.code }
637643
case .callSuperConstructor:
@@ -666,10 +672,6 @@ extension Instruction: ProtobufConvertible {
666672
$0.beginWith = Fuzzilli_Protobuf_BeginWith()
667673
case .endWith:
668674
$0.endWith = Fuzzilli_Protobuf_EndWith()
669-
case .loadFromScope(let op):
670-
$0.loadFromScope = Fuzzilli_Protobuf_LoadFromScope.with { $0.id = op.id }
671-
case .storeToScope(let op):
672-
$0.storeToScope = Fuzzilli_Protobuf_StoreToScope.with { $0.id = op.id }
673675
case .beginIf(let op):
674676
$0.beginIf = Fuzzilli_Protobuf_BeginIf.with {
675677
$0.inverted = op.inverted
@@ -1033,6 +1035,12 @@ extension Instruction: ProtobufConvertible {
10331035
op = DestructObjectAndReassign(properties: p.properties, hasRestElement: p.hasRestElement_p)
10341036
case .compare(let p):
10351037
op = Compare(try convertEnum(p.op, Comparator.allCases))
1038+
case .loadNamedVariable(let p):
1039+
op = LoadNamedVariable(p.variableName)
1040+
case .storeNamedVariable(let p):
1041+
op = StoreNamedVariable(p.variableName)
1042+
case .defineNamedVariable(let p):
1043+
op = DefineNamedVariable(p.variableName)
10361044
case .eval(let p):
10371045
op = Eval(p.code, numArguments: inouts.count)
10381046
case .callSuperConstructor:
@@ -1061,10 +1069,6 @@ extension Instruction: ProtobufConvertible {
10611069
op = BeginWith()
10621070
case .endWith:
10631071
op = EndWith()
1064-
case .loadFromScope(let p):
1065-
op = LoadFromScope(id: p.id)
1066-
case .storeToScope(let p):
1067-
op = StoreToScope(id: p.id)
10681072
case .beginIf(let p):
10691073
op = BeginIf(inverted: p.inverted)
10701074
case .beginElse:

Sources/Fuzzilli/FuzzIL/JSTyper.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,8 @@ public struct JSTyper: Analyzer {
675675
case .compare:
676676
set(instr.output, .boolean)
677677

678-
case .loadFromScope:
678+
case .loadNamedVariable:
679+
// We don't currently track these.
679680
set(instr.output, .unknown)
680681

681682
case .await:

Sources/Fuzzilli/FuzzIL/JsOperations.swift

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,6 +1428,48 @@ final class Compare: JsOperation {
14281428
}
14291429
}
14301430

1431+
// Named Variables.
1432+
//
1433+
// Named variables are used to cover global and `var` variables in JavaScript
1434+
// as well as to support variable hoisting.
1435+
//
1436+
// When a named variable is defined, it becomes a `var` variable in JavaScript.
1437+
// However, it is allowed to access (load/store) a named variable without
1438+
// defining it first, in which case the access becomes either a global variable
1439+
// access (if the variable isn't later defined) or a hoisted variable access.
1440+
final class LoadNamedVariable: JsOperation {
1441+
override var opcode: Opcode { .loadNamedVariable(self) }
1442+
1443+
let variableName: String
1444+
1445+
init(_ name: String) {
1446+
self.variableName = name
1447+
super.init(numOutputs: 1, attributes: .isMutable)
1448+
}
1449+
}
1450+
1451+
final class StoreNamedVariable: JsOperation {
1452+
override var opcode: Opcode { .storeNamedVariable(self) }
1453+
1454+
let variableName: String
1455+
1456+
init(_ name: String) {
1457+
self.variableName = name
1458+
super.init(numInputs: 1, attributes: .isMutable)
1459+
}
1460+
}
1461+
1462+
final class DefineNamedVariable: JsOperation {
1463+
override var opcode: Opcode { .defineNamedVariable(self) }
1464+
1465+
let variableName: String
1466+
1467+
init(_ name: String) {
1468+
self.variableName = name
1469+
super.init(numInputs: 1, attributes: .isMutable)
1470+
}
1471+
}
1472+
14311473
/// An operation that will be lifted to a given string. The string can use %@ placeholders which
14321474
/// will be replaced by the expressions for the input variables during lifting.
14331475
final class Eval: JsOperation {
@@ -1457,28 +1499,6 @@ final class EndWith: JsOperation {
14571499
}
14581500
}
14591501

1460-
final class LoadFromScope: JsOperation {
1461-
override var opcode: Opcode { .loadFromScope(self) }
1462-
1463-
let id: String
1464-
1465-
init(id: String) {
1466-
self.id = id
1467-
super.init(numOutputs: 1, attributes: [.isMutable], requiredContext: [.javascript, .with])
1468-
}
1469-
}
1470-
1471-
final class StoreToScope: JsOperation {
1472-
override var opcode: Opcode { .storeToScope(self) }
1473-
1474-
let id: String
1475-
1476-
init(id: String) {
1477-
self.id = id
1478-
super.init(numInputs: 1, attributes: [.isMutable], requiredContext: [.javascript, .with])
1479-
}
1480-
}
1481-
14821502
final class CallSuperConstructor: JsOperation {
14831503
override var opcode: Opcode { .callSuperConstructor(self) }
14841504

Sources/Fuzzilli/FuzzIL/Opcodes.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,12 @@ enum Opcode {
143143
case destructObject(DestructObject)
144144
case destructObjectAndReassign(DestructObjectAndReassign)
145145
case compare(Compare)
146+
case loadNamedVariable(LoadNamedVariable)
147+
case storeNamedVariable(StoreNamedVariable)
148+
case defineNamedVariable(DefineNamedVariable)
146149
case eval(Eval)
147150
case beginWith(BeginWith)
148151
case endWith(EndWith)
149-
case loadFromScope(LoadFromScope)
150-
case storeToScope(StoreToScope)
151152
case callSuperConstructor(CallSuperConstructor)
152153
case callSuperMethod(CallSuperMethod)
153154
case getPrivateProperty(GetPrivateProperty)

Sources/Fuzzilli/Lifting/FuzzILLifter.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,15 @@ public class FuzzILLifter: Lifter {
462462
case .compare(let op):
463463
w.emit("\(output()) <- Compare \(input(0)), '\(op.op.token)', \(input(1))")
464464

465+
case .loadNamedVariable(let op):
466+
w.emit("\(output()) <- LoadNamedVariable '\(op.variableName)'")
467+
468+
case .storeNamedVariable(let op):
469+
w.emit("StoreNamedVariable '\(op.variableName)' <- \(input(0))")
470+
471+
case .defineNamedVariable(let op):
472+
w.emit("DefineNamedVariable '\(op.variableName)' <- \(input(0))")
473+
465474
case .eval(let op):
466475
let args = instr.inputs.map(lift).joined(separator: ", ")
467476
w.emit("Eval '\(op.code)', [\(args)]")
@@ -481,12 +490,6 @@ public class FuzzILLifter: Lifter {
481490
w.decreaseIndentionLevel()
482491
w.emit("EndWith")
483492

484-
case .loadFromScope(let op):
485-
w.emit("\(output()) <- LoadFromScope '\(op.id)'")
486-
487-
case .storeToScope(let op):
488-
w.emit("StoreToScope '\(op.id)', \(input(0))")
489-
490493
case .nop:
491494
w.emit("Nop")
492495

Sources/Fuzzilli/Lifting/JavaScriptLifter.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,19 @@ public class JavaScriptLifter: Lifter {
762762
let expr = BinaryExpression.new() + lhs + " " + op.op.token + " " + rhs
763763
w.assign(expr, to: instr.output)
764764

765+
case .loadNamedVariable(let op):
766+
w.assign(Identifier.new(op.variableName), to: instr.output)
767+
768+
case .storeNamedVariable(let op):
769+
let NAME = op.variableName
770+
let VALUE = w.retrieve(expressionFor: instr.input(0))
771+
w.emit("\(NAME) = \(VALUE);")
772+
773+
case .defineNamedVariable(let op):
774+
let NAME = op.variableName
775+
let VALUE = w.retrieve(expressionFor: instr.input(0))
776+
w.emit("var \(NAME) = \(VALUE);")
777+
765778
case .eval(let op):
766779
// Woraround until Strings implement the CVarArg protocol in the linux Foundation library...
767780
// TODO can make this permanent, but then use different placeholder pattern
@@ -794,14 +807,6 @@ public class JavaScriptLifter: Lifter {
794807
w.leaveCurrentBlock()
795808
w.emit("}")
796809

797-
case .loadFromScope(let op):
798-
w.assign(Identifier.new(op.id), to: instr.output)
799-
800-
case .storeToScope(let op):
801-
let NAME = op.id
802-
let VALUE = w.retrieve(expressionFor: instr.input(0))
803-
w.emit("\(NAME) = \(VALUE);")
804-
805810
case .nop:
806811
break
807812

Sources/Fuzzilli/Mutators/OperationMutator.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,13 @@ public class OperationMutator: BaseInstructionMutator {
198198
newOp = DestructObjectAndReassign(properties: newProperties.sorted(), hasRestElement: !op.hasRestElement)
199199
case .compare(_):
200200
newOp = Compare(chooseUniform(from: Comparator.allCases))
201-
case .loadFromScope(_):
202-
newOp = LoadFromScope(id: b.randomPropertyName())
203-
case .storeToScope(_):
204-
newOp = StoreToScope(id: b.randomPropertyName())
201+
case .loadNamedVariable:
202+
// We just use property names as variable names here. It's not clear if there's a better alternative and this also works well with `with` statements.
203+
newOp = LoadNamedVariable(b.randomPropertyName())
204+
case .storeNamedVariable:
205+
newOp = StoreNamedVariable(b.randomPropertyName())
206+
case .defineNamedVariable:
207+
newOp = DefineNamedVariable(b.randomPropertyName())
205208
case .callSuperMethod(let op):
206209
let methodName = b.currentSuperType().randomMethod() ?? b.randomMethodName()
207210
newOp = CallSuperMethod(methodName: methodName, numArguments: op.numArguments)

0 commit comments

Comments
 (0)