Skip to content

Commit 3d901fa

Browse files
mi-acV8-internal LUCI CQ
authored andcommitted
Support more valid JS method names
With this change we support defining methods on classes and objects with non-identifier names, like number and string literals. Internally, all method names remain strings, reusing any type information. At lifting, we approximate simple identifiers and use them unquoted for method definition and for usage in dot notation. For definitions, we also support quoted strings and unquoted index values. At call sites, we ensure bracket notation where needed, supporting index access without quotes. This covers method names for plain objects and classes. This does not cover properties, getters and setters yet. We also add 2 custom method names to the environment that don't follow the previous identifier naming. Instructions that define such methods currently are: ObjectLiteralMethod ClassInstanceMethod ClassStaticMethod Instructions that use such methods are: CallMethod CallMethodWithSpread CallSuperMethod BindMethod We ignore definitions and calls of private methods. They also reuse the same typer logic, but naming rules are more strict here, non-identifiers are not supported and should never be produced. We need to separate now identifiers for private and other method names in the JS environment. This also extends the compiler to enable importing the new method types. Bug: 446634535 Change-Id: I2b8fbb8306e4b6bd901b61952c6da91d4210ae3f Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/9047716 Reviewed-by: Dominik Klemba <tacet@google.com> Reviewed-by: Matthias Liedtke <mliedtke@google.com> Commit-Queue: Michael Achenbach <machenbach@google.com>
1 parent 159714e commit 3d901fa

9 files changed

Lines changed: 340 additions & 30 deletions

File tree

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,13 @@ public class ProgramBuilder {
487487
return chooseUniform(from: fuzzer.environment.customMethods)
488488
}
489489

490+
/// Returns a random custom private method name.
491+
///
492+
/// As above but for private methods, where a # symbol will be prepended.
493+
public func randomCustomPrivateMethodName() -> String {
494+
return chooseUniform(from: fuzzer.environment.customPrivateMethods)
495+
}
496+
490497
/// Returns either a builtin or a custom method name, with equal probability.
491498
public func randomMethodName() -> String {
492499
return probability(0.5) ? randomBuiltinMethodName() : randomCustomMethodName()

Sources/Fuzzilli/CodeGen/CodeGenerators.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,8 +1236,8 @@ public let CodeGenerators: [CodeGenerator] = [
12361236
inContext: .single(.classDefinition),
12371237
provides: [.javascript, .subroutine, .method, .classMethod]
12381238
) { b in
1239-
// Try to find a private field that hasn't already been added to this class.
1240-
let methodName = b.generateString(b.randomCustomMethodName,
1239+
// Try to find a private method that hasn't already been added to this class.
1240+
let methodName = b.generateString(b.randomCustomPrivateMethodName,
12411241
notIn: b.currentClassDefinition.privateFields)
12421242
let parameters = b.randomParameters()
12431243
b.emit(
@@ -1273,8 +1273,8 @@ public let CodeGenerators: [CodeGenerator] = [
12731273
inContext: .single(.classDefinition),
12741274
provides: [.javascript, .subroutine, .method, .classMethod]
12751275
) { b in
1276-
// Try to find a private field that hasn't already been added to this class.
1277-
let methodName = b.generateString(b.randomCustomMethodName,
1276+
// Try to find a private method that hasn't already been added to this class.
1277+
let methodName = b.generateString(b.randomCustomPrivateMethodName,
12781278
notIn: b.currentClassDefinition.privateFields)
12791279
let parameters = b.randomParameters()
12801280
b.emit(

Sources/Fuzzilli/Compiler/Compiler.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,12 @@ public class JavaScriptCompiler {
170170
} else {
171171
head = emit(BeginClassInstanceMethod(methodName: name, parameters: parameters))
172172
}
173-
case .index:
174-
throw CompilerError.invalidNodeError("Not supported")
173+
case .index(let index):
174+
if method.isStatic {
175+
head = emit(BeginClassStaticMethod(methodName: String(index), parameters: parameters))
176+
} else {
177+
head = emit(BeginClassInstanceMethod(methodName: String(index), parameters: parameters))
178+
}
175179
case .expression:
176180
if method.isStatic {
177181
head = emit(BeginClassStaticComputedMethod(parameters: parameters), withInputs: [computedKeys.removeLast()])
@@ -197,7 +201,11 @@ public class JavaScriptCompiler {
197201
emit(EndClassInstanceMethod())
198202
}
199203
case .index:
200-
throw CompilerError.invalidNodeError("Not supported")
204+
if method.isStatic {
205+
emit(EndClassStaticMethod())
206+
} else {
207+
emit(EndClassInstanceMethod())
208+
}
201209
case .expression:
202210
if method.isStatic {
203211
emit(EndClassStaticComputedMethod())

Sources/Fuzzilli/Compiler/Parser/parser.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,15 @@ function parse(script, proto) {
492492
if (method.computed) {
493493
out.expression = visitExpression(method.key);
494494
} else {
495-
assert(method.key.type === 'Identifier', "Expected method.key.type to be exactly 'Identifier'")
496-
out.name = method.key.name;
495+
if (method.key.type === 'Identifier') {
496+
out.name = method.key.name;
497+
} else if (method.key.type === 'NumericLiteral') {
498+
out.name = String(method.key.value);
499+
} else if (method.key.type === 'StringLiteral') {
500+
out.name = method.key.value;
501+
} else {
502+
throw "Unknown method key type: " + method.key.type;
503+
}
497504
}
498505

499506
field = {};

Sources/Fuzzilli/Environment/JavaScriptEnvironment.swift

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,13 @@ public class JavaScriptEnvironment: ComponentBase {
290290

291291
/// Identifiers that should be used for custom properties and methods.
292292
public static let CustomPropertyNames = ["a", "b", "c", "d", "e", "f", "g", "h"]
293-
public static let CustomMethodNames = ["m", "n", "o", "p", "valueOf", "toString"]
293+
public static let CustomMethodNames = ["m", "n", "o", "?", "67", "valueOf", "toString"]
294+
public static let CustomPrivateMethodNames = ["m", "n", "o", "p"]
294295

295296
public private(set) var builtins = Set<String>()
296297
public let customProperties = Set<String>(CustomPropertyNames)
297298
public let customMethods = Set<String>(CustomMethodNames)
299+
public let customPrivateMethods = Set<String>(CustomPrivateMethodNames)
298300
public private(set) var builtinProperties = Set<String>()
299301
public private(set) var builtinMethods = Set<String>()
300302

@@ -314,7 +316,35 @@ public class JavaScriptEnvironment: ComponentBase {
314316
private var producingProperties: [ILType: [(group: String, property: String)]] = [:]
315317
private var subtypes: [ILType: [ILType]] = [:]
316318

319+
private let validMethodDefinitionName: Regex<AnyRegexOutput>?
320+
private let validDotNotationName: Regex<AnyRegexOutput>?
321+
private let validPropertyIndex: Regex<AnyRegexOutput>?
322+
317323
public init(additionalBuiltins: [String: ILType] = [:], additionalObjectGroups: [ObjectGroup] = [], additionalEnumerations: [ILType] = []) {
324+
// A simple approximation of a valid JS identifier (used in dot
325+
// notation and for method definitions).
326+
let simpleId = "[_$a-zA-Z][_$a-zA-Z0-9]*"
327+
328+
// A non-negative integer (with no leading zero) for index access
329+
// without quotes.
330+
let index = "[1-9]\\d*|0"
331+
332+
// Initialize regular expressions to determine valid property
333+
// identifiers:
334+
335+
// At method definition site, we support simple identifiers and
336+
// non-negative numbers. Other names will be quoted.
337+
// We don't support unquoted doubles.
338+
self.validMethodDefinitionName = try! Regex("^(\(index)|\(simpleId))$")
339+
340+
// For dot notation we only support simple identifiers. We don't
341+
// support all possible names according to JS spec.
342+
// Unsupported names will be accessed with bracket notation.
343+
self.validDotNotationName = try! Regex("^(\(simpleId))$")
344+
345+
// Valid indexes to use in bracket notation without quotes.
346+
self.validPropertyIndex = try! Regex("^(\(index))$")
347+
318348
super.init(name: "JavaScriptEnvironment")
319349

320350
// Build model of the JavaScript environment
@@ -655,6 +685,7 @@ public class JavaScriptEnvironment: ComponentBase {
655685
logger.info("Have \(builtinMethods.count) builtin method names: \(builtinMethods.sorted())")
656686
logger.info("Have \(customProperties.count) custom property names: \(customProperties.sorted())")
657687
logger.info("Have \(customMethods.count) custom method names: \(customMethods.sorted())")
688+
logger.info("Have \(customPrivateMethods.count) custom private method names: \(customPrivateMethods.sorted())")
658689
}
659690

660691
func checkConstructorAvailability() {
@@ -936,6 +967,18 @@ public class JavaScriptEnvironment: ComponentBase {
936967
$0 || type.Is($1)
937968
}
938969
}
970+
971+
func isValidNameForMethodDefinition(_ name: String) -> Bool {
972+
return (try? validMethodDefinitionName?.wholeMatch(in: name)) != nil
973+
}
974+
975+
func isValidDotNotationName(_ name: String) -> Bool {
976+
return (try? validDotNotationName?.wholeMatch(in: name)) != nil
977+
}
978+
979+
func isValidPropertyIndex(_ name: String) -> Bool {
980+
return (try? validPropertyIndex?.wholeMatch(in: name)) != nil
981+
}
939982
}
940983

941984
/// A struct to encapsulate property and method type information for a group of related objects.

Sources/Fuzzilli/Lifting/JavaScriptLifter.swift

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class JavaScriptLifter: Lifter {
3434
let version: ECMAScriptVersion
3535

3636
/// This environment is used if we need to re-type a program before we compile Wasm code.
37-
private var environment: JavaScriptEnvironment?
37+
private var environment: JavaScriptEnvironment
3838

3939
/// Counter to assist the lifter in detecting nested CodeStrings
4040
private var codeStringNestingLevel = 0
@@ -78,7 +78,7 @@ public class JavaScriptLifter: Lifter {
7878
public init(prefix: String = "",
7979
suffix: String = "",
8080
ecmaVersion: ECMAScriptVersion,
81-
environment: JavaScriptEnvironment? = nil,
81+
environment: JavaScriptEnvironment,
8282
alwaysEmitVariables: Bool = false) {
8383
self.prefix = prefix
8484
self.suffix = suffix
@@ -131,7 +131,7 @@ public class JavaScriptLifter: Lifter {
131131

132132
if needToSupportWasm {
133133
// If we need to support Wasm we need to type all instructions outside of Wasm such that the WasmLifter can access extra type information during lifting.
134-
typer = JSTyper(for: environment!)
134+
typer = JSTyper(for: environment)
135135
}
136136

137137
var w = JavaScriptWriter(analyzer: analyzer, version: version, stripComments: !options.contains(.includeComments), includeLineNumbers: options.contains(.includeLineNumbers), alwaysEmitVariables: alwaysEmitVariables)
@@ -409,7 +409,7 @@ public class JavaScriptLifter: Lifter {
409409
case .beginObjectLiteralMethod(let op):
410410
let vars = w.declareAll(instr.innerOutputs.dropFirst(), usePrefix: "a")
411411
let PARAMS = liftParameters(op.parameters, as: vars)
412-
let METHOD = op.methodName
412+
let METHOD = quoteMethodDefinitionIfNeeded(op.methodName)
413413
currentObjectLiteral.beginMethod("\(METHOD)(\(PARAMS)) {", &w)
414414
bindVariableToThis(instr.innerOutput(0))
415415

@@ -526,7 +526,7 @@ public class JavaScriptLifter: Lifter {
526526
case .beginClassInstanceMethod(let op):
527527
let vars = w.declareAll(instr.innerOutputs.dropFirst(), usePrefix: "a")
528528
let PARAMS = liftParameters(op.parameters, as: vars)
529-
let METHOD = op.methodName
529+
let METHOD = quoteMethodDefinitionIfNeeded(op.methodName)
530530
w.emit("\(METHOD)(\(PARAMS)) {")
531531
w.enterNewBlock()
532532
bindVariableToThis(instr.innerOutput(0))
@@ -596,7 +596,7 @@ public class JavaScriptLifter: Lifter {
596596
case .beginClassStaticMethod(let op):
597597
let vars = w.declareAll(instr.innerOutputs.dropFirst(), usePrefix: "a")
598598
let PARAMS = liftParameters(op.parameters, as: vars)
599-
let METHOD = op.methodName
599+
let METHOD = quoteMethodDefinitionIfNeeded(op.methodName)
600600
w.emit("static \(METHOD)(\(PARAMS)) {")
601601
w.enterNewBlock()
602602
bindVariableToThis(instr.innerOutput(0))
@@ -981,14 +981,14 @@ public class JavaScriptLifter: Lifter {
981981

982982
case .callMethod(let op):
983983
let obj = input(0)
984-
let method = MemberExpression.new() + obj + "." + op.methodName
984+
let method = MemberExpression.new() + obj + (liftMemberAccess(op.methodName))
985985
let args = inputs.dropFirst()
986986
let expr = CallExpression.new() + method + "(" + liftCallArguments(args) + ")"
987987
w.assign(expr, to: instr.output)
988988

989989
case .callMethodWithSpread(let op):
990990
let obj = input(0)
991-
let method = MemberExpression.new() + obj + "." + op.methodName
991+
let method = MemberExpression.new() + obj + (liftMemberAccess(op.methodName))
992992
let args = inputs.dropFirst()
993993
let expr = CallExpression.new() + method + "(" + liftCallArguments(args, spreading: op.spreads) + ")"
994994
w.assign(expr, to: instr.output)
@@ -1152,7 +1152,8 @@ public class JavaScriptLifter: Lifter {
11521152
w.emit("\(EXPR);")
11531153

11541154
case .callSuperMethod(let op):
1155-
let expr = CallExpression.new() + "super.\(op.methodName)(" + liftCallArguments(inputs) + ")"
1155+
let method = MemberExpression.new() + "super" + liftMemberAccess(op.methodName)
1156+
let expr = CallExpression.new() + method + "(" + liftCallArguments(inputs) + ")"
11561157
w.assign(expr, to: instr.output)
11571158

11581159
case .getPrivateProperty(let op):
@@ -1511,9 +1512,9 @@ public class JavaScriptLifter: Lifter {
15111512

15121513
case .bindMethod(let op):
15131514
let V = w.declare(instr.output)
1514-
let OBJECT = input(0)
15151515
let LET = w.varKeyword
1516-
w.emit("\(LET) \(V) = Function.prototype.call.bind(\(OBJECT).\(op.methodName));")
1516+
let method = MemberExpression.new() + input(0) + liftMemberAccess(op.methodName)
1517+
w.emit("\(LET) \(V) = Function.prototype.call.bind(\(method));")
15171518

15181519
case .bindFunction(_):
15191520
let V = w.declare(instr.output)
@@ -1875,6 +1876,21 @@ public class JavaScriptLifter: Lifter {
18751876
}
18761877
}
18771878

1879+
func quoteMethodDefinitionIfNeeded(_ name: String) -> String {
1880+
if environment.isValidNameForMethodDefinition(name) {
1881+
return name
1882+
}
1883+
return "\"\(name)\""
1884+
}
1885+
1886+
private func liftMemberAccess(_ name: String) -> String {
1887+
if environment.isValidDotNotationName(name){
1888+
return "." + name
1889+
}
1890+
let safeName = environment.isValidPropertyIndex(name) ? name : "\"\(name)\""
1891+
return "[" + safeName + "]"
1892+
}
1893+
18781894
private func liftParameters(_ parameters: Parameters, as variables: [String]) -> String {
18791895
assert(parameters.count == variables.count)
18801896
var paramList = [String]()

Tests/FuzzilliTests/CompilerTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class CompilerTests: XCTestCase {
3939

4040
let compiler = JavaScriptCompiler()
4141

42-
let lifter = JavaScriptLifter(ecmaVersion: .es6)
42+
let lifter = JavaScriptLifter(ecmaVersion: .es6, environment: JavaScriptEnvironment())
4343

4444
for testcasePath in enumerateAllTestcases() {
4545
let testName = URL(fileURLWithPath: testcasePath).lastPathComponent

Tests/FuzzilliTests/CompilerTests/computed_and_indexed_properties.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ console.log("Computed object method");
1515
console.log(obj.theAnswerIs());
1616
})();
1717

18+
console.log("Indexed object method");
19+
(() => {
20+
const obj = { 67() { return 42; } };
21+
console.log(obj[67]());
22+
})();
23+
24+
console.log("String literal object method");
25+
(() => {
26+
const obj = { "?"() { return 42; } };
27+
console.log(obj["?"]());
28+
})();
29+
1830
console.log("Computed class property (field)");
1931
(() => {
2032
function classify (name) {
@@ -136,7 +148,6 @@ console.log("Indexed static class property (field)");
136148
console.log(C[42]);
137149
})();
138150

139-
/*
140151
console.log("Indexed class property (method)");
141152
(() => {
142153
class C {
@@ -148,9 +159,7 @@ console.log("Indexed class property (method)");
148159
const c = new C();
149160
console.log(c[42]());
150161
})();
151-
*/
152162

153-
/*
154163
console.log("Indexed static class property (method)");
155164
(() => {
156165
class C {
@@ -161,7 +170,6 @@ console.log("Indexed static class property (method)");
161170
}
162171
console.log(C[42]());
163172
})();
164-
*/
165173

166174
/*
167175
console.log("Indexed class property (getter/setter)");
@@ -319,24 +327,24 @@ console.log("String-literal static class property (field)");
319327
console.log("String-literal class property (method)");
320328
(() => {
321329
class C {
322-
"theAnswerIs"() {
330+
"theAnswerIs?"() {
323331
console.log("Heavy calculations");
324332
return 42;
325333
}
326334
}
327335
const c = new C();
328-
console.log(c.theAnswerIs());
336+
console.log(c["theAnswerIs?"]());
329337
})();
330338

331339
console.log("String-literal static class property (method)");
332340
(() => {
333341
class C {
334-
static "theAnswerIs"() {
342+
static "theAnswerIs?"() {
335343
console.log("Heavy calculations");
336344
return 42;
337345
}
338346
}
339-
console.log(C.theAnswerIs());
347+
console.log(C["theAnswerIs?"]());
340348
})();
341349

342350
/*

0 commit comments

Comments
 (0)