├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── Fixtures
├── .gitignore
├── Entrypoints
│ ├── I64ImportTransformerTests.swift
│ └── StackOverflowSanitizerTests.swift
├── Makefile
├── build
│ └── .gitkeep
├── index.js
├── package-lock.json
├── package.json
├── stack_sanitizer_support.c
└── webpack.config.js
├── IntegrationTests
├── .gitignore
├── .swiftpm
│ └── xcode
│ │ └── package.xcworkspace
│ │ └── contents.xcworkspacedata
├── Package.resolved
├── Package.swift
├── README.md
└── Tests
│ └── IntegrationTests
│ ├── I64ImportTransformerTests.swift
│ ├── StackOverflowSanitizerTests.swift
│ └── misc.swift
├── LICENSE
├── Makefile
├── Package.swift
├── README.md
├── Sources
├── WasmTransformer
│ ├── BinaryFormat.swift
│ ├── ByteEncodable.swift
│ ├── InputByteStream.swift
│ ├── LEB128.swift
│ ├── OutputWriter.swift
│ ├── Readers
│ │ ├── CodeSectionReader.swift
│ │ ├── ElementSectionReader.swift
│ │ ├── FunctionSectionReader.swift
│ │ ├── ImportSectionReader.swift
│ │ ├── ModuleReader.swift
│ │ ├── TypeSectionReader.swift
│ │ └── VectorSectionReader.swift
│ ├── Sections.swift
│ ├── Trampoline.swift
│ ├── Transformers
│ │ ├── CustomSectionStripper.swift
│ │ ├── I64ImportTransformer.swift
│ │ ├── SizeProfiler.swift
│ │ ├── StackOverflowSanitizer+Fixtures.swift
│ │ └── StackOverflowSanitizer.swift
│ └── WasmTransformer.swift
└── wasm-trans
│ └── main.swift
├── Tests
├── LinuxMain.swift
└── WasmTransformerTests
│ ├── CustomSectionStripperTests.swift
│ ├── I64ImportTransformerTests.swift
│ ├── InputByteStreamTests.swift
│ ├── IntegrationTests.swift
│ ├── SectionsInfoTests.swift
│ ├── StackOverflowSanitizerTests.swift
│ └── misc.swift
└── Tools
└── GenerateStackOverflowSanitizerSupport.swift
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 | on: [push]
3 |
4 | jobs:
5 | build-fixtures:
6 | runs-on: macos-latest
7 | strategy:
8 | matrix:
9 | toolchain-version:
10 | - swift-wasm-5.3.0-RELEASE
11 | - swift-wasm-5.4.0-RELEASE
12 | - swift-wasm-5.5.0-RELEASE
13 | - swift-wasm-5.6.0-RELEASE
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 | - name: Install SwiftWasm toolchain
18 | run: |
19 | VERSION=${{ matrix.toolchain-version }}
20 | TOOLCHAIN_URL="https://github.com/swiftwasm/swift/releases/download/$VERSION/$VERSION-macos_x86_64.pkg"
21 | wget $TOOLCHAIN_URL
22 | installer -target CurrentUserHomeDirectory -pkg $VERSION-macos_x86_64.pkg
23 | echo "SWIFT_TOOLCHAIN=$HOME/Library/Developer/Toolchains/$VERSION.xctoolchain/usr" >> $GITHUB_ENV
24 | - name: Setup fixtures
25 | run: |
26 | npm install
27 | npm run build
28 | make all
29 | working-directory: Fixtures
30 | - uses: actions/upload-artifact@v2
31 | with:
32 | name: test-fixtures-${{ matrix.toolchain-version }}
33 | path: Fixtures/build
34 |
35 | ubuntu-unit-tests:
36 | runs-on: ubuntu-latest
37 | needs: build-fixtures
38 | strategy:
39 | matrix:
40 | toolchain-version:
41 | - swift-wasm-5.3.0-RELEASE
42 | - swift-wasm-5.4.0-RELEASE
43 | - swift-wasm-5.5.0-RELEASE
44 | - swift-wasm-5.6.0-RELEASE
45 | steps:
46 | - uses: actions/checkout@v2
47 | - uses: actions/download-artifact@v2
48 | with:
49 | name: test-fixtures-${{ matrix.toolchain-version }}
50 | path: Fixtures/build
51 | - name: Install dependencies
52 | run: |
53 | curl -L -o wabt.tar.gz https://github.com/WebAssembly/wabt/releases/download/1.0.19/wabt-1.0.19-ubuntu.tar.gz
54 | tar xzvf wabt.tar.gz
55 | sudo cp wabt-*/bin/* /usr/local/bin
56 | - name: Run unit tests
57 | run: swift test --enable-test-discovery
58 |
59 | macos-unit-tests:
60 | runs-on: macos-12
61 | needs: build-fixtures
62 | strategy:
63 | matrix:
64 | xcode: ["Xcode_14.0", "Xcode_13.4.1", "Xcode_13.2.1"]
65 | toolchain-version:
66 | - swift-wasm-5.3.0-RELEASE
67 | - swift-wasm-5.4.0-RELEASE
68 | - swift-wasm-5.5.0-RELEASE
69 | - swift-wasm-5.6.0-RELEASE
70 |
71 | steps:
72 | - uses: actions/checkout@v2
73 | - uses: actions/download-artifact@v2
74 | with:
75 | name: test-fixtures-${{ matrix.toolchain-version }}
76 | path: Fixtures/build
77 | - name: Select Xcode toolchain
78 | run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer
79 | - name: Install dependencies
80 | run: brew install wabt
81 | - name: Run unit tests
82 | run: swift test
83 |
84 | integration-tests:
85 | runs-on: macos-12
86 | needs: build-fixtures
87 | strategy:
88 | fail-fast: false
89 | matrix:
90 | toolchain-version:
91 | - swift-wasm-5.3.0-RELEASE
92 | - swift-wasm-5.4.0-RELEASE
93 | - swift-wasm-5.5.0-RELEASE
94 | - swift-wasm-5.6.0-RELEASE
95 | steps:
96 | - uses: actions/checkout@v2
97 | - uses: actions/download-artifact@v2
98 | with:
99 | name: test-fixtures-${{ matrix.toolchain-version }}
100 | path: Fixtures/build
101 | - name: Select Xcode toolchain
102 | run: sudo xcode-select -s /Applications/Xcode_13.2.1.app/Contents/Developer/
103 | - name: Install dependencies
104 | run: |
105 | sudo pip3 install selenium
106 | - run: swift test
107 | working-directory: IntegrationTests
108 | env:
109 | PYTHON_VERSION: 3
110 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | Package.resolved
7 | .vscode
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Fixtures/.gitignore:
--------------------------------------------------------------------------------
1 | /build/*
2 | !.gitkeep
3 | /node_modules
4 |
--------------------------------------------------------------------------------
/Fixtures/Entrypoints/I64ImportTransformerTests.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | let date = Date()
4 | print("timeIntervalSince1970 is \(date.timeIntervalSince1970)")
5 |
--------------------------------------------------------------------------------
/Fixtures/Entrypoints/StackOverflowSanitizerTests.swift:
--------------------------------------------------------------------------------
1 | struct LargeBox0 {
2 | let value0: UInt64 = 0xdeadbeef
3 | let value1: UInt64 = 0xdeadbeef
4 | let value2: UInt64 = 0xdeadbeef
5 | let value3: UInt64 = 0xdeadbeef
6 | let value4: UInt64 = 0xdeadbeef
7 | let value5: UInt64 = 0xdeadbeef
8 | let value6: UInt64 = 0xdeadbeef
9 | let value7: UInt64 = 0xdeadbeef
10 | }
11 |
12 | struct LargeBox {
13 | let value0 = LargeBox0()
14 | let value1 = LargeBox0()
15 | let value2 = LargeBox0()
16 | let value3 = LargeBox0()
17 | let value4 = LargeBox0()
18 | let value5 = LargeBox0()
19 | let value6 = LargeBox0()
20 | let value7 = LargeBox0()
21 | }
22 |
23 | func causeStackOverflow(box: LargeBox) {
24 | let box = LargeBox()
25 | causeStackOverflow(box: box)
26 | }
27 |
28 | causeStackOverflow(box: LargeBox())
29 |
--------------------------------------------------------------------------------
/Fixtures/Makefile:
--------------------------------------------------------------------------------
1 | ifndef SWIFT_TOOLCHAIN
2 | $(error Please set SWIFT_TOOLCHAIN env variable)
3 | endif
4 |
5 | SWIFTC = ${SWIFT_TOOLCHAIN}/bin/swiftc
6 | CLANG = ${SWIFT_TOOLCHAIN}/bin/clang
7 | LLVM_AR = ${SWIFT_TOOLCHAIN}/bin/llvm-ar
8 |
9 | .PHONY: all
10 | all: build/I64ImportTransformerTests.wasm build/StackOverflowSanitizerTests.wasm
11 |
12 | build/%.wasm: Entrypoints/%.swift
13 | $(SWIFTC) -target wasm32-unknown-wasi \
14 | -sdk "${SWIFT_TOOLCHAIN}/share/wasi-sysroot" \
15 | -I "${SWIFT_TOOLCHAIN}/lib/swift/wasi/wasm32" \
16 | -lFoundation \
17 | -lCoreFoundation \
18 | -lBlocksRuntime \
19 | -licui18n \
20 | -luuid \
21 | $< -o $@
22 |
23 | build/StackOverflowSanitizerTests.wasm: Entrypoints/StackOverflowSanitizerTests.swift build/stack_sanitizer_support.o
24 | $(SWIFTC) -target wasm32-unknown-wasi \
25 | -sdk "${SWIFT_TOOLCHAIN}/share/wasi-sysroot" \
26 | -I "${SWIFT_TOOLCHAIN}/lib/swift/wasi/wasm32" \
27 | -lFoundation \
28 | -lCoreFoundation \
29 | -lBlocksRuntime \
30 | -licui18n \
31 | -luuid \
32 | build/stack_sanitizer_support.o \
33 | $< -o $@
34 |
35 | build/stack_sanitizer_support.o: stack_sanitizer_support.c
36 | $(CLANG) -c -target wasm32-unknown-wasi $< \
37 | -o build/stack_sanitizer_support.o
38 |
--------------------------------------------------------------------------------
/Fixtures/build/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/swiftwasm/WasmTransformer/d04b31f61b6f528a9a96ebfe4fa4275e333eba82/Fixtures/build/.gitkeep
--------------------------------------------------------------------------------
/Fixtures/index.js:
--------------------------------------------------------------------------------
1 | import { WASI } from "@wasmer/wasi";
2 | import { WasmFs } from "@wasmer/wasmfs";
3 |
4 | const wrapWASI = (wasiObject) => {
5 | for (const key in wasiObject.wasiImport) {
6 | const func = wasiObject.wasiImport[key]
7 | wasiObject.wasiImport[key] = function() {
8 | console.log(`[tracing] WASI.${key}`);
9 | return Reflect.apply(func, undefined, arguments);
10 | }
11 | }
12 | // PATCH: @wasmer-js/wasi@0.x forgets to call `refreshMemory` in `clock_res_get`,
13 | // which writes its result to memory view. Without the refresh the memory view,
14 | // it accesses a detached array buffer if the memory is grown by malloc.
15 | // But they wasmer team discarded the 0.x codebase at all and replaced it with
16 | // a new implementation written in Rust. The new version 1.x is really unstable
17 | // and not production-ready as far as katei investigated in Apr 2022.
18 | // So override the broken implementation of `clock_res_get` here instead of
19 | // fixing the wasi polyfill.
20 | // Reference: https://github.com/wasmerio/wasmer-js/blob/55fa8c17c56348c312a8bd23c69054b1aa633891/packages/wasi/src/index.ts#L557
21 | const original_clock_res_get = wasiObject.wasiImport["clock_res_get"];
22 | wasiObject.wasiImport["clock_res_get"] = (clockId, resolution) => {
23 | wasiObject.refreshMemory();
24 | return original_clock_res_get(clockId, resolution)
25 | };
26 | return wasiObject.wasiImport;
27 | }
28 |
29 | window.I64ImportTransformerTests = async (bytes) => {
30 | const wasmFs = new WasmFs();
31 | const wasi = new WASI({
32 | args: [], env: {},
33 | bindings: {
34 | ...WASI.defaultBindings,
35 | fs: wasmFs.fs,
36 | }
37 | });
38 |
39 | const importObject = {
40 | wasi_snapshot_preview1: wrapWASI(wasi),
41 | };
42 |
43 | const module = await WebAssembly.compile(bytes);
44 | const instance = await WebAssembly.instantiate(module, importObject);
45 | wasi.start(instance);
46 | };
47 |
48 | window.StackOverflowSanitizerTests = async (bytes) => {
49 | const wasmFs = new WasmFs();
50 | const wasi = new WASI({
51 | args: [], env: {},
52 | bindings: {
53 | ...WASI.defaultBindings,
54 | fs: wasmFs.fs,
55 | }
56 | });
57 |
58 | const importObject = {
59 | wasi_snapshot_preview1: wrapWASI(wasi),
60 | __stack_sanitizer: {
61 | report_stack_overflow: () => {
62 | throw new Error("CATCH_STACK_OVERFLOW");
63 | }
64 | }
65 | };
66 |
67 | const module = await WebAssembly.compile(bytes);
68 | const instance = await WebAssembly.instantiate(module, importObject);
69 | wasi.start(instance);
70 | }
71 |
--------------------------------------------------------------------------------
/Fixtures/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fixtures",
3 | "main": "index.js",
4 | "dependencies": {
5 | "@wasmer/wasi": "^0.12.0",
6 | "@wasmer/wasmfs": "^0.12.0"
7 | },
8 | "scripts": {
9 | "build": "webpack --config webpack.config.js"
10 | },
11 | "devDependencies": {
12 | "webpack": "^4.42.0",
13 | "webpack-cli": "^3.3.11"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Fixtures/stack_sanitizer_support.c:
--------------------------------------------------------------------------------
1 | __attribute__((__import_module__("__stack_sanitizer"),
2 | __import_name__("report_stack_overflow")))
3 | void __stack_sanitizer_report_stack_overflow(void);
4 |
5 | __attribute__((visibility("hidden")))
6 | void __dummy() {
7 | __stack_sanitizer_report_stack_overflow();
8 | }
9 |
--------------------------------------------------------------------------------
/Fixtures/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: "development",
3 | entry: './index.js',
4 | output: {
5 | filename: 'bundle.js',
6 | path: __dirname + "/build",
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/IntegrationTests/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/IntegrationTests/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/IntegrationTests/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "PythonKit",
6 | "repositoryURL": "https://github.com/pvieito/PythonKit.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "8de2a3f1f8c1388e9fca84f192f96821d9ccd43d",
10 | "version": null
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/IntegrationTests/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "IntegrationTests",
7 | dependencies: [
8 | .package(url: "https://github.com/pvieito/PythonKit.git", .revision("8de2a3f1f8c1388e9fca84f192f96821d9ccd43d")),
9 | .package(name: "WasmTransformer", path: "../"),
10 | ],
11 | targets: [
12 | .testTarget(
13 | name: "IntegrationTests",
14 | dependencies: ["WasmTransformer", "PythonKit"]),
15 | ]
16 | )
17 |
--------------------------------------------------------------------------------
/IntegrationTests/README.md:
--------------------------------------------------------------------------------
1 | # IntegrationTests
2 |
3 | A description of this package.
4 |
--------------------------------------------------------------------------------
/IntegrationTests/Tests/IntegrationTests/I64ImportTransformerTests.swift:
--------------------------------------------------------------------------------
1 | import WasmTransformer
2 | import Foundation
3 | import XCTest
4 | import PythonKit
5 |
6 | final class I64ImportTransformerTests: XCTestCase {
7 |
8 | let binaryPath = buildPath.appendingPathComponent("I64ImportTransformerTests.wasm")
9 |
10 | func lowerI64Imports(_ input: URL) throws -> URL {
11 | let transformer = I64ImportTransformer()
12 | var inputStream = try InputByteStream(from: binaryPath)
13 | var writer = InMemoryOutputWriter()
14 | try transformer.transform(&inputStream, writer: &writer)
15 |
16 | let (url, wasmFileHandle) = makeTemporaryFile(suffix: ".wasm")
17 | wasmFileHandle.write(Data(writer.bytes()))
18 | return url
19 | }
20 |
21 | func testIntegration() throws {
22 | let html = try createCheckHtml(embedding: lowerI64Imports(binaryPath))
23 | let (tmpHtmlURL, htmlFileHandle) = makeTemporaryFile(suffix: ".html")
24 | htmlFileHandle.write(html.data(using: .utf8)!)
25 |
26 |
27 | // Ensure that no exception happen
28 | let time = Python.import("time")
29 | let webdriver = Python.import("selenium.webdriver")
30 |
31 | let driver = webdriver.Safari()
32 | driver.set_page_load_timeout(120)
33 | driver.get(tmpHtmlURL.absoluteString)
34 | time.sleep(5)
35 | driver.execute_async_script("await window.I64ImportTransformerTests(wasmBytes);arguments[0]();")
36 | time.sleep(5)
37 | driver.quit()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/IntegrationTests/Tests/IntegrationTests/StackOverflowSanitizerTests.swift:
--------------------------------------------------------------------------------
1 | import WasmTransformer
2 | import Foundation
3 | import XCTest
4 | import PythonKit
5 |
6 | final class StackOverflowSanitizerTests: XCTestCase {
7 |
8 | let binaryPath = buildPath.appendingPathComponent("StackOverflowSanitizerTests.wasm")
9 |
10 | func instrumentStackSanitizer(_ input: URL) throws -> URL {
11 | let transformer = StackOverflowSanitizer()
12 | var inputStream = try InputByteStream(from: binaryPath)
13 | var writer = InMemoryOutputWriter()
14 | try transformer.transform(&inputStream, writer: &writer)
15 |
16 | let (url, wasmFileHandle) = makeTemporaryFile(suffix: ".wasm")
17 | wasmFileHandle.write(Data(writer.bytes()))
18 | return url
19 | }
20 |
21 | func testIntegration() throws {
22 | let wasm = try instrumentStackSanitizer(binaryPath)
23 | let html = try createCheckHtml(embedding: wasm)
24 | let (tmpHtmlURL, htmlFileHandle) = makeTemporaryFile(suffix: ".html")
25 | htmlFileHandle.write(html.data(using: .utf8)!)
26 |
27 |
28 | // Ensure that no exception happen
29 | let time = Python.import("time")
30 | let webdriver = Python.import("selenium.webdriver")
31 |
32 | let driver = webdriver.Safari()
33 | driver.set_page_load_timeout(120)
34 | driver.get(tmpHtmlURL.absoluteString)
35 | time.sleep(5)
36 | let entryScript = "await window.StackOverflowSanitizerTests(wasmBytes);arguments[0]();"
37 | XCTAssertThrowsError(
38 | try driver.execute_async_script.throwing.dynamicallyCall(withArguments: entryScript)
39 | ) { error in
40 | let description = String(describing: error)
41 | XCTAssertTrue(description.contains("CATCH_STACK_OVERFLOW"), description)
42 | }
43 | driver.quit()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/IntegrationTests/Tests/IntegrationTests/misc.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | let buildPath = URL(fileURLWithPath: #filePath)
4 | .deletingLastPathComponent()
5 | .deletingLastPathComponent()
6 | .deletingLastPathComponent()
7 | .deletingLastPathComponent()
8 | .appendingPathComponent("Fixtures/build")
9 |
10 | func makeTemporaryFile(suffix: String) -> (URL, FileHandle) {
11 | let tempdir = URL(fileURLWithPath: NSTemporaryDirectory())
12 | let templatePath = tempdir.appendingPathComponent("wasm-transformer.XXXXXX\(suffix)")
13 | var template = [UInt8](templatePath.path.utf8).map { Int8($0) } + [Int8(0)]
14 | let fd = mkstemps(&template, Int32(suffix.utf8.count))
15 | if fd == -1 {
16 | fatalError("Failed to create temp directory")
17 | }
18 | let url = URL(fileURLWithPath: String(cString: template))
19 | let handle = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
20 | return (url, handle)
21 | }
22 |
23 | let bundleJSPath = buildPath.appendingPathComponent("bundle.js")
24 | func createCheckHtml(embedding binaryPath: URL) throws -> String {
25 | let wasmBytes = try Data(contentsOf: binaryPath)
26 | return try """
27 |
37 | """
38 | }
39 |
40 | import WasmTransformer
41 |
42 | extension InputByteStream {
43 | init(from url: URL) throws {
44 | let bytes = try Array(Data(contentsOf: url))
45 | self.init(bytes: bytes)
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2020 WasmTransformer contributors
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | update-fixture: Sources/WasmTransformer/Transformers/StackOverflowSanitizer+Fixtures.swift
2 |
3 | Fixtures/build/stack_sanitizer_support.o:
4 | $(MAKE) -C Fixtures build/stack_sanitizer_support.o
5 | Sources/WasmTransformer/Transformers/StackOverflowSanitizer+Fixtures.swift: Fixtures/build/stack_sanitizer_support.o Tools/GenerateStackOverflowSanitizerSupport.swift
6 | swift ./Tools/GenerateStackOverflowSanitizerSupport.swift Fixtures/build/stack_sanitizer_support.o > Sources/WasmTransformer/Transformers/StackOverflowSanitizer+Fixtures.swift
7 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.4
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "WasmTransformer",
7 | products: [
8 | .library(
9 | name: "WasmTransformer",
10 | targets: ["WasmTransformer"]
11 | ),
12 | ],
13 | dependencies: [
14 | .package(url: "https://github.com/apple/swift-argument-parser", .exact("1.0.0")),
15 | ],
16 | targets: [
17 | .executableTarget(name: "wasm-trans", dependencies: [
18 | "WasmTransformer",
19 | .product(name: "ArgumentParser", package: "swift-argument-parser"),
20 | ]),
21 | .target(name: "WasmTransformer", dependencies: []),
22 | .testTarget(name: "WasmTransformerTests", dependencies: ["WasmTransformer"]),
23 | ]
24 | )
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WasmTransformer
2 |
3 | 
4 |
5 | A package which provides transformation operation for WebAssembly binary. Inspired by [Rust implementation](https://github.com/wasmerio/wasmer-js/tree/master/crates/wasm_transformer)
6 |
7 | ## Available transformations
8 |
9 | ### `lowerI64Imports`
10 |
11 |
12 | ```swift
13 | public func lowerI64Imports(_ input: [UInt8]) throws -> [UInt8]
14 | ```
15 |
16 | Inserts trampoline functions for imports that have i64 params or returns. This is useful for running Wasm modules in browsers that do not support JavaScript BigInt -> Wasm i64 integration. Especially in the case for i64 WASI Imports.
17 |
18 |
19 | ### `stripCustomSections`
20 |
21 | ```swift
22 | public func stripCustomSections(_ input: [UInt8]) throws -> [UInt8]
23 | ```
24 |
25 | Strip all custom sections from input WebAssembly binary.
26 |
27 |
28 | ## Testing
29 |
30 | 1. Set environment variable `SWIFT_TOOLCHAIN` to the path to your SwiftWasm toolchain.
31 | e.g. `$HOME/Library/Developer/Toolchains/swift-wasm-5.7.3-RELEASE.xctoolchain/usr`
32 | 2. Set up testing fixtures by: `(cd ./Fixtures/ && npm install && npm run build && make all)`
33 | 3. Run `swift test`
34 |
35 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/BinaryFormat.swift:
--------------------------------------------------------------------------------
1 | let magic: [UInt8] = [0x00, 0x61, 0x73, 0x6D]
2 | let version: [UInt8] = [0x01, 0x00, 0x00, 0x00]
3 |
4 | let LIMITS_HAS_MAX_FLAG: UInt8 = 0x1
5 | let LIMITS_IS_SHARED_FLAG: UInt8 = 0x2
6 |
7 | public enum SectionType: UInt8 {
8 | case custom = 0
9 | case type = 1
10 | case `import` = 2
11 | case function = 3
12 | case table = 4
13 | case memory = 5
14 | case global = 6
15 | case export = 7
16 | case start = 8
17 | case element = 9
18 | case code = 10
19 | case data = 11
20 | case dataCount = 12
21 | }
22 |
23 | public enum ValueType: UInt8, Equatable {
24 | case i32 = 0x7F
25 | case i64 = 0x7E
26 | case f32 = 0x7D
27 | case f64 = 0x7C
28 | }
29 |
30 | enum ExternalKind: UInt8, Equatable {
31 | case `func` = 0
32 | case table = 1
33 | case memory = 2
34 | case global = 3
35 | case except = 4
36 | }
37 |
38 | enum ConstOpcode: UInt8 {
39 | case i32Const = 0x41
40 | case i64Const = 0x42
41 | case f32Const = 0x43
42 | case f64Const = 0x44
43 | }
44 |
45 |
46 | enum BlockType {
47 | case empty
48 | }
49 |
50 | let END_INST_OPCODE: UInt8 = 0x0B
51 | enum Opcode: Equatable {
52 | case unreachable
53 | case end
54 | case localGet(UInt32)
55 | case localSet(UInt32)
56 | case globalSet(UInt32)
57 | case `if`(BlockType)
58 | case call(UInt32)
59 | case i32Const(Int32)
60 | case i32LtS
61 | case i32WrapI64
62 | case unknown([UInt8])
63 |
64 | func serialize() -> [UInt8] {
65 | switch self {
66 | case .unreachable: return [0x00]
67 | case .end: return [END_INST_OPCODE]
68 | case let .localGet(localIndex):
69 | return [0x20] + encodeULEB128(localIndex)
70 | case let .localSet(localIndex):
71 | return [0x21] + encodeULEB128(localIndex)
72 | case let .globalSet(globalIndex):
73 | return [0x24] + encodeULEB128(globalIndex)
74 | case .if(.empty):
75 | return [0x04, 0x40]
76 | case let .call(funcIndex):
77 | return [0x10] + encodeULEB128(funcIndex)
78 | case let .i32Const(value):
79 | return [0x41] + encodeSLEB128(value)
80 | case .i32LtS: return [0x48]
81 | case .i32WrapI64: return [0xA7]
82 | case let .unknown(bytes): return bytes
83 | }
84 | }
85 | }
86 |
87 | public struct FuncSignature {
88 | public let params: [ValueType]
89 | public let results: [ValueType]
90 | }
91 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/ByteEncodable.swift:
--------------------------------------------------------------------------------
1 | protocol ByteEncodable {
2 | func encode(to writer: inout Writer) throws
3 | }
4 |
5 | extension FuncSignature: ByteEncodable {
6 | func encode(to writer: inout Writer) throws {
7 | try writer.writeByte(0x60)
8 | try writer.writeResultTypes(params)
9 | try writer.writeResultTypes(results)
10 | }
11 | }
12 |
13 | extension Import: ByteEncodable {
14 | func encode(to writer: inout Writer) throws {
15 | try writer.writeString(module)
16 | try writer.writeString(field)
17 | switch descriptor {
18 | case .function(let sigIndex):
19 | try writer.writeByte(ExternalKind.func.rawValue)
20 | try writer.writeBytes(encodeULEB128(sigIndex))
21 | case .table(let rawBytes):
22 | try writer.writeByte(ExternalKind.table.rawValue)
23 | try writer.writeBytes(rawBytes)
24 | case .memory(let rawBytes):
25 | try writer.writeByte(ExternalKind.memory.rawValue)
26 | try writer.writeBytes(rawBytes)
27 | case .global(let rawBytes):
28 | try writer.writeByte(ExternalKind.global.rawValue)
29 | try writer.writeBytes(rawBytes)
30 | }
31 | }
32 | }
33 |
34 | extension SignatureIndex: ByteEncodable {
35 | func encode(to writer: inout Writer) throws {
36 | try writer.writeBytes(encodeULEB128(value))
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/InputByteStream.swift:
--------------------------------------------------------------------------------
1 | public struct InputByteStream {
2 | public private(set) var offset: Int
3 | public let bytes: ArraySlice
4 | public var isEOF: Bool {
5 | offset >= bytes.endIndex
6 | }
7 |
8 | public init(bytes: ArraySlice) {
9 | self.bytes = bytes
10 | self.offset = bytes.startIndex
11 | }
12 |
13 | public init(bytes: [UInt8]) {
14 | self.init(bytes: bytes[...])
15 | }
16 |
17 | mutating func readHeader() throws -> ArraySlice {
18 | let maybeMagic = read(4)
19 | guard maybeMagic.elementsEqual(magic) else {
20 | throw Error.badMagic(maybeMagic)
21 | }
22 | let version = read(4)
23 | return version
24 | }
25 |
26 | mutating func readSectionInfo() throws -> SectionInfo {
27 | let startOffset = offset
28 | let rawType = readUInt8()
29 | guard let type = SectionType(rawValue: rawType) else {
30 | throw Error.unexpectedSection(rawType)
31 | }
32 | let size = Int(readVarUInt32())
33 | let contentStart = offset
34 |
35 | return .init(
36 | startOffset: startOffset,
37 | contentStart: contentStart,
38 | type: type,
39 | size: size
40 | )
41 | }
42 |
43 | public mutating func seek(_ offset: Int) {
44 | self.offset = offset
45 | }
46 |
47 | public mutating func skip(_ length: Int) {
48 | offset += length
49 | }
50 |
51 | public mutating func read(_ length: Int) -> ArraySlice {
52 | let result = bytes[offset ..< offset + length]
53 | offset += length
54 | return result
55 | }
56 |
57 | mutating func readUInt8() -> UInt8 {
58 | let byte = read(1)
59 | return byte[byte.startIndex]
60 | }
61 |
62 | public mutating func readVarUInt32() -> UInt32 {
63 | let (value, advanced) = decodeULEB128(bytes[offset...], UInt32.self)
64 | offset += advanced
65 | return value
66 | }
67 |
68 | mutating func consumeULEB128(_: T.Type) where T: UnsignedInteger, T: FixedWidthInteger {
69 | let (_, advanced) = decodeULEB128(bytes[offset...], T.self)
70 | offset += advanced
71 | }
72 |
73 | enum Error: Swift.Error {
74 | case badMagic(ArraySlice)
75 | case invalidValueType(UInt8)
76 | case invalidExternalKind(UInt8)
77 | case unsupportedExternalKind(ExternalKind)
78 | case expectConstOpcode(UInt8)
79 | case expectI32Const(ConstOpcode)
80 | case unexpectedOpcode(UInt8)
81 | case unexpectedSection(UInt8)
82 | case expectEnd
83 | }
84 |
85 | /// https://webassembly.github.io/spec/core/binary/types.html#result-types
86 | mutating func readResultTypes() throws -> [ValueType] {
87 | let count = readVarUInt32()
88 | var resultTypes: [ValueType] = []
89 | for _ in 0 ..< count {
90 | let rawType = readUInt8()
91 | guard let type = ValueType(rawValue: rawType) else {
92 | throw Error.invalidValueType(rawType)
93 | }
94 | resultTypes.append(type)
95 | }
96 | return resultTypes
97 | }
98 |
99 | mutating func readExternalKind() throws -> ExternalKind {
100 | let rawKind = readUInt8()
101 | guard let kind = ExternalKind(rawValue: rawKind) else {
102 | throw Error.invalidExternalKind(rawKind)
103 | }
104 | return kind
105 | }
106 |
107 | typealias Consumer = (ArraySlice) throws -> Void
108 |
109 | mutating func consumeString(consumer: Consumer? = nil) rethrows {
110 | let start = offset
111 | let length = Int(readVarUInt32())
112 | offset += length
113 | try consumer?(bytes[start ..< offset])
114 | }
115 |
116 | /// https://webassembly.github.io/spec/core/binary/values.html#names
117 | mutating func readString() -> String {
118 | let length = Int(readVarUInt32())
119 | let strStart = offset
120 | offset += length
121 | return String(decoding: bytes[strStart ..< offset], as: UTF8.self)
122 | }
123 |
124 | mutating func readImportDescriptor() throws -> ImportDescriptor {
125 | let kind = try readExternalKind()
126 | switch kind {
127 | case .func: return .function(readVarUInt32())
128 | case .table: return .table(rawBytes: consumeTable())
129 | case .memory: return .memory(rawBytes: consumeMemory())
130 | case .global: return .global(rawBytes: consumeGlobalHeader())
131 | case .except: throw Error.unsupportedExternalKind(kind)
132 | }
133 | }
134 |
135 | /// https://webassembly.github.io/spec/core/binary/modules.html#import-section
136 | mutating func readImport() throws -> Import {
137 | let module = readString()
138 | let field = readString()
139 | let desc = try readImportDescriptor()
140 | return Import(module: module, field: field, descriptor: desc)
141 | }
142 |
143 | /// https://webassembly.github.io/spec/core/binary/types.html#value-types
144 | mutating func readFuncType() throws -> FuncSignature {
145 | let params = try readResultTypes()
146 | let results = try readResultTypes()
147 | return FuncSignature(
148 | params: params, results: results
149 | )
150 | }
151 | /// https://webassembly.github.io/spec/core/binary/types.html#table-types
152 | mutating func consumeTable() -> ArraySlice {
153 | let start = offset
154 | _ = readUInt8() // element type
155 | let hasMax = readUInt8() != 0
156 | _ = readVarUInt32() // initial
157 | if hasMax {
158 | _ = readVarUInt32() // max
159 | }
160 | return bytes[start ..< offset]
161 | }
162 |
163 | /// https://webassembly.github.io/spec/core/binary/types.html#memory-types
164 | mutating func consumeMemory() -> ArraySlice {
165 | let start = offset
166 | let flags = readUInt8()
167 | let hasMax = (flags & LIMITS_HAS_MAX_FLAG) != 0
168 | _ = readVarUInt32() // initial
169 | if hasMax {
170 | _ = readVarUInt32() // max
171 | }
172 | return bytes[start ..< offset]
173 | }
174 |
175 | /// https://webassembly.github.io/spec/core/binary/types.html#global-types
176 | mutating func consumeGlobalHeader() -> ArraySlice {
177 | let start = offset
178 | _ = readUInt8() // value type
179 | _ = readUInt8() // mutable
180 | return bytes[start ..< offset]
181 | }
182 |
183 | mutating func consumeI32InitExpr() throws -> ArraySlice {
184 | let start = offset
185 | let code = readUInt8()
186 | guard let constOp = ConstOpcode(rawValue: code) else {
187 | throw Error.expectConstOpcode(code)
188 | }
189 | switch constOp {
190 | case .i32Const:
191 | _ = readVarUInt32()
192 | case .f32Const, .f64Const, .i64Const:
193 | throw Error.expectI32Const(constOp)
194 | }
195 | let opcode = readUInt8()
196 | guard opcode == END_INST_OPCODE else {
197 | throw Error.expectEnd
198 | }
199 | return bytes[start ..< offset]
200 | }
201 |
202 | /// https://webassembly.github.io/spec/core/binary/modules.html#binary-local
203 | @discardableResult
204 | mutating func consumeLocals(consumer: Consumer? = nil) throws -> UInt32 {
205 | let start = offset
206 | let count = readVarUInt32()
207 | for _ in 0.. (count: UInt32, rawBytes: ArraySlice) {
215 | let start = offset
216 | let count = readVarUInt32() // n
217 | _ = readUInt8() // value type
218 | return (count, bytes[start ..< offset])
219 | }
220 |
221 | mutating func consumeBlockType() {
222 | let head = bytes[offset]
223 | let length: Int
224 | switch head {
225 | case 0x40:
226 | length = 1
227 | case _ where ValueType(rawValue: head) != nil:
228 | length = 1
229 | default:
230 | (_, length) = decodeSLEB128(bytes, Int64.self)
231 | }
232 | offset += length
233 | }
234 |
235 | mutating func consumeBrTable() {
236 | let count = readVarUInt32()
237 | for _ in 0 ..< count {
238 | _ = readVarUInt32()
239 | }
240 | _ = readVarUInt32()
241 | }
242 |
243 | mutating func consumeMemoryArg() {
244 | _ = readVarUInt32()
245 | _ = readVarUInt32()
246 | }
247 |
248 | mutating func consumeInst(code: UInt8) throws {
249 | switch code {
250 | // https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions
251 | case 0x00, 0x01: break
252 | case 0x02, 0x03, 0x04: consumeBlockType()
253 | case 0x05, 0x0B: break
254 | case 0x0C, 0x0D: _ = readVarUInt32() // label index
255 | case 0x0E: consumeBrTable()
256 | case 0x0F: break
257 | case 0x10: _ = readVarUInt32()
258 | case 0x11:
259 | _ = readVarUInt32() // type index
260 | _ = readUInt8() // 0x00
261 |
262 | // https://webassembly.github.io/spec/core/binary/instructions.html#parametric-instructions
263 | case 0x1A, 0x1B: break
264 |
265 | // https://webassembly.github.io/spec/core/binary/instructions.html#variable-instructions
266 | case 0x20 ... 0x24: _ = readVarUInt32() // local index
267 |
268 | // https://webassembly.github.io/spec/core/binary/instructions.html#memory-instructions
269 | case 0x28 ... 0x3E: consumeMemoryArg()
270 | case 0x3F, 0x40: _ = readUInt8() // 0x00
271 |
272 | // https://webassembly.github.io/spec/core/binary/instructions.html#numeric-instructions
273 | case 0x41: consumeULEB128(UInt32.self)
274 | case 0x42: consumeULEB128(UInt64.self)
275 | case 0x43: _ = read(4)
276 | case 0x44: _ = read(8)
277 | case 0x45 ... 0xC4: break
278 | case 0xFC: _ = readVarUInt32()
279 | default:
280 | throw Error.unexpectedOpcode(code)
281 | }
282 | }
283 |
284 | mutating func readGlobalSet() throws -> UInt32? {
285 | let rawCode = readUInt8()
286 | switch rawCode {
287 | // https://webassembly.github.io/spec/core/binary/instructions.html#variable-instructions
288 | case 0x24:
289 | return readVarUInt32()
290 | default:
291 | try consumeInst(code: rawCode)
292 | return nil
293 | }
294 | }
295 |
296 | mutating func readCallInst() throws -> UInt32? {
297 | let rawCode = readUInt8()
298 | switch rawCode {
299 | // https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions
300 | case 0x10:
301 | return readVarUInt32()
302 | default:
303 | try consumeInst(code: rawCode)
304 | return nil
305 | }
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/LEB128.swift:
--------------------------------------------------------------------------------
1 | func decodeULEB128(_ bytes: ArraySlice, _: T.Type) -> (value: T, offset: Int)
2 | where T: UnsignedInteger, T: FixedWidthInteger
3 | {
4 | var index: Int = bytes.startIndex
5 | var value: T = 0
6 | var shift: UInt = 0
7 | var byte: UInt8
8 | repeat {
9 | byte = bytes[index]
10 | index += 1
11 | value |= T(byte & 0x7F) << shift
12 | shift += 7
13 | } while byte >= 128
14 | return (value, index - bytes.startIndex)
15 | }
16 |
17 | func decodeSLEB128(_ bytes: ArraySlice, _: T.Type) -> (value: T, offset: Int)
18 | where T: SignedInteger, T: FixedWidthInteger
19 | {
20 | var index: Int = bytes.startIndex
21 | var value: T = 0
22 | var shift: UInt = 0
23 | var byte: UInt8
24 | repeat {
25 | byte = bytes[index]
26 | index += 1
27 | value |= T(byte & 0x7F) << shift
28 | shift += 7
29 | } while byte >= 128
30 | if byte & 0x40 != 0 {
31 | value |= T(-1) << shift
32 | }
33 | return (value, index - bytes.startIndex)
34 | }
35 |
36 | func encodeULEB128(_ value: T, padTo: Int? = nil) -> [UInt8]
37 | where T: UnsignedInteger, T: FixedWidthInteger
38 | {
39 | var value = value
40 | var length = 0
41 | var results: [UInt8] = []
42 | var needPad: Bool {
43 | guard let padTo = padTo else { return false }
44 | return length < padTo
45 | }
46 | repeat {
47 | var byte = UInt8(value & 0x7F)
48 | value >>= 7
49 | length += 1
50 | if value != 0 || needPad {
51 | byte |= 0x80
52 | }
53 | results.append(byte)
54 | } while value != 0
55 |
56 | if let padTo = padTo, length < padTo {
57 | while length < padTo - 1 {
58 | results.append(0x80)
59 | length += 1
60 | }
61 | results.append(0x00)
62 | }
63 | return results
64 | }
65 |
66 | func encodeSLEB128(_ value: T, padTo: Int? = nil) -> [UInt8]
67 | where T: SignedInteger, T: FixedWidthInteger
68 | {
69 | var value = value
70 | var length = 0
71 | var results: [UInt8] = []
72 | var hasMore: Bool
73 | var needPad: Bool {
74 | guard let padTo = padTo else { return false }
75 | return length < padTo
76 | }
77 | repeat {
78 | var byte = UInt8(value & 0x7F)
79 | value >>= 7
80 | length += 1
81 | hasMore = !((value == 0 && (byte & 0x40) == 0) || (value == -1 && (byte & 0x40) != 0))
82 | if hasMore || needPad {
83 | byte |= 0x80
84 | }
85 | results.append(byte)
86 | } while hasMore
87 |
88 | if let padTo = padTo, length < padTo {
89 | let padValue: UInt8 = value < 0 ? 0x7F : 0x00
90 | while length < padTo - 1 {
91 | results.append(padValue | 0x80)
92 | length += 1
93 | }
94 | results.append(padValue)
95 | }
96 | return results
97 | }
98 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/OutputWriter.swift:
--------------------------------------------------------------------------------
1 | public protocol OutputWriter {
2 | func writeByte(_ byte: UInt8) throws
3 | func writeBytes(_ bytes: S) throws where S.Element == UInt8
4 | }
5 |
6 | extension OutputWriter {
7 | func writeString(_ value: String) throws {
8 | let bytes = value.utf8
9 | try writeBytes(encodeULEB128(UInt32(bytes.count)))
10 | try writeBytes(bytes)
11 | }
12 |
13 | /// https://webassembly.github.io/spec/core/binary/types.html#result-types
14 | func writeResultTypes(_ types: [ValueType]) throws {
15 | try writeBytes(encodeULEB128(UInt32(types.count)))
16 | for type in types {
17 | try writeByte(type.rawValue)
18 | }
19 | }
20 |
21 | mutating func writeSection(_ type: SectionType, bodyWriter: (inout InMemoryOutputWriter) throws -> T) throws -> T {
22 | try writeByte(type.rawValue)
23 | var buffer = InMemoryOutputWriter()
24 | let result = try bodyWriter(&buffer)
25 | try writeBytes(encodeULEB128(UInt32(buffer.bytes().count)))
26 | try writeBytes(buffer.bytes())
27 | return result
28 | }
29 |
30 | mutating func writeVectorSection(
31 | type: SectionType,
32 | reader: Reader, extras: [Reader.Item] = []
33 | ) throws where Reader.Item: ByteEncodable {
34 | let count = reader.count + UInt32(extras.count)
35 | try self.writeSection(type) { buffer in
36 | try buffer.writeBytes(encodeULEB128(count))
37 | for result in reader {
38 | let entry = try result.get()
39 | try entry.encode(to: &buffer)
40 | }
41 | for extra in extras {
42 | try extra.encode(to: &buffer)
43 | }
44 | }
45 | }
46 |
47 | mutating func writeVectorSection(
48 | type: SectionType,
49 | count: UInt32,
50 | writeItems: (inout InMemoryOutputWriter) throws -> Void
51 | ) throws {
52 | try self.writeSection(type) { buffer in
53 | try buffer.writeBytes(encodeULEB128(count))
54 | try writeItems(&buffer)
55 | }
56 | }
57 |
58 | mutating func writeVectorSection(
59 | type: SectionType,
60 | items: [Item] = []
61 | ) throws {
62 | let count = UInt32(items.count)
63 | try self.writeSection(type) { buffer in
64 | try buffer.writeBytes(encodeULEB128(count))
65 | for extra in items {
66 | try extra.encode(to: &buffer)
67 | }
68 | }
69 | }
70 | }
71 |
72 | public class InMemoryOutputWriter: OutputWriter {
73 | private var _bytes: [UInt8] = []
74 |
75 | public init(reservingCapacity capacity: Int = 0) {
76 | _bytes.reserveCapacity(capacity)
77 | }
78 |
79 | public func writeByte(_ byte: UInt8) throws {
80 | _bytes.append(byte)
81 | }
82 |
83 | public func writeBytes(_ newBytes: S) throws where S : Sequence, S.Element == UInt8 {
84 | _bytes.append(contentsOf: newBytes)
85 | }
86 |
87 | public func bytes() -> [UInt8] { _bytes }
88 | }
89 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Readers/CodeSectionReader.swift:
--------------------------------------------------------------------------------
1 | public struct FunctionBody {
2 | public internal(set) var input: InputByteStream
3 | public let size: UInt32
4 | public let endOffset: Int
5 |
6 | func locals() -> LocalsReader {
7 | LocalsReader(input: input)
8 | }
9 | }
10 |
11 | struct LocalsReader {
12 | var input: InputByteStream
13 | let count: UInt32
14 |
15 | init(input: InputByteStream) {
16 | self.input = input
17 | self.count = self.input.readVarUInt32()
18 | }
19 |
20 | mutating func read() throws -> (count: UInt32, rawBytes: ArraySlice) {
21 | input.consumeLocal()
22 | }
23 |
24 | func operators() -> InputByteStream {
25 | input
26 | }
27 | }
28 |
29 | public struct CodeSectionReader: VectorSectionReader {
30 | var input: InputByteStream
31 | public let count: UInt32
32 |
33 | init(input: InputByteStream) {
34 | self.input = input
35 | self.count = self.input.readVarUInt32()
36 | }
37 |
38 | public mutating func read() throws -> FunctionBody {
39 | let size = input.readVarUInt32()
40 | let body = FunctionBody(input: input, size: size,
41 | endOffset: input.offset + Int(size))
42 | input.skip(Int(size))
43 | return body
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Readers/ElementSectionReader.swift:
--------------------------------------------------------------------------------
1 | public struct ElementSegment {
2 | let flags: UInt32
3 | let initExpr: ArraySlice
4 | var items: ElementItemsReader
5 | }
6 |
7 | struct FunctionIndex: Equatable {
8 | let value: UInt32
9 | }
10 |
11 | struct ElementItemsReader {
12 | var input: InputByteStream
13 | let count: UInt32
14 |
15 | mutating func read() -> FunctionIndex {
16 | FunctionIndex(value: input.readVarUInt32())
17 | }
18 | }
19 |
20 | public struct ElementSectionReader: VectorSectionReader {
21 | var input: InputByteStream
22 | public let count: UInt32
23 |
24 | init(input: InputByteStream) {
25 | self.input = input
26 | self.count = self.input.readVarUInt32()
27 | }
28 |
29 | public mutating func read() throws -> ElementSegment {
30 | let flags = input.readVarUInt32()
31 | let initExpr = try input.consumeI32InitExpr()
32 | let count = input.readVarUInt32()
33 | return ElementSegment(
34 | flags: flags,
35 | initExpr: initExpr,
36 | items: ElementItemsReader(input: input, count: count)
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Readers/FunctionSectionReader.swift:
--------------------------------------------------------------------------------
1 | public struct SignatureIndex: Equatable {
2 | public let value: UInt32
3 | }
4 |
5 | public struct FunctionSectionReader: VectorSectionReader {
6 | var input: InputByteStream
7 | public let count: UInt32
8 |
9 | init(input: InputByteStream) {
10 | self.input = input
11 | self.count = self.input.readVarUInt32()
12 | }
13 |
14 | public mutating func read() throws -> SignatureIndex {
15 | return SignatureIndex(value: input.readVarUInt32())
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Readers/ImportSectionReader.swift:
--------------------------------------------------------------------------------
1 | public struct Import {
2 | public var module: String
3 | public var field: String
4 | public var descriptor: ImportDescriptor
5 | }
6 |
7 | public enum ImportDescriptor {
8 | case function(UInt32)
9 | case table(rawBytes: ArraySlice)
10 | case memory(rawBytes: ArraySlice)
11 | case global(rawBytes: ArraySlice)
12 | }
13 |
14 | public struct ImportSectionReader: VectorSectionReader {
15 | var input: InputByteStream
16 | public let count: UInt32
17 |
18 | init(input: InputByteStream) {
19 | self.input = input
20 | self.count = self.input.readVarUInt32()
21 | }
22 |
23 | public mutating func read() throws -> Import {
24 | try input.readImport()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Readers/ModuleReader.swift:
--------------------------------------------------------------------------------
1 | public enum ModuleSection {
2 | case type(TypeSectionReader)
3 | case `import`(ImportSectionReader)
4 | case function(FunctionSectionReader)
5 | case element(ElementSectionReader)
6 | case code(CodeSectionReader)
7 | case rawSection(type: SectionType, content: ArraySlice)
8 | }
9 |
10 | public struct ModuleReader {
11 | enum Error: Swift.Error {
12 | case invalidMagic
13 | }
14 |
15 | var input: InputByteStream
16 |
17 | public init(input: InputByteStream) {
18 | self.input = input
19 | }
20 |
21 | public var isEOF: Bool { input.isEOF }
22 |
23 | mutating func readHeader() throws -> ArraySlice {
24 | return try input.readHeader()
25 | }
26 |
27 | public mutating func readSection() throws -> ModuleSection {
28 | let sectionInfo = try input.readSectionInfo()
29 | defer {
30 | input.seek(sectionInfo.endOffset)
31 | }
32 | switch sectionInfo.type {
33 | case .type:
34 | return .type(TypeSectionReader(input: input))
35 | case .import:
36 | return .import(ImportSectionReader(input: input))
37 | case .function:
38 | return .function(FunctionSectionReader(input: input))
39 | case .element:
40 | return .element(ElementSectionReader(input: input))
41 | case .code:
42 | return .code(CodeSectionReader(input: input))
43 | default:
44 | return .rawSection(
45 | type: sectionInfo.type,
46 | content: input.bytes[sectionInfo.contentRange]
47 | )
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Readers/TypeSectionReader.swift:
--------------------------------------------------------------------------------
1 | public struct TypeSectionReader: VectorSectionReader {
2 | enum Error: Swift.Error {
3 | case unsupportedTypeDefKind(UInt8)
4 | }
5 |
6 | var input: InputByteStream
7 | public let count: UInt32
8 |
9 | init(input: InputByteStream) {
10 | self.input = input
11 | count = self.input.readVarUInt32()
12 | }
13 |
14 | public mutating func read() throws -> FuncSignature {
15 | let rawKind = input.readUInt8()
16 | switch rawKind {
17 | case 0x60:
18 | return try input.readFuncType()
19 | default:
20 | throw Error.unsupportedTypeDefKind(rawKind)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Readers/VectorSectionReader.swift:
--------------------------------------------------------------------------------
1 | public protocol VectorSectionReader: Sequence where Element == Result- {
2 | associatedtype Item
3 | var count: UInt32 { get }
4 | mutating func read() throws -> Item
5 | }
6 |
7 | public struct VectorSectionIterator: IteratorProtocol {
8 | private(set) var reader: Reader
9 | private(set) var left: UInt32
10 | init(reader: Reader, count: UInt32) {
11 | self.reader = reader
12 | self.left = count
13 | }
14 | private var end: Bool = false
15 | public mutating func next() -> Reader.Element? {
16 | guard !end else { return nil }
17 | guard left != 0 else { return nil }
18 | let result = Result(catching: { try reader.read() })
19 | left -= 1
20 | switch result {
21 | case .success: return result
22 | case .failure:
23 | end = true
24 | return result
25 | }
26 | }
27 | }
28 |
29 | extension VectorSectionReader where Iterator == VectorSectionIterator {
30 | __consuming public func makeIterator() -> VectorSectionIterator {
31 | VectorSectionIterator(reader: self, count: count)
32 | }
33 |
34 | public func collect() throws -> [Item] {
35 | var items: [Item] = []
36 | items.reserveCapacity(Int(count))
37 | for result in self {
38 | try items.append(result.get())
39 | }
40 | return items
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Sections.swift:
--------------------------------------------------------------------------------
1 | public struct SectionInfo: Equatable {
2 | public let startOffset: Int
3 | public let contentStart: Int
4 | public var endOffset: Int {
5 | contentStart + size
6 | }
7 | public var contentRange: Range {
8 | contentStart.. (Trampoline, Int)? {
40 | trampolineByBaseFuncIndex[index]
41 | }
42 |
43 | typealias Iterator = Array.Iterator
44 | func makeIterator() -> Iterator {
45 | trampolines.makeIterator()
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Transformers/CustomSectionStripper.swift:
--------------------------------------------------------------------------------
1 |
2 | public struct CustomSectionStripper: Transformer {
3 |
4 | private let stripIf: (_ name: String) -> Bool
5 |
6 | /// Create a new transformer of `CustomSectionStripper`
7 | /// - Parameter stripIf: The closure accepting the custom section name and
8 | /// returning `true` if it should strip the section.
9 | public init(
10 | stripIf: @escaping (_ name: String) -> Bool = { _ in true }
11 | ) {
12 | self.stripIf = stripIf
13 | }
14 |
15 | public let metadata = TransformerMetadata(
16 | name: "strip-custom-section", description: "Strips custom sections from the wasm module"
17 | )
18 |
19 | public func transform(_ input: inout InputByteStream, writer: inout Writer) throws {
20 | let version = try input.readHeader()
21 | try writer.writeBytes(magic)
22 | try writer.writeBytes(version)
23 |
24 | while !input.isEOF {
25 | let section = try input.readSectionInfo()
26 |
27 | let shouldStrip: Bool
28 | if section.type == .custom {
29 | let name = input.readString()
30 | shouldStrip = stripIf(name)
31 | } else {
32 | shouldStrip = false
33 | }
34 | input.seek(section.endOffset)
35 | if !shouldStrip {
36 | try writer.writeBytes(input.bytes[section.startOffset.. Bool
11 |
12 | public init(shouldLower: @escaping (Import) -> Bool = { _ in true }) {
13 | self.shouldLower = shouldLower
14 | }
15 |
16 | public let metadata = TransformerMetadata(
17 | name: "i64-to-i32-lowering",
18 | description: "Replaces all i64 imports with i32 imports"
19 | )
20 |
21 | public func transform(
22 | _ input: inout InputByteStream,
23 | writer: inout Writer
24 | ) throws {
25 | var moduleReader = ModuleReader(input: input)
26 | let version = try moduleReader.readHeader()
27 | try writer.writeBytes(magic)
28 | try writer.writeBytes(version)
29 |
30 | var importedFunctionCount = 0
31 | var trampolines = Trampolines()
32 | do {
33 | // Phase 1. Scan Type and Import sections to determine import records
34 | // which will be lowered.
35 | var copyingSections: [(type: SectionType, content: ArraySlice)] = []
36 | var typeSection: [FuncSignature] = []
37 | var replacements: [ImportFuncReplacement] = []
38 | var importSection: ImportSectionReader?
39 | Phase1: while !moduleReader.isEOF {
40 | switch try moduleReader.readSection() {
41 | case .type(let reader):
42 | typeSection = try reader.collect()
43 | case .import(var reader):
44 | importSection = reader
45 | importedFunctionCount = try scan(
46 | importSection: &reader,
47 | typeSection: &typeSection, replacements: &replacements,
48 | trampolines: &trampolines
49 | )
50 | break Phase1
51 | case .rawSection(type: .custom, content: let content):
52 | copyingSections.append((type: .custom, content: content))
53 | case .element:
54 | throw Error.unexpectedSection(SectionType.element.rawValue)
55 | case .function(_):
56 | throw Error.unexpectedSection(SectionType.function.rawValue)
57 | case .code(_):
58 | throw Error.unexpectedSection(SectionType.code.rawValue)
59 | case .rawSection(let type, _):
60 | throw Error.unexpectedSection(type.rawValue)
61 | }
62 | }
63 |
64 | // Phase 2. Write out Type and Import section based on scanned results.
65 | try writer.writeVectorSection(type: .type, items: typeSection)
66 | if var importSection = importSection {
67 | try writer.writeVectorSection(type: .import, count: importSection.count) { writer in
68 | for index in 0 ..< importSection.count {
69 | var entry = try importSection.read()
70 | switch entry.descriptor {
71 | case .function:
72 | if let replacement = replacements.first(where: { $0.index == index }) {
73 | entry.descriptor = .function(UInt32(replacement.toTypeIndex))
74 | }
75 | default: break
76 | }
77 | try entry.encode(to: &writer)
78 | }
79 | }
80 | }
81 |
82 | for section in copyingSections {
83 | try writer.writeSection(section.type) { buffer in
84 | try buffer.writeBytes(section.content)
85 | }
86 | }
87 | }
88 |
89 | // After here, we can emit binary sequentially
90 |
91 | var originalFuncCount: Int?
92 | while !moduleReader.isEOF {
93 | switch try moduleReader.readSection() {
94 | case .type, .import:
95 | fatalError("unreachable")
96 | case .function(let reader):
97 | // Phase 3. Write out Func section and add trampoline signatures.
98 | try writer.writeVectorSection(type: .function, reader: reader, extras: trampolines.map {
99 | SignatureIndex(value: UInt32($0.fromSignatureIndex))
100 | })
101 | originalFuncCount = Int(reader.count) + importedFunctionCount
102 | case .element(var reader):
103 | // Phase 4. Read Elem section and rewrite i64 functions with trampoline functions.
104 | guard let originalFuncCount = originalFuncCount else {
105 | throw Error.expectFunctionSection
106 | }
107 | try transformElemSection(
108 | input: &reader, writer: &writer,
109 | trampolines: trampolines, originalFuncCount: originalFuncCount
110 | )
111 | case .code(var reader):
112 | // Phase 5. Read Code section and rewrite i64 function calls with trampoline function call.
113 | // And add trampoline functions at the tail
114 | guard let originalFuncCount = originalFuncCount else {
115 | throw Error.expectFunctionSection
116 | }
117 | try transformCodeSection(
118 | input: &reader, writer: &writer,
119 | trampolines: trampolines, originalFuncCount: originalFuncCount
120 | )
121 | case .rawSection(let type, let content):
122 | try writer.writeSection(type) { buffer in
123 | try buffer.writeBytes(content)
124 | }
125 | }
126 | }
127 | }
128 |
129 | /// https://webassembly.github.io/spec/core/binary/modules.html#import-section
130 | /// Returns a count of imported functions
131 | func scan(importSection: inout ImportSectionReader,
132 | typeSection: inout [FuncSignature],
133 | replacements: inout [ImportFuncReplacement],
134 | trampolines: inout Trampolines) throws -> Int
135 | {
136 | var importFuncCount = 0
137 | for (index, result) in importSection.enumerated() {
138 | let entry = try result.get()
139 | switch entry.descriptor {
140 | case .function(let sigIndex):
141 | let signatureIndex = Int(sigIndex)
142 | let signature = typeSection[signatureIndex]
143 | defer { importFuncCount += 1 }
144 | guard signature.hasI64Param(), shouldLower(entry) else { continue }
145 | let toTypeIndex = typeSection.count
146 | let toSignature = signature.lowered()
147 | typeSection.append(toSignature)
148 | replacements.append(
149 | (index: Int(index), toTypeIndex: toTypeIndex)
150 | )
151 | trampolines.add(
152 | importIndex: importFuncCount, from: signature,
153 | fromIndex: signatureIndex, to: toSignature
154 | )
155 | case .table, .memory, .global: break
156 | }
157 | }
158 | return importFuncCount
159 | }
160 | }
161 |
162 | private func transformCodeSection(
163 | input: inout CodeSectionReader, writer: inout Writer,
164 | trampolines: Trampolines, originalFuncCount: Int) throws
165 | {
166 | let count = input.count
167 | let newCount = count + UInt32(trampolines.count)
168 | try writer.writeVectorSection(type: .code, count: newCount) { writer in
169 | for _ in 0 ..< input.count {
170 | let body = try input.read()
171 | let bodyBuffer = try replaceFunctionCall(body: body) { funcIndex in
172 | guard let (_, trampolineIndex) = trampolines.trampoline(byBaseFuncIndex: Int(funcIndex)) else { return nil }
173 | return UInt32(originalFuncCount + trampolineIndex)
174 | }
175 | let newSize = bodyBuffer.count
176 | try writer.writeBytes(encodeULEB128(UInt32(newSize)))
177 | try writer.writeBytes(bodyBuffer)
178 | }
179 | for trampoline in trampolines {
180 | try trampoline.write(to: writer)
181 | }
182 | }
183 | }
184 | private func replaceFunctionCall(
185 | body: FunctionBody,
186 | replace: (_ funcIndex: UInt32) -> UInt32?
187 | ) throws -> [UInt8] {
188 | var locals = body.locals()
189 | var bodyBuffer: [UInt8] = []
190 | bodyBuffer.reserveCapacity(Int(body.size))
191 |
192 | bodyBuffer.append(contentsOf: encodeULEB128(locals.count))
193 | for _ in 0 ..< locals.count {
194 | try bodyBuffer.append(contentsOf: locals.read().rawBytes)
195 | }
196 |
197 | var operators = locals.operators()
198 | var nonCallInstStart = operators.offset
199 | while operators.offset < body.endOffset {
200 | let nonCallInstEnd = operators.offset
201 | guard let funcIndex = try operators.readCallInst(),
202 | let newFuncIndex = replace(funcIndex) else {
203 | continue
204 | }
205 | bodyBuffer.append(contentsOf: operators.bytes[nonCallInstStart..(
217 | input: inout ElementSectionReader, writer: inout Writer,
218 | trampolines: Trampolines, originalFuncCount: Int
219 | ) throws {
220 | try writer.writeVectorSection(type: .element, count: input.count) { writer in
221 | for result in input {
222 | var entry = try result.get()
223 | try writer.writeBytes(encodeULEB128(entry.flags))
224 | try writer.writeBytes(entry.initExpr)
225 | try writer.writeBytes(encodeULEB128(entry.items.count))
226 | for _ in 0.. FuncSignature {
240 | func transform(_ type: ValueType) -> ValueType {
241 | if case .i64 = type { return .i32 }
242 | else { return type }
243 | }
244 | return FuncSignature(
245 | params: params.map(transform),
246 | results: results
247 | )
248 | }
249 |
250 | func hasI64Param() -> Bool {
251 | for param in params {
252 | if param == ValueType.i64 {
253 | return true
254 | }
255 | }
256 | return false
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Transformers/SizeProfiler.swift:
--------------------------------------------------------------------------------
1 | public extension InputByteStream {
2 | mutating func readSectionsInfo() throws -> [SectionInfo] {
3 | precondition(offset == bytes.startIndex)
4 | _ = try readHeader()
5 |
6 | var result = [SectionInfo]()
7 | while !isEOF {
8 | let section = try readSectionInfo()
9 | result.append(section)
10 | skip(section.size)
11 |
12 | }
13 | return result
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Transformers/StackOverflowSanitizer+Fixtures.swift:
--------------------------------------------------------------------------------
1 | // GENERATED FROM Tools/GenerateStackOverflowSanitizerSupport.swift
2 |
3 | extension StackOverflowSanitizer {
4 | public static let supportObjectFile: [UInt8] = [
5 | 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60, 0x00, 0x00, 0x02, 0xc2, 0x80, 0x80, 0x80, 0x00, 0x02, 0x03, 0x65, 0x6e, 0x76, 0x0f, 0x5f, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x00, 0x11, 0x5f, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x15, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x00, 0x00, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x0a, 0x8b, 0x80, 0x80, 0x80, 0x00, 0x01, 0x09, 0x00, 0x10, 0x80, 0x80, 0x80, 0x80, 0x00, 0x0f, 0x0b, 0x00, 0xc6, 0x80, 0x80, 0x80, 0x00, 0x07, 0x6c, 0x69, 0x6e, 0x6b, 0x69, 0x6e, 0x67, 0x02, 0x08, 0xb7, 0x80, 0x80, 0x80, 0x00, 0x02, 0x00, 0x04, 0x01, 0x07, 0x5f, 0x5f, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x00, 0x50, 0x00, 0x27, 0x5f, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x73, 0x61, 0x6e, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x00, 0x90, 0x80, 0x80, 0x80, 0x00, 0x0a, 0x72, 0x65, 0x6c, 0x6f, 0x63, 0x2e, 0x43, 0x4f, 0x44, 0x45, 0x03, 0x01, 0x00, 0x04, 0x01, 0x00, 0x89, 0x81, 0x80, 0x80, 0x00, 0x09, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x72, 0x73, 0x01, 0x0c, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x2d, 0x62, 0x79, 0x01, 0x0f, 0x53, 0x77, 0x69, 0x66, 0x74, 0x57, 0x61, 0x73, 0x6d, 0x20, 0x63, 0x6c, 0x61, 0x6e, 0x67, 0x5f, 0x31, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x20, 0x28, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x77, 0x69, 0x66, 0x74, 0x77, 0x61, 0x73, 0x6d, 0x2f, 0x6c, 0x6c, 0x76, 0x6d, 0x2d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x67, 0x69, 0x74, 0x20, 0x31, 0x36, 0x35, 0x64, 0x38, 0x61, 0x62, 0x32, 0x64, 0x31, 0x39, 0x62, 0x65, 0x63, 0x63, 0x30, 0x64, 0x39, 0x61, 0x39, 0x63, 0x62, 0x33, 0x31, 0x39, 0x32, 0x39, 0x65, 0x30, 0x39, 0x36, 0x63, 0x38, 0x30, 0x36, 0x37, 0x34, 0x36, 0x65, 0x38, 0x29
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/Sources/WasmTransformer/Transformers/StackOverflowSanitizer.swift:
--------------------------------------------------------------------------------
1 |
2 | public struct StackOverflowSanitizer: Transformer {
3 | enum Error: Swift.Error {
4 | case expectFunctionSection
5 | case supportLibraryNotLinked
6 | case expectTypeSection
7 | case invalidFunctionIndex
8 | case invalidTypeIndex
9 | }
10 |
11 | public init() {}
12 |
13 | public let metadata = TransformerMetadata(
14 | name: "stack-sanitizer",
15 | description: "Sanitize stack overflow assuming --stack-first and stack pointer is placed at globals[0]"
16 | )
17 |
18 | public func transform(_ input: inout InputByteStream, writer: inout Writer) throws {
19 | let version = try input.readHeader()
20 | try writer.writeBytes(magic)
21 | try writer.writeBytes(version)
22 | var importCount: UInt32?
23 | var reportFuncIndex: UInt32?
24 | var assertSigIndex: UInt32?
25 | var assertFuncIndex: UInt32?
26 |
27 | while !input.isEOF {
28 | let sectionInfo = try input.readSectionInfo()
29 |
30 | switch sectionInfo.type {
31 | case .type:
32 | let reader = TypeSectionReader(input: input)
33 | assertSigIndex = reader.count
34 | let assertSignature = FuncSignature(
35 | params: [.i32], results: [.i32]
36 | )
37 | try writer.writeVectorSection(
38 | type: .type, reader: reader, extras: [assertSignature]
39 | )
40 | case .import:
41 | let reader = ImportSectionReader(input: input)
42 | importCount = reader.count
43 | let entries = try reader.lazy.map { try $0.get() }
44 | .filter {
45 | guard case .function(_) = $0.descriptor else { return false }
46 | return true
47 | }
48 | reportFuncIndex = entries.firstIndex(where: {
49 | return $0.module == "__stack_sanitizer" && $0.field == "report_stack_overflow"
50 | })
51 | .map(UInt32.init)
52 |
53 | try writer.writeBytes(
54 | input.bytes[sectionInfo.startOffset ..< sectionInfo.endOffset]
55 | )
56 | case .function:
57 | let reader = FunctionSectionReader(input: input)
58 | assertFuncIndex = (importCount ?? 0) + reader.count
59 | guard let assertSigIndex = assertSigIndex else {
60 | throw Error.expectTypeSection
61 | }
62 | try writer.writeVectorSection(
63 | type: .function, reader: reader, extras: [
64 | SignatureIndex(value: assertSigIndex)
65 | ]
66 | )
67 | case .code:
68 | guard let reportFuncIndex = reportFuncIndex else {
69 | throw Error.supportLibraryNotLinked
70 | }
71 | guard let assertFuncIndex = assertFuncIndex else {
72 | throw Error.expectFunctionSection
73 | }
74 | var reader = CodeSectionReader(input: input)
75 | try transformCodeSection(
76 | input: &reader, writer: &writer,
77 | reportFuncIndex: reportFuncIndex,
78 | assertFuncIndex: assertFuncIndex
79 | )
80 | default:
81 | try writer.writeBytes(
82 | input.bytes[sectionInfo.startOffset ..< sectionInfo.endOffset]
83 | )
84 | }
85 | input.skip(sectionInfo.size)
86 | }
87 | }
88 |
89 | func transformCodeSection(
90 | input: inout CodeSectionReader,
91 | writer: inout Writer,
92 | reportFuncIndex: UInt32,
93 | assertFuncIndex: UInt32
94 | ) throws {
95 | try writer.writeVectorSection(type: .code, count: input.count + 1) { writer in
96 | for _ in 0 ..< input.count {
97 | let body = try input.read()
98 | try transformFunction(
99 | input: body, writer: writer,
100 | assertFuncIndex: assertFuncIndex
101 | )
102 | }
103 | try emitStackPointerAssert(reportFuncIndex: reportFuncIndex, writer: writer)
104 | }
105 | }
106 |
107 | func emitStackPointerAssert(reportFuncIndex: UInt32, writer: OutputWriter) throws {
108 | var bodyBuffer: [UInt8] = []
109 | bodyBuffer.append(0x00) // local decl count
110 | let opcode = [
111 | Opcode.localGet(0),
112 | Opcode.i32Const(0),
113 | Opcode.i32LtS,
114 | Opcode.if(.empty),
115 | Opcode.call(reportFuncIndex),
116 | Opcode.end,
117 | Opcode.localGet(0),
118 | Opcode.end,
119 | ]
120 | bodyBuffer.append(contentsOf: opcode.flatMap { $0.serialize() })
121 | try writer.writeBytes(encodeULEB128(UInt32(bodyBuffer.count)))
122 | try writer.writeBytes(bodyBuffer)
123 | }
124 |
125 | func transformFunction(input: FunctionBody, writer: OutputWriter,
126 | assertFuncIndex: UInt32) throws {
127 | let oldSize = Int(input.size)
128 | var bodyBuffer: [UInt8] = []
129 | bodyBuffer.reserveCapacity(oldSize)
130 |
131 | var locals = input.locals()
132 |
133 | bodyBuffer.append(contentsOf: encodeULEB128(locals.count))
134 | for _ in 0 ..< locals.count {
135 | try bodyBuffer.append(contentsOf: locals.read().rawBytes)
136 | }
137 |
138 | var operators = locals.operators()
139 | var lazyChunkStart = operators.offset
140 | while operators.offset < input.endOffset {
141 | let lazyChunkEnd = operators.offset
142 | let rawCode = operators.readUInt8()
143 | func flushLazyChunk() {
144 | bodyBuffer.append(contentsOf: operators.bytes[lazyChunkStart..(_ input: inout InputByteStream, writer: inout Writer) throws
9 | }
10 |
11 | public func lowerI64Imports(
12 | _ input: [UInt8],
13 | shouldLower: @escaping (Import) -> Bool = { _ in true }
14 | ) throws -> [UInt8] {
15 | let transformer = I64ImportTransformer(shouldLower: shouldLower)
16 | var inputStream = InputByteStream(bytes: input)
17 | var writer = InMemoryOutputWriter()
18 | try transformer.transform(&inputStream, writer: &writer)
19 | return writer.bytes()
20 | }
21 |
22 | public func stripCustomSections(_ input: [UInt8]) throws -> [UInt8] {
23 | let transformer = CustomSectionStripper()
24 | var inputStream = InputByteStream(bytes: input)
25 | var writer = InMemoryOutputWriter()
26 | try transformer.transform(&inputStream, writer: &writer)
27 | return writer.bytes()
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/wasm-trans/main.swift:
--------------------------------------------------------------------------------
1 | import WasmTransformer
2 | import ArgumentParser
3 | import Foundation
4 |
5 | struct TransformerOption: EnumerableFlag, CustomStringConvertible {
6 | let transformer: Transformer
7 |
8 | var description: String {
9 | transformer.metadata.name
10 | }
11 |
12 | static func name(for value: TransformerOption) -> NameSpecification {
13 | .long
14 | }
15 |
16 | static func help(for value: TransformerOption) -> ArgumentHelp? {
17 | ArgumentHelp(value.transformer.metadata.description)
18 | }
19 |
20 | static var allCases: [TransformerOption] {
21 | [
22 | I64ImportTransformer(),
23 | CustomSectionStripper(),
24 | StackOverflowSanitizer()
25 | ]
26 | .map(TransformerOption.init(transformer: ))
27 | }
28 |
29 | static func == (lhs: TransformerOption, rhs: TransformerOption) -> Bool {
30 | return lhs.description == rhs.description
31 | }
32 | }
33 |
34 | struct WasmTrans: ParsableCommand {
35 |
36 | @Argument
37 | var input: String
38 | @Option(name: .shortAndLong, help: "Output file")
39 | var output: String
40 |
41 | @Flag
42 | var passes: [TransformerOption] = []
43 |
44 | mutating func run() throws {
45 | var nextBytes = try Array(Data(contentsOf: URL(fileURLWithPath: input)))
46 | for pass in passes {
47 | var inputStream = InputByteStream(bytes: nextBytes)
48 | var writer = InMemoryOutputWriter(reservingCapacity: nextBytes.count)
49 | try pass.transformer.transform(&inputStream, writer: &writer)
50 | nextBytes = writer.bytes()
51 | }
52 | try Data(nextBytes).write(to: URL(fileURLWithPath: output))
53 | }
54 | }
55 |
56 | WasmTrans.main()
57 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | fatalError("Use `swift test --enable-test-discovery` to run tests")
2 |
--------------------------------------------------------------------------------
/Tests/WasmTransformerTests/CustomSectionStripperTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import WasmTransformer
3 |
4 | private func transformWat(_ input: String, stripIf: @escaping (_ name: String) -> Bool = { _ in true }) throws -> URL {
5 | let inputWasm = compileWat(input, options: ["--debug-names"])
6 | let transformer = CustomSectionStripper(stripIf: stripIf)
7 | var inputStream = try InputByteStream(from: inputWasm)
8 | var writer = InMemoryOutputWriter()
9 | try transformer.transform(&inputStream, writer: &writer)
10 |
11 | let (url, handle) = makeTemporaryFile()
12 | handle.write(Data(writer.bytes()))
13 | return url
14 | }
15 |
16 | final class CustomSectionStripperTests: XCTestCase {
17 | static let wat = """
18 | (module
19 | (func $add (result i32)
20 | (i32.add
21 | (i32.const 1)
22 | (i32.const 1)
23 | )
24 | )
25 | )
26 | """
27 | func testStripDebugSection() throws {
28 | let wat = Self.wat
29 | do {
30 | let original = compileWat(wat, options: ["--debug-names"])
31 | let output = wasmObjdump(original, args: ["--header"])
32 | let expectedCustomSection = #"Custom start=0x00000020 end=0x00000032 (size=0x00000012) "name""#
33 | XCTAssertTrue(output.contains(expectedCustomSection))
34 | }
35 | do {
36 | let url = try transformWat(wat)
37 | let output = wasmObjdump(url, args: ["--header"])
38 | XCTAssertFalse(output.contains("Custom start="))
39 | }
40 | }
41 |
42 | func testStripIf() throws {
43 | let wat = Self.wat
44 | do {
45 | let original = compileWat(wat, options: ["--debug-names"])
46 | let output = wasmObjdump(original, args: ["--header"])
47 | let expectedCustomSection = #"Custom start=0x00000020 end=0x00000032 (size=0x00000012) "name""#
48 | XCTAssertTrue(output.contains(expectedCustomSection))
49 | }
50 | do {
51 | let url = try transformWat(wat, stripIf: { $0 != "name" })
52 | let output = wasmObjdump(url, args: ["--header"])
53 | XCTAssertTrue(output.contains("Custom start="))
54 | }
55 | do {
56 | let url = try transformWat(wat, stripIf: { $0 == "name" })
57 | let output = wasmObjdump(url, args: ["--header"])
58 | XCTAssertFalse(output.contains("Custom start="))
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Tests/WasmTransformerTests/I64ImportTransformerTests.swift:
--------------------------------------------------------------------------------
1 | @testable import WasmTransformer
2 | import XCTest
3 |
4 | private func transformWat(_ input: String, shouldLower: @escaping (Import) -> Bool = { _ in true }) throws -> URL {
5 | let inputWasm = compileWat(input)
6 | let transformer = I64ImportTransformer(shouldLower: shouldLower)
7 | var inputStream = try InputByteStream(from: inputWasm)
8 | var writer = InMemoryOutputWriter()
9 | try transformer.transform(&inputStream, writer: &writer)
10 |
11 | let (url, handle) = makeTemporaryFile()
12 | handle.write(Data(writer.bytes()))
13 | return url
14 | }
15 |
16 | final class I64ImportTransformerTests: XCTestCase {
17 |
18 | func testI64ParamsImport() throws {
19 | let wat = """
20 | (module
21 | (import "foo" "bar" (func (param i64)))
22 | )
23 | """
24 | let url = try transformWat(wat)
25 | let output = wasmObjdump(url, args: ["--details"])
26 | let expectedTypes = """
27 | Type[2]:
28 | - type[0] (i64) -> nil
29 | - type[1] (i32) -> nil
30 | """
31 | XCTAssertTrue(output.contains(expectedTypes))
32 | let expectedImport = """
33 | Import[1]:
34 | - func[0] sig=1 <- foo.bar
35 | """
36 | XCTAssertContains(output, contains: expectedImport)
37 | }
38 |
39 | func testI64ResultImport() throws {
40 | let wat = """
41 | (module
42 | (import "foo" "bar" (func (result i64)))
43 | )
44 | """
45 | let url = try transformWat(wat)
46 | let output = wasmObjdump(url, args: ["--details"])
47 | // No transformation happens since small to large cast is valid without BigInt support
48 | let expectedTypes = """
49 | Type[1]:
50 | - type[0] () -> i64
51 | """
52 | XCTAssertTrue(output.contains(expectedTypes))
53 | let expectedImport = """
54 | Import[1]:
55 | - func[0] sig=0 <- foo.bar
56 | """
57 | XCTAssertContains(output, contains: expectedImport)
58 | }
59 |
60 | func testSelectLoweringImport() throws {
61 | let wat = """
62 | (module
63 | (import "foo" "bar" (func (param i64)))
64 | (import "fizz" "bar" (func (param i64)))
65 | )
66 | """
67 | let url = try transformWat(wat) { $0.module == "fizz" }
68 | let output = wasmObjdump(url, args: ["--details"])
69 | let expectedTypes = """
70 | Type[2]:
71 | - type[0] (i64) -> nil
72 | - type[1] (i32) -> nil
73 | """
74 | XCTAssertTrue(output.contains(expectedTypes))
75 | let expectedImport = """
76 | Import[2]:
77 | - func[0] sig=0 <- foo.bar
78 | - func[1] sig=1 <- fizz.bar
79 | """
80 | XCTAssertContains(output, contains: expectedImport)
81 | }
82 |
83 | func testI64ImportCall() throws {
84 | typealias TestCase = (
85 | line: UInt, wat: String, expectedTypes: String?, expectedImport: String?, expectedCode: String?
86 | )
87 |
88 | let testCases: [TestCase] = [
89 | (line: #line,
90 | wat: """
91 | (module
92 | (import "foo" "bar" (func (param i64)))
93 | (func (call 0 (i64.const 0)))
94 | )
95 | """,
96 | expectedTypes: """
97 | Type[3]:
98 | - type[0] (i64) -> nil
99 | - type[1] () -> nil
100 | - type[2] (i32) -> nil
101 | """,
102 | expectedImport: """
103 | Import[1]:
104 | - func[0] sig=2 <- foo.bar
105 | """,
106 | expectedCode: """
107 | 00002c func[1]:
108 | 00002d: 42 00 | i64.const 0
109 | 00002f: 10 02 | call 2
110 | 000031: 0b | end
111 | 000033 func[2]:
112 | 000034: 20 00 | local.get 0
113 | 000036: a7 | i32.wrap_i64
114 | 000037: 10 00 | call 0
115 | 000039: 0b | end
116 | """
117 | ),
118 | (line: #line,
119 | wat: """
120 | (module
121 | (import "foo" "bar" (func (param i64) (param i32) (param i64)))
122 | (func (call 0 (i64.const 0) (i32.const 0) (i64.const 0)))
123 | )
124 | """,
125 | expectedTypes: """
126 | Type[3]:
127 | - type[0] (i64, i32, i64) -> nil
128 | - type[1] () -> nil
129 | - type[2] (i32, i32, i32) -> nil
130 | """,
131 | expectedImport: "func[0] sig=2 <- foo.bar",
132 | expectedCode: """
133 | 000030 func[1]:
134 | 000031: 42 00 | i64.const 0
135 | 000033: 41 00 | i32.const 0
136 | 000035: 42 00 | i64.const 0
137 | 000037: 10 02 | call 2
138 | 000039: 0b | end
139 | 00003b func[2]:
140 | 00003c: 20 00 | local.get 0
141 | 00003e: a7 | i32.wrap_i64
142 | 00003f: 20 01 | local.get 1
143 | 000041: 20 02 | local.get 2
144 | 000043: a7 | i32.wrap_i64
145 | 000044: 10 00 | call 0
146 | 000046: 0b | end
147 | """
148 | ),
149 | ]
150 |
151 | for testCase in testCases {
152 | let url = try transformWat(testCase.wat)
153 | let summary = wasmObjdump(url, args: ["--details"])
154 | if let expectedTypes = testCase.expectedTypes {
155 | XCTAssertTrue(summary.contains(expectedTypes), "\(summary) doesn't contains \(expectedTypes)", line: testCase.line)
156 | }
157 | if let expectedImport = testCase.expectedImport {
158 | XCTAssertTrue(summary.contains(expectedImport), "\(summary) doesn't contains \(expectedImport)", line: testCase.line)
159 | }
160 | let disassemble = wasmObjdump(url, args: ["--disassemble"])
161 | if let expectedCode = testCase.expectedCode {
162 | XCTAssertTrue(disassemble.contains(expectedCode), "\(disassemble) doesn't contains \(expectedCode)", line: testCase.line)
163 | }
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/Tests/WasmTransformerTests/InputByteStreamTests.swift:
--------------------------------------------------------------------------------
1 | @testable import WasmTransformer
2 | import XCTest
3 |
4 | final class InputByteStreamTests: XCTestCase {
5 | let buildPath = URL(fileURLWithPath: #filePath)
6 | .deletingLastPathComponent()
7 | .deletingLastPathComponent()
8 | .deletingLastPathComponent()
9 | .appendingPathComponent("Fixtures/build")
10 |
11 | func testReadCallInst() {
12 | measureMetrics([.wallClockTime], automaticallyStartMeasuring: false) {
13 | var input = try! InputByteStream(from: buildPath.appendingPathComponent("I64ImportTransformerTests.wasm"))
14 | _ = try! input.readHeader()
15 | readUntilCode: while !input.isEOF {
16 | let sectionInfo = try! input.readSectionInfo()
17 |
18 | switch sectionInfo.type {
19 | case .code:
20 | break readUntilCode
21 | default:
22 | input.skip(sectionInfo.size)
23 | }
24 | }
25 |
26 | let count = Int(input.readVarUInt32())
27 | self.startMeasuring()
28 | for _ in 0 ..< count {
29 | let oldSize = Int(input.readVarUInt32())
30 | let bodyEnd = input.offset + oldSize
31 | var bodyBuffer: [UInt8] = []
32 | bodyBuffer.reserveCapacity(oldSize)
33 |
34 | try! input.consumeLocals(consumer: {
35 | bodyBuffer.append(contentsOf: $0)
36 | })
37 |
38 | while input.offset < bodyEnd {
39 | _ = try! input.readCallInst()
40 | }
41 | }
42 | self.stopMeasuring()
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Tests/WasmTransformerTests/IntegrationTests.swift:
--------------------------------------------------------------------------------
1 | @testable import WasmTransformer
2 | import XCTest
3 |
4 | class IntegrationTests: XCTestCase {
5 | func testSwiftFoundationDate() throws {
6 | let binaryPath = URL(fileURLWithPath: #filePath)
7 | .deletingLastPathComponent()
8 | .deletingLastPathComponent()
9 | .deletingLastPathComponent()
10 | .appendingPathComponent("Fixtures/build/I64ImportTransformerTests.wasm")
11 |
12 | let transformer = I64ImportTransformer()
13 | var inputStream = try InputByteStream(from: binaryPath)
14 | var writer = InMemoryOutputWriter()
15 | try transformer.transform(&inputStream, writer: &writer)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Tests/WasmTransformerTests/SectionsInfoTests.swift:
--------------------------------------------------------------------------------
1 | @testable import WasmTransformer
2 | import XCTest
3 |
4 | final class SectionsInfoTests: XCTestCase {
5 | func testSectionsInfo() throws {
6 | let wat = """
7 | (module
8 | (func $add (result i32)
9 | (i32.add
10 | (i32.const 1)
11 | (i32.const 1)
12 | )
13 | )
14 | )
15 | """
16 |
17 | let binaryURL = compileWat(wat, options: ["--debug-names"])
18 | var input = try InputByteStream(bytes: [UInt8](Data(contentsOf: binaryURL)))
19 | try XCTAssertEqual(
20 | input.readSectionsInfo(),
21 | [
22 | .init(startOffset: 8, contentStart: 10, type: .type, size: 5),
23 | .init(startOffset: 15, contentStart: 17, type: .function, size: 2),
24 | .init(startOffset: 19, contentStart: 21, type: .code, size: 9),
25 | .init(startOffset: 30, contentStart: 32, type: .custom, size: 18),
26 | ]
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Tests/WasmTransformerTests/StackOverflowSanitizerTests.swift:
--------------------------------------------------------------------------------
1 | @testable import WasmTransformer
2 | import XCTest
3 |
4 | final class StackOverflowSanitizerTests: XCTestCase {
5 |
6 | func testTransformFunction() throws {
7 | let wat = """
8 | (module
9 | (import "__stack_sanitizer" "report_stack_overflow" (func))
10 | (global (mut i32) (i32.const 0))
11 | (func $bar
12 | call $foo
13 | )
14 | (func $foo)
15 | (func $llvm_func
16 | i32.const 0
17 | global.set 0
18 | )
19 | )
20 | """
21 |
22 | let binaryURL = compileWat(wat)
23 | var input = try InputByteStream(bytes: [UInt8](Data(contentsOf: binaryURL)))
24 |
25 | var writer = InMemoryOutputWriter()
26 | let transformer = StackOverflowSanitizer()
27 | try transformer.transform(&input, writer: &writer)
28 | let (url, handle) = makeTemporaryFile()
29 | handle.write(Data(writer.bytes()))
30 | let disassemble = wasmObjdump(url, args: ["--disassemble"])
31 | let expected = """
32 | 00005b func[3]:
33 | 00005c: 41 00 | i32.const 0
34 | 00005e: 10 04 | call 4
35 | 000060: 24 00 | global.set 0
36 | 000062: 0b | end
37 | 000064 func[4]:
38 | 000065: 20 00 | local.get 0
39 | 000067: 41 00 | i32.const 0
40 | 000069: 48 | i32.lt_s
41 | 00006a: 04 40 | if
42 | 00006c: 10 00 | call 0 <__stack_sanitizer.report_stack_overflow>
43 | 00006e: 0b | end
44 | 00006f: 20 00 | local.get 0
45 | 000071: 0b | end
46 | """
47 | XCTAssertContains(disassemble, contains: expected)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Tests/WasmTransformerTests/misc.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | func findExecutable(_ name: String) -> String {
4 | guard let pathsString = ProcessInfo.processInfo.environment["PATH"] else {
5 | fatalError("PATH is not set in environment")
6 | }
7 | let paths = pathsString.split(separator: ":")
8 | for path in paths {
9 | let candidate = URL(fileURLWithPath: String(path)).appendingPathComponent(name)
10 | if FileManager.default.isExecutableFile(atPath: candidate.path) {
11 | return candidate.path
12 | }
13 | }
14 | fatalError("Command '\(name)' not found in PATH")
15 | }
16 |
17 | @discardableResult
18 | func exec(_ launchPath: String, _ arguments: [String]) -> String? {
19 | let process = Process()
20 | process.launchPath = launchPath
21 | process.arguments = arguments
22 | let stdoutPipe = Pipe()
23 | process.standardOutput = stdoutPipe
24 | process.launch()
25 | process.waitUntilExit()
26 | assert(process.terminationStatus == 0)
27 | let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()
28 | guard let stdout = String(data: stdoutData, encoding: .utf8) else {
29 | return nil
30 | }
31 | return stdout
32 | }
33 |
34 | func makeTemporaryFile() -> (URL, FileHandle) {
35 | let tempdir = URL(fileURLWithPath: NSTemporaryDirectory())
36 | let templatePath = tempdir.appendingPathComponent("wasm-transformer.XXXXXX")
37 | var template = [UInt8](templatePath.path.utf8).map { Int8($0) } + [Int8(0)]
38 | let fd = mkstemp(&template)
39 | if fd == -1 {
40 | fatalError("Failed to create temp directory")
41 | }
42 | let url = URL(fileURLWithPath: String(cString: template))
43 | let handle = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
44 | return (url, handle)
45 | }
46 |
47 | func createFile(_ content: String) -> URL {
48 | let (url, handle) = makeTemporaryFile()
49 | handle.write(content.data(using: .utf8)!)
50 | return url
51 | }
52 |
53 | func compileWat(_ content: String, options: [String] = []) -> URL {
54 | let module = createFile(content)
55 | let (output, _) = makeTemporaryFile()
56 | exec(findExecutable("wat2wasm"), [module.path, "-o", output.path] + options)
57 | return output
58 | }
59 |
60 | func wasmObjdump(_ input: URL, args: [String]) -> String {
61 | exec(findExecutable("wasm-objdump"), [input.path] + args)!
62 | }
63 |
64 |
65 | @testable import WasmTransformer
66 |
67 | extension WasmTransformer.InputByteStream {
68 | init(from url: URL) throws {
69 | let bytes = try Array(Data(contentsOf: url))
70 | self.init(bytes: bytes)
71 | }
72 | }
73 |
74 | import XCTest
75 |
76 | func XCTAssertContains(_ value: String, contains: String, file: StaticString = #file, line: UInt = #line) {
77 | XCTAssertTrue(value.contains(contains), "'\(value)'\n does not contain \n'\(contains)'", file: file, line: line)
78 | }
79 |
--------------------------------------------------------------------------------
/Tools/GenerateStackOverflowSanitizerSupport.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | guard CommandLine.arguments.count == 2 else {
4 | print("Usage: \(CommandLine.arguments[0]) ")
5 | exit(1)
6 | }
7 |
8 | let objectFile = try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[1]))
9 |
10 | let sourceCode = """
11 | // GENERATED FROM Tools/GenerateStackOverflowSanitizerSupport.swift
12 |
13 | extension StackOverflowSanitizer {
14 | public static let supportObjectFile: [UInt8] = [
15 | \(objectFile.map { String(format: "0x%02x", $0) }.joined(separator: ", "))
16 | ]
17 | }
18 | """
19 |
20 | print(sourceCode)
21 |
--------------------------------------------------------------------------------