Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
23d8417
[temporal] Fill in timeZoneLike overloads
Manishearth Sep 16, 2025
52de2a3
Remove outdated comment about purity of Wasm tags
Liedtke Sep 17, 2025
00e72ab
Add support for named strings, use for timezones
Manishearth Sep 16, 2025
c211b5b
[wasm] Add signature type definition to wasm type groups
Liedtke Sep 16, 2025
9a11fad
Extend named string support to enums
Manishearth Sep 18, 2025
fbd7eef
[wasm] Implement self and forward references inside signatures
Liedtke Sep 18, 2025
bcbb448
Implement a new CodeGeneration approach.
carl-smith Sep 22, 2025
486960a
Remove .with and .wasmBlock context.
carl-smith Sep 22, 2025
67b1b43
Register Enumerations defined in Profiles.
carl-smith Sep 22, 2025
af50fa4
Fix compile error in release build.
carl-smith Sep 22, 2025
c886251
Add explicit resource management to the fuzzIL compiler
mi-ac Sep 23, 2025
3df2feb
[Turbolev] Adjust fuzzing flags probabilities
Sep 23, 2025
6d4173a
Have the mutator produce valid values half the time when mutating nam…
Manishearth Sep 19, 2025
39b95c3
[intl] Add fuzz definitions for Intl.DateTimeFormat
Manishearth Sep 19, 2025
177973b
Add --handle-weak-ref-weakly-in-minor-gc to Fuzzilli
omerktz Sep 24, 2025
3673577
[intl] Add type definitions for Intl.Collator
Manishearth Sep 23, 2025
245cc71
[intl] Add type definitions for Intl.ListFormat
Manishearth Sep 24, 2025
f64007b
[intl] Add type definitions for Intl.NumberFormat
Manishearth Sep 24, 2025
04f20e0
Reduce MinorMS probability in Fuzzilli
omerktz Sep 26, 2025
c40f869
[intl] Add type definitions for Intl.PluralRules
Manishearth Sep 24, 2025
684538e
[intl] Add type definitions for Intl.RelativeTimeFormat
Manishearth Sep 24, 2025
88a84c7
[intl] Add type definitions for Intl.Segmenter
Manishearth Sep 24, 2025
3848643
[intl] Fill in missing Intl methods
Manishearth Sep 24, 2025
80ad045
Simplify constrained string generation in code generators
Liedtke Sep 12, 2025
ac0aa7c
Revert "Simplify constrained string generation in code generators"
Liedtke Oct 1, 2025
76e3cd6
Update README for JSC (#529)
sosukesuzuki Oct 1, 2025
4359350
[heap]: Add PretenureAllocationSiteGenerator
Oct 1, 2025
adfa084
Support computed class methods
mi-ac Sep 30, 2025
3946513
Lower the stack-size in the V8 profile in 10% of the cases
mi-ac Oct 2, 2025
12507b3
Make computed class method immutable
mi-ac Oct 2, 2025
3e9962b
OperationMutator: Report the misconfigured opcode on crash
Liedtke Oct 2, 2025
5827a55
[js] Test that all global builtins are registered
Liedtke Oct 1, 2025
ba9eb8f
Upgrade required macOS version to 13.
danylo1999 Oct 2, 2025
54513de
Merge remote-tracking branch 'upstream/main'
Aeshus Oct 5, 2025
2a585ad
Fix missing aditionalEnumerations field
Aeshus Oct 5, 2025
8d75c17
Test possible fix
Aeshus Oct 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 25-nightly
- uses: swift-actions/setup-swift@v2

# Is it failing to run tests b/c we're overriding
# what swift binary to use?
- name: Setup Swift for Ubuntu
if: runner.os == 'Linux'
uses: swift-actions/setup-swift@v2
with:
swift-version: "6.0.3"
- name: Swift Version
run: swift --version

- uses: actions/checkout@v2

- name: Build
run: swift build -v

- name: Run tests
run: swift test -v
15 changes: 10 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.7
//
// Copyright 2019 Google LLC
//
Expand All @@ -19,7 +19,7 @@ import PackageDescription
let package = Package(
name: "Fuzzilli",
platforms: [
.macOS(.v11),
.macOS(.v13),
],
products: [
.library(name: "Fuzzilli",targets: ["Fuzzilli"]),
Expand All @@ -28,6 +28,10 @@ let package = Package(
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"),
.package(url: "https://github.com/swift-server/RediStack.git", from: "1.4.1"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
.package(
url: "https://github.com/apple/swift-collections.git",
.upToNextMinor(from: "1.2.0")
),
],
targets: [
.target(name: "libsocket",
Expand All @@ -46,6 +50,7 @@ let package = Package(
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
.product(name: "NIO", package: "swift-nio"),
.product(name: "RediStack", package: "RediStack"),
.product(name: "Collections", package: "swift-collections"),
"libsocket",
"libreprl",
"libcoverage"],
Expand All @@ -60,13 +65,13 @@ let package = Package(
.copy("Protobuf/ast.proto"),
.copy("Compiler/Parser")]),

.target(name: "REPRLRun",
.executableTarget(name: "REPRLRun",
dependencies: ["libreprl"]),

.target(name: "FuzzilliCli",
.executableTarget(name: "FuzzilliCli",
dependencies: ["Fuzzilli"]),

.target(name: "FuzzILTool",
.executableTarget(name: "FuzzILTool",
dependencies: ["Fuzzilli"]),

.testTarget(name: "FuzzilliTests",
Expand Down
222 changes: 222 additions & 0 deletions Sources/Fuzzilli/Base/ContextGraph.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Collections

public class ContextGraph {
// This is an edge, it holds all Generators that provide the `to` context at some point.
// Another invariant is that each Generator will keep the original context, i.e. it will return to the `from` conetxt.
struct EdgeKey: Hashable {
let from: Context
let to: Context

public init(from: Context, to: Context) {
self.from = from
self.to = to
}
}

// This struct describes the value of an edge in this ContextGraph.
// It holds all `CodeGenerator`s that go from one context to another in a direct transition.
public struct GeneratorEdge {
var generators: [CodeGenerator] = []

// Adds a generator to this Edge.
public mutating func addGenerator(_ generator: CodeGenerator) {
generators.append(generator)
}
}

// This is a Path that goes from one Context to another via (usually more than one) `GeneratorEdge`. It is a full path in the Graph.
// Every Edge in a path may be provided by various CodeGenerators.
public struct Path {
let edges: [GeneratorEdge]

// For each edge, pick a random Generator that provides that edge.
public func randomConcretePath() -> [CodeGenerator] {
edges.map { edge in
chooseUniform(from: edge.generators)
}
}
}

// This is the Graph, each pair of from and to, maps to a `GeneratorEdge`.
var edges: [EdgeKey: GeneratorEdge] = [:]

public init(for generators: WeightedList<CodeGenerator>, withLogger logger: Logger) {
// Technically we don't need any generator to emit the .javascript context, as this is provided by the toplevel.
var providedContexts = Set<Context>([.javascript])
var requiredContexts = Set<Context>()

for generator in generators {
generator.providedContexts.forEach { ctx in
providedContexts.insert(ctx)
}

requiredContexts.insert(generator.requiredContext)
}

// Check that every part that provides something is used by the next part of the Generator. This is a simple consistency check.
for generator in generators where generator.parts.count > 1 {
var currentContext = Context(generator.parts[0].providedContext)

for i in 1..<generator.parts.count {
let stub = generator.parts[i]

guard stub.requiredContext.matches(currentContext) ||
(stub.requiredContext.isJavascript && currentContext.isEmpty) ||
// If the requiredContext is more than two, we should never provide a context.
// See `CodeGenerator` for details.
(!stub.requiredContext.isSingle && currentContext.isEmpty) else {
fatalError("Inconsistent requires/provides Contexts for \(generator.name)")
}

currentContext = Context(stub.providedContext)
}
}

for generator in generators {
// Now check which generators don't have providers
if !providedContexts.contains(generator.requiredContext) {
logger.warning("Generator \(generator.name) cannot be run as it doesn't have a Generator that can provide this context.")

}

// All provided contexts must be required by some generator.
if !generator.providedContexts.allSatisfy({
requiredContexts.contains($0)
}) {
logger.warning("Generator \(generator.name) provides a context that is never required by another generator \(generator.providedContexts)")
}
}

// One can still try to build in a context that doesn't have generators, this will be caught in the build function, if we fail to find any suitable generator.
// Otherwise we could assert here that the sets are equal.
// One example is a GeneratorStub that opens a context, then calls build manually without it being split into two stubs where we have a yield point to assemble a synthetic generator.
self.edges = [:]

for generator in generators {
for providableContext in generator.providedContexts {
let edge = EdgeKey(from: generator.requiredContext, to: providableContext)
self.edges[edge, default: GeneratorEdge()].addGenerator(generator)
}
}
}

/// Gets all possible paths from the `from` Context to the `to` Context.
/// TODO: Do this initially and cache all results?
func getAllPaths(from src: Context, to dst: Context) -> [[EdgeKey]] {
// Do simple BFS to find all possible paths.
var queue: Deque<[Context]> = [[src]]
var paths: [[Context]] = []
var seenNodes = Set<Context>([src])

while !queue.isEmpty {
// use popFirst here from the deque.
let currentPath = queue.popFirst()!

let currentNode = currentPath.last!

if currentNode == dst {
paths.append(currentPath)
}

// Get all possible edges from here on and push all of those to the queue.
for edge in self.edges where edge.key.from == currentNode && !seenNodes.contains(edge.key.to) {
// Prevent cycles, we don't care about complicated paths, but rather simple direct paths.
seenNodes.insert(edge.key.to)
queue.append(currentPath + [edge.key.to])
}
}

if paths.isEmpty {
return []
}

// Reduce this to Edges structs that we can easily look up.
var edgePaths: [[EdgeKey]] = []
for path in paths {
var edgePath: [EdgeKey] = []
for i in 0..<(path.count - 1) {
let edge = EdgeKey(from: path[i], to: path[i+1])
edgePath.append(edge)
}
edgePaths.append(edgePath)
}

return edgePaths
}

func getGenerators(from src: Context, to dst: Context) -> GeneratorEdge? {
self.edges[EdgeKey(from: src, to: dst)]
}

// TODO(cffsmith) implement this to filter for generators that are actually reachable, we can use this to avoid picking a .javascript generator when we're in .wasmFunction context for example.
// This is needed since we cannot close Contexts or go back in time yet. This essentially calculates all reachable destinations from `src`.
// The caller can then use `fuzzer.codeGenerators.filter { $0.requiredContext.contains(<any reachable>) }` and pick a random one from that subset.
func getReachableContexts(from src: Context) -> [Context] {
// Do simple BFS to find all possible paths.
var queue: Deque<[Context]> = [[src]]
var paths: [[Context]] = []
var seenNodes = Set<Context>([src])

while !queue.isEmpty {
let currentPath = queue.popFirst()!

let currentNode = currentPath.last!

var stillExploring = false

// Get all possible edges from here on and push all of those to the queue.
for edge in self.edges where edge.key.from == currentNode && !seenNodes.contains(edge.key.to) {
// Prevent cycles, we don't care about complicated paths, but rather simple direct paths.
stillExploring = true
seenNodes.insert(edge.key.to)
queue.append(currentPath + [edge.key.to])
}

// If we haven't added another node, it means we have found an "end".
if !stillExploring {
paths.append(currentPath)
}
}

if paths.isEmpty {
return []
}

// Map to all reachable contexts. so all that are on the path.
let contextSet = paths.reduce(Set()) { res, path in
res.union(path)
}
return Array(contextSet)
}

// The return value is a list of possible Paths.
// A Path is a possible way from one context to another.
public func getCodeGeneratorPaths(from src: Context, to dst: Context) -> [Path]? {

let paths = getAllPaths(from: src, to: dst)

if paths.isEmpty {
return nil
}

return paths.map { edges in
Path(edges: edges.map { edge in
self.edges[edge]!
})
}
}
}
Loading