Skip to content

Commit 2f24d8c

Browse files
committed
Add guards for possible invalid function calls
This patch adds an easy way for callers of randomVariable and randomArguments to know when the returned variables may not fulfill the given constraints. It also adds guards to various function calls if this is the case. Bug: 40272934 Change-Id: Id5dbc944ea23a0fb6486b0abdb65f5b1a5d358d7 Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/8543123 Reviewed-by: Matthias Liedtke <mliedtke@google.com> Commit-Queue: Hendrik Wüthrich <whendrik@google.com>
1 parent 6a16b25 commit 2f24d8c

4 files changed

Lines changed: 109 additions & 28 deletions

File tree

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -804,12 +804,15 @@ public class ProgramBuilder {
804804
/// It's the caller's responsibility to check the type of the returned variable to avoid runtime exceptions if necessary. For example, if performing a
805805
/// property access, the returned variable should be checked if it `MayBe(.nullish)` in which case a property access would result in a
806806
/// runtime exception and so should be appropriately guarded against that.
807+
/// This function also returns a boolean, `matches`. If matches is not true, it means that value being returned either `MayBe` the type requested, or
808+
/// is a random JsVariable.
807809
///
808810
/// If the variable must be of the specified type, use `randomVariable(ofType:)` instead.
809-
public func randomVariable(forUseAs type: ILType) -> Variable {
811+
public func randomVariable(forUseAsGuarded type: ILType) -> (variable: Variable, matches: Bool) {
810812
assert(type != .nothing)
811813

812814
var result: Variable? = nil
815+
var matches = true
813816

814817
// Prefer variables that are known to have the requested type if there's a sufficient number of them.
815818
if probability(probabilityOfVariableSelectionTryingToFindAnExactMatch) {
@@ -820,13 +823,24 @@ public class ProgramBuilder {
820823
// In particular, this query will include all variable for which we don't know the type as they'll
821824
// be typed as .jsAnything. We usually expect to have a lot of candidates available for this query,
822825
// so we don't check the number of them upfront as we do for the above query.
826+
// If findVariable(satisfying: { self.type(of: $0).Is(type) }) returned nil, we cannot be sure that
827+
// the result matches the requested type, so we set matches to be false.
823828
if result == nil {
829+
matches = false
824830
result = findVariable(satisfying: { self.type(of: $0).MayBe(type) })
825831
}
826832

827833
// Worst case fall back to completely random variables. This should happen rarely, as we'll usually have
828834
// at least some variables of type .jsAnything.
829-
return result ?? randomJsVariable()
835+
return (result ?? randomJsVariable(), matches)
836+
}
837+
838+
/// This function is a wrapper around `randomVariable(forUseAsGuarded type:)' which ignores the `matches`
839+
/// value that it returns.
840+
/// See the comment above the other function for details.
841+
public func randomVariable(forUseAs type: ILType) -> Variable {
842+
assert(type != .nothing)
843+
return randomVariable(forUseAsGuarded: type).variable
830844
}
831845

832846
/// Returns a random variable that is known to have the given type.
@@ -941,6 +955,29 @@ public class ProgramBuilder {
941955
return parameterTypes.map({ randomVariable(forUseAs: $0) })
942956
}
943957

958+
/// Find random variables to use as arguments for calling a function with the given parameters.
959+
///
960+
/// If any of the arguments returned does not match the type of its corresponding parameter,
961+
/// the boolean this function returns will be true. If everything matches, it will be false.
962+
public func randomArguments(forCallingGuardableFunction function: Variable) -> (arguments: [Variable], allArgsMatch: Bool) {
963+
let signature = type(of: function).signature ?? Signature.forUnknownFunction
964+
let params = signature.parameters
965+
assert(params.count == 0 || hasVisibleVariables)
966+
967+
let parameterTypes = ProgramBuilder.prepareArgumentTypes(forParameters: params)
968+
969+
var variables: [Variable] = []
970+
var allArgsMatch = true
971+
for type in parameterTypes {
972+
let (variable, matches) = randomVariable(forUseAsGuarded: type)
973+
variables.append(variable)
974+
allArgsMatch = allArgsMatch && matches
975+
}
976+
977+
return (variables, allArgsMatch)
978+
}
979+
980+
944981
/// Converts the JS world signature into a Wasm world signature.
945982
/// In practice this means that we will try to map JS types to corresponding Wasm types.
946983
/// E.g. .number becomes .wasmf32, .bigint will become .wasmi64, etc.

Sources/Fuzzilli/CodeGen/CodeGenerators.swift

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,12 @@ public let CodeGenerators: [CodeGenerator] = [
125125
let fctName = (useProperty ? prototypeType.properties : prototypeType.methods).randomElement()!
126126
let fct = b.getProperty(fctName, of: prototype)
127127
let fctType = b.type(of: fct)
128-
let arguments = b.randomArguments(forCalling: fct)
128+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: fct)
129129
let receiverType = fctType.receiver ?? prototypeType
130130
let desiredReceiverType = fctType.receiver ?? prototypeType
131131
let receiver = b.randomVariable(forUseAs: desiredReceiverType)
132132
let needGuard = (!fctType.Is(.function()) && !fctType.Is(.unboundFunction()))
133-
|| !b.type(of: receiver).Is(receiverType)
133+
|| !b.type(of: receiver).Is(receiverType) || !matches
134134
if Bool.random() {
135135
b.callMethod("call", on: fct, withArgs: [receiver] + arguments, guard: needGuard)
136136
} else {
@@ -918,7 +918,8 @@ public let CodeGenerators: [CodeGenerator] = [
918918
b.buildRecursive()
919919
b.doReturn(b.randomJsVariable())
920920
}
921-
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
921+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: f)
922+
b.callFunction(f, withArgs: arguments, guard: !matches)
922923
},
923924

924925
RecursiveCodeGenerator("StrictModeFunctionGenerator") { b in
@@ -929,7 +930,8 @@ public let CodeGenerators: [CodeGenerator] = [
929930
b.buildRecursive()
930931
b.doReturn(b.randomJsVariable())
931932
}
932-
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
933+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: f)
934+
b.callFunction(f, withArgs: arguments, guard: !matches)
933935
},
934936

935937
RecursiveCodeGenerator("ArrowFunctionGenerator") { b in
@@ -952,7 +954,8 @@ public let CodeGenerators: [CodeGenerator] = [
952954
}
953955
b.doReturn(b.randomJsVariable())
954956
}
955-
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
957+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: f)
958+
b.callFunction(f, withArgs: arguments, guard: !matches)
956959
},
957960

958961
RecursiveCodeGenerator("AsyncFunctionGenerator") { b in
@@ -961,7 +964,8 @@ public let CodeGenerators: [CodeGenerator] = [
961964
b.await(b.randomJsVariable())
962965
b.doReturn(b.randomJsVariable())
963966
}
964-
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
967+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: f)
968+
b.callFunction(f, withArgs: arguments, guard: !matches)
965969
},
966970

967971
RecursiveCodeGenerator("AsyncArrowFunctionGenerator") { b in
@@ -986,7 +990,8 @@ public let CodeGenerators: [CodeGenerator] = [
986990
}
987991
b.doReturn(b.randomJsVariable())
988992
}
989-
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
993+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: f)
994+
b.callFunction(f, withArgs: arguments, guard: !matches)
990995
},
991996

992997
CodeGenerator("PropertyRetrievalGenerator", inputs: .preferred(.object())) { b, obj in
@@ -1227,16 +1232,13 @@ public let CodeGenerators: [CodeGenerator] = [
12271232
},
12281233

12291234
CodeGenerator("FunctionCallGenerator", inputs: .preferred(.function())) { b, f in
1230-
let arguments = b.randomArguments(forCalling: f)
1231-
// TODO: we may also need guarding if the arguments aren't compatible with the expected ones
1232-
let needGuard = b.type(of: f).MayNotBe(.function())
1233-
b.callFunction(f, withArgs: arguments, guard: needGuard)
1235+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: f)
1236+
b.callFunction(f, withArgs: arguments, guard: !matches)
12341237
},
12351238

12361239
CodeGenerator("ConstructorCallGenerator", inputs: .preferred(.constructor())) { b, c in
1237-
let arguments = b.randomArguments(forCalling: c)
1238-
// TODO: we may also need guarding if the arguments aren't compatible with the expected ones
1239-
let needGuard = b.type(of: c).MayNotBe(.constructor())
1240+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: c)
1241+
let needGuard = b.type(of: c).MayNotBe(.constructor()) || !matches
12401242
b.construct(c, withArgs: arguments, guard: needGuard)
12411243
},
12421244

@@ -1261,22 +1263,20 @@ public let CodeGenerators: [CodeGenerator] = [
12611263
},
12621264

12631265
CodeGenerator("UnboundFunctionCallGenerator", inputs: .preferred(.unboundFunction())) { b, f in
1264-
let arguments = b.randomArguments(forCalling: f)
1266+
let (arguments, argsMatch) = b.randomArguments(forCallingGuardableFunction: f)
12651267
let fctType = b.type(of: f)
1266-
// TODO: we may also need guarding if the arguments aren't compatible with the expected ones
1267-
let needGuard = fctType.MayNotBe(.unboundFunction())
1268-
let receiver = b.randomVariable(forUseAs: fctType.receiver ?? .object())
1268+
let (receiver, recMatches) = b.randomVariable(forUseAsGuarded: fctType.receiver ?? .object())
1269+
let needGuard = fctType.MayNotBe(.unboundFunction()) || !argsMatch || !recMatches
12691270
// For simplicity we just hard-code the call function. If this was a separate IL
12701271
// instruction, the JSTyper could infer the result type.
12711272
b.callMethod("call", on: f, withArgs: [receiver] + arguments, guard: needGuard)
12721273
},
12731274

12741275
CodeGenerator("UnboundFunctionApplyGenerator", inputs: .preferred(.unboundFunction())) { b, f in
1275-
let arguments = b.randomArguments(forCalling: f)
1276+
let (arguments, argsMatch) = b.randomArguments(forCallingGuardableFunction: f)
12761277
let fctType = b.type(of: f)
1277-
// TODO: we may also need guarding if the arguments aren't compatible with the expected ones
1278-
let needGuard = fctType.MayNotBe(.unboundFunction())
1279-
let receiver = b.randomVariable(forUseAs: fctType.receiver ?? .object())
1278+
let (receiver, recMatches) = b.randomVariable(forUseAsGuarded: fctType.receiver ?? .object())
1279+
let needGuard = fctType.MayNotBe(.unboundFunction()) || !argsMatch || !recMatches
12801280
// For simplicity we just hard-code the apply function. If this was a separate IL
12811281
// instruction, the JSTyper could infer the result type.
12821282
b.callMethod("apply", on: f, withArgs: [receiver, b.createArray(with: arguments)], guard: needGuard)

Sources/FuzzilliCli/Profiles/V8CommonProfile.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,22 +254,26 @@ public let MapTransitionFuzzer = ProgramTemplate("MapTransitionFuzzer") { b in
254254
}
255255

256256
for _ in 0..<3 {
257-
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
257+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: f)
258+
b.callFunction(f, withArgs: arguments, guard: !matches)
258259
}
259260
}
260261
let functionCallGenerator = CodeGenerator("FunctionCall", inputs: .required(.function())) { b, f in
261262
assert(b.type(of: f).Is(.function()))
262-
let rval = b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
263+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: f)
264+
let rval = b.callFunction(f, withArgs: arguments, guard: !matches)
263265
}
264266
let constructorCallGenerator = CodeGenerator("ConstructorCall", inputs: .required(.constructor())) { b, c in
265267
assert(b.type(of: c).Is(.constructor()))
266-
let rval = b.construct(c, withArgs: b.randomArguments(forCalling: c))
268+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: c)
269+
let rval = b.construct(c, withArgs: arguments, guard: !matches)
267270
}
268271
let functionJitCallGenerator = CodeGenerator("FunctionJitCall", inputs: .required(.function())) { b, f in
269272
assert(b.type(of: f).Is(.function()))
270273
let args = b.randomArguments(forCalling: f)
271274
b.buildRepeatLoop(n: 100) { _ in
272-
b.callFunction(f, withArgs: args)
275+
let (arguments, matches) = b.randomArguments(forCallingGuardableFunction: f)
276+
b.callFunction(f, withArgs: arguments, guard: !matches)
273277
}
274278
}
275279

Tests/FuzzilliTests/ProgramBuilderTest.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,46 @@ class ProgramBuilderTests: XCTestCase {
260260
XCTAssertEqual(b.randomVariable(preferablyNotOfType: .jsAnything), nil)
261261
}
262262

263+
func testVariableRetrieval4() {
264+
// This testcase demonstrates the behavior of `b.randomVariable(forUseAsGuarded:)`
265+
// This API behaves in the following way:
266+
// - If a variable that matches or subsumes the requested type was found, it
267+
// returns the variable, along with a boolean that is true.
268+
// - If no matching variable was found, it will return one whose type intersects
269+
// with the requested one, or a random variable. In either of these cases, the
270+
// returned boolean will be false.
271+
272+
let fuzzer = makeMockFuzzer()
273+
let b = fuzzer.makeBuilder()
274+
275+
let obj = b.createObject(with: ["x": b.loadInt(1), "y": b.loadInt(2)])
276+
277+
// We created three visible variables before; obj and its two properties.
278+
XCTAssertEqual(b.numberOfVisibleVariables, 3)
279+
280+
b.probabilityOfVariableSelectionTryingToFindAnExactMatch = 1.0
281+
282+
do { // Search with exact match.
283+
let (foundVar, matches) = b.randomVariable(forUseAsGuarded: b.type(of: obj))
284+
XCTAssertEqual(obj, foundVar)
285+
XCTAssert(matches)
286+
}
287+
do { // Search for supertype.
288+
let (foundVar, matches) = b.randomVariable(forUseAsGuarded: .object(withProperties: ["x"]))
289+
XCTAssertEqual(obj, foundVar)
290+
XCTAssert(matches)
291+
}
292+
do { // Search for subtype.
293+
let (foundVar, matches) = b.randomVariable(forUseAsGuarded: .object(withProperties: ["x", "y", "z"]))
294+
XCTAssertEqual(obj, foundVar)
295+
XCTAssertFalse(matches)
296+
}
297+
do { // Search for unrelated type. We ignore the variable, since it's completely random.
298+
let (_, matches) = b.randomVariable(forUseAsGuarded: .string)
299+
XCTAssertFalse(matches)
300+
}
301+
}
302+
263303
func testRandomVarableInternal() {
264304
let fuzzer = makeMockFuzzer()
265305
let b = fuzzer.makeBuilder()

0 commit comments

Comments
 (0)