mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
WIP objdiff 3.0 refactor
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "objdiff-wasm"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
readme = "../README.md"
|
||||
description = """
|
||||
A local diffing tool for decompilation projects.
|
||||
"""
|
||||
publish = false
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["objdiff-core/std"]
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4", default-features = false }
|
||||
regex = { version = "1.11", default-features = false }
|
||||
|
||||
[dependencies.objdiff-core]
|
||||
path = "../objdiff-core"
|
||||
default-features = false
|
||||
features = ["arm", "arm64", "mips", "ppc", "dwarf"]
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
talc = { version = "4.4", default-features = false, features = ["lock_api"] }
|
||||
|
||||
[target.'cfg(target_os = "wasi")'.dependencies]
|
||||
wit-bindgen = { version = "0.39", default-features = false, features = ["macros"] }
|
||||
|
||||
[build-dependencies]
|
||||
wit-deps = "0.5"
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"formatter": {
|
||||
"indentStyle": "space"
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "single"
|
||||
}
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"a11y": {
|
||||
"all": false
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
wit_deps::lock_sync!()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default [
|
||||
{files: ["**/*.{js,mjs,cjs,ts}"]},
|
||||
{languageOptions: {globals: globals.browser}},
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
rules: {
|
||||
"semi": [2, "always"],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
// https://typescript-eslint.io/rules/no-unused-vars/#benefits-over-typescript
|
||||
{
|
||||
"args": "all",
|
||||
"argsIgnorePattern": "^_",
|
||||
"caughtErrors": "all",
|
||||
"caughtErrorsIgnorePattern": "^_",
|
||||
"destructuredArrayIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { WasiLoggingLogging010Draft as logging } from '../pkg/objdiff';
|
||||
|
||||
export const log: typeof logging.log = (level, context, message) => {
|
||||
const msg = `[${context}] ${message}`;
|
||||
switch (level) {
|
||||
case 'trace':
|
||||
console.trace(msg);
|
||||
break;
|
||||
case 'debug':
|
||||
console.debug(msg);
|
||||
break;
|
||||
case 'info':
|
||||
console.info(msg);
|
||||
break;
|
||||
case 'warn':
|
||||
console.warn(msg);
|
||||
break;
|
||||
case 'error':
|
||||
console.error(msg);
|
||||
break;
|
||||
case 'critical':
|
||||
console.error(msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
Generated
+1957
-2625
File diff suppressed because it is too large
Load Diff
+12
-21
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "objdiff-wasm",
|
||||
"version": "2.7.1",
|
||||
"version": "3.0.0-alpha.1",
|
||||
"description": "A local diffing tool for decompilation projects.",
|
||||
"author": {
|
||||
"name": "Luke Street",
|
||||
@@ -12,28 +12,19 @@
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/encounter/objdiff.git"
|
||||
},
|
||||
"files": [
|
||||
"dist/*"
|
||||
],
|
||||
"main": "dist/main.js",
|
||||
"types": "dist/main.d.ts",
|
||||
"files": ["dist/*"],
|
||||
"main": "dist/objdiff.js",
|
||||
"types": "dist/objdiff.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"build:all": "npm run build:wasm && npm run build:proto && npm run build",
|
||||
"build:proto": "protoc --ts_out=gen --ts_opt add_pb_suffix,eslint_disable,ts_nocheck,use_proto_field_name --proto_path=../objdiff-core/protos ../objdiff-core/protos/*.proto",
|
||||
"build:wasm": "cd ../objdiff-core && wasm-pack build --out-dir ../objdiff-wasm/pkg --target web -- --features arm,arm64,dwarf,config,ppc,x86,wasm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@protobuf-ts/runtime": "^2.9.4"
|
||||
"build": "npm run build:wasm && npm run build:transpile && npm run build:lib",
|
||||
"build:wasm": "cargo +nightly -Zbuild-std=panic_abort,core,alloc -Zbuild-std-features=compiler-builtins-mem build --target wasm32-wasip2 --release --no-default-features",
|
||||
"build:transpile": "jco transpile ../target/wasm32-wasip2/release/objdiff_wasm.wasm --no-nodejs-compat --no-wasi-shim --no-namespaced-exports --map wasi:logging/logging=./wasi-logging.js -o pkg --name objdiff",
|
||||
"build:lib": "rslib build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
"@protobuf-ts/plugin": "^2.9.4",
|
||||
"@types/node": "^22.4.1",
|
||||
"esbuild": "^0.23.1",
|
||||
"eslint": "^9.9.0",
|
||||
"globals": "^15.9.0",
|
||||
"tsup": "^8.2.4",
|
||||
"typescript-eslint": "^8.2.0"
|
||||
"@biomejs/biome": "^1.9.3",
|
||||
"@bytecodealliance/jco": "^1.9.1",
|
||||
"@rslib/core": "^0.4.1",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from '@rslib/core';
|
||||
|
||||
export default defineConfig({
|
||||
source: {
|
||||
entry: {
|
||||
'wasi-logging': 'lib/wasi-logging.ts',
|
||||
},
|
||||
},
|
||||
lib: [
|
||||
{
|
||||
format: 'esm',
|
||||
syntax: 'es2022',
|
||||
},
|
||||
],
|
||||
output: {
|
||||
target: 'web',
|
||||
copy: [{ from: 'pkg' }, { from: '../objdiff-core/config-schema.json' }],
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,321 @@
|
||||
use alloc::{
|
||||
format,
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::cell::RefCell;
|
||||
|
||||
use objdiff_core::{diff, obj};
|
||||
use regex::RegexBuilder;
|
||||
|
||||
use super::logging;
|
||||
|
||||
wit_bindgen::generate!({
|
||||
world: "api",
|
||||
with: {
|
||||
"wasi:logging/logging@0.1.0-draft": logging::wasi_logging,
|
||||
},
|
||||
});
|
||||
|
||||
use exports::objdiff::core::{
|
||||
diff::Guest as GuestDiff,
|
||||
diff_types::{
|
||||
DiffConfigBorrow, DiffResult, Guest as GuestDiffTypes, GuestDiffConfig, GuestObject,
|
||||
GuestObjectDiff, Object, ObjectBorrow, ObjectDiff, ObjectDiffBorrow,
|
||||
},
|
||||
display_types::{
|
||||
ContextMenuItem, DiffText, DiffTextOpcode, DiffTextSegment, DiffTextSymbol, DisplayConfig,
|
||||
HoverItem, InstructionDiffKind, InstructionDiffRow, SectionDisplay, SectionDisplaySymbol,
|
||||
SymbolDisplay, SymbolFilter, SymbolFlags, SymbolKind, SymbolRef,
|
||||
},
|
||||
};
|
||||
|
||||
struct Component;
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(level: logging::wasi_logging::Level) { logging::init(level); }
|
||||
|
||||
fn version() -> String { env!("CARGO_PKG_VERSION").to_string() }
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
struct ResourceObject(Rc<obj::Object>);
|
||||
|
||||
struct ResourceObjectDiff(Rc<obj::Object>, diff::ObjectDiff);
|
||||
|
||||
#[repr(transparent)]
|
||||
struct ResourceDiffConfig(RefCell<diff::DiffObjConfig>);
|
||||
|
||||
impl GuestDiffTypes for Component {
|
||||
type DiffConfig = ResourceDiffConfig;
|
||||
type Object = ResourceObject;
|
||||
type ObjectDiff = ResourceObjectDiff;
|
||||
}
|
||||
|
||||
impl GuestDiff for Component {
|
||||
fn run_diff(
|
||||
left: Option<ObjectBorrow>,
|
||||
right: Option<ObjectBorrow>,
|
||||
diff_config: DiffConfigBorrow,
|
||||
) -> Result<DiffResult, String> {
|
||||
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||
log::debug!("Running diff with config: {:?}", diff_config);
|
||||
let result = diff::diff_objs(
|
||||
left.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()),
|
||||
right.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()),
|
||||
None,
|
||||
&diff_config,
|
||||
&diff::MappingConfig::default(),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(DiffResult {
|
||||
left: result.left.map(|d| {
|
||||
ObjectDiff::new(ResourceObjectDiff(
|
||||
left.unwrap().get::<ResourceObject>().0.clone(),
|
||||
d,
|
||||
))
|
||||
}),
|
||||
right: result.right.map(|d| {
|
||||
ObjectDiff::new(ResourceObjectDiff(
|
||||
right.unwrap().get::<ResourceObject>().0.clone(),
|
||||
d,
|
||||
))
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn symbol_context(_obj: ObjectBorrow, _symbol: SymbolRef) -> Vec<ContextMenuItem> { todo!() }
|
||||
|
||||
fn symbol_hover(_obj: ObjectBorrow, _symbol: SymbolRef) -> Vec<HoverItem> { todo!() }
|
||||
|
||||
fn display_sections(
|
||||
diff: ObjectDiffBorrow,
|
||||
filter: SymbolFilter,
|
||||
config: DisplayConfig,
|
||||
) -> Vec<SectionDisplay> {
|
||||
let regex = filter.regex.as_ref().and_then(|s| {
|
||||
RegexBuilder::new(s).case_insensitive(true).build().ok().or_else(|| {
|
||||
// Use the string as a literal if the regex fails to compile
|
||||
let escaped = regex::escape(s);
|
||||
RegexBuilder::new(&escaped).case_insensitive(true).build().ok()
|
||||
})
|
||||
});
|
||||
let filter = if let Some(mapping) = filter.mapping {
|
||||
diff::display::SymbolFilter::Mapping(mapping as usize, regex.as_ref())
|
||||
} else if let Some(regex) = ®ex {
|
||||
diff::display::SymbolFilter::Search(regex)
|
||||
} else {
|
||||
diff::display::SymbolFilter::None
|
||||
};
|
||||
let obj_diff = diff.get::<ResourceObjectDiff>();
|
||||
diff::display::display_sections(
|
||||
obj_diff.0.as_ref(),
|
||||
&obj_diff.1,
|
||||
filter,
|
||||
config.show_hidden_symbols,
|
||||
config.show_mapped_symbols,
|
||||
config.reverse_fn_order,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|d| SectionDisplay {
|
||||
id: d.id,
|
||||
name: d.name,
|
||||
size: d.size,
|
||||
match_percent: d.match_percent,
|
||||
symbols: d
|
||||
.symbols
|
||||
.into_iter()
|
||||
.map(|s| SectionDisplaySymbol {
|
||||
symbol: s.symbol as SymbolRef,
|
||||
is_mapping_symbol: s.is_mapping_symbol,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn display_symbol(
|
||||
diff: ObjectDiffBorrow,
|
||||
symbol_display: SectionDisplaySymbol,
|
||||
) -> SymbolDisplay {
|
||||
let obj_diff = diff.get::<ResourceObjectDiff>();
|
||||
let obj = obj_diff.0.as_ref();
|
||||
let obj_diff = &obj_diff.1;
|
||||
let symbol_idx = symbol_display.symbol as usize;
|
||||
let symbol = &obj.symbols[symbol_idx];
|
||||
let symbol_diff = if symbol_display.is_mapping_symbol {
|
||||
obj_diff
|
||||
.mapping_symbols
|
||||
.iter()
|
||||
.find(|s| s.symbol_index == symbol_idx)
|
||||
.map(|s| &s.symbol_diff)
|
||||
.unwrap()
|
||||
} else {
|
||||
&obj_diff.symbols[symbol_idx]
|
||||
};
|
||||
SymbolDisplay {
|
||||
name: symbol.name.clone(),
|
||||
demangled_name: symbol.demangled_name.clone(),
|
||||
address: symbol.address,
|
||||
size: symbol.size,
|
||||
kind: SymbolKind::from(symbol.kind),
|
||||
section: symbol.section.map(|s| s as u32),
|
||||
flags: SymbolFlags::from(symbol.flags),
|
||||
align: symbol.align.map(|a| a.get()),
|
||||
virtual_address: symbol.virtual_address,
|
||||
target_symbol: symbol_diff.target_symbol.map(|s| s as u32),
|
||||
match_percent: symbol_diff.match_percent,
|
||||
diff_score: symbol_diff.diff_score,
|
||||
row_count: symbol_diff.instruction_rows.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
fn display_instruction_row(
|
||||
diff: ObjectDiffBorrow,
|
||||
symbol_display: SectionDisplaySymbol,
|
||||
row_index: u32,
|
||||
diff_config: DiffConfigBorrow,
|
||||
) -> InstructionDiffRow {
|
||||
let mut segments = Vec::with_capacity(16);
|
||||
let obj_diff = diff.get::<ResourceObjectDiff>();
|
||||
let obj = obj_diff.0.as_ref();
|
||||
let obj_diff = &obj_diff.1;
|
||||
let symbol_idx = symbol_display.symbol as usize;
|
||||
let symbol_diff = if symbol_display.is_mapping_symbol {
|
||||
obj_diff
|
||||
.mapping_symbols
|
||||
.iter()
|
||||
.find(|s| s.symbol_index == symbol_idx)
|
||||
.map(|s| &s.symbol_diff)
|
||||
.unwrap()
|
||||
} else {
|
||||
&obj_diff.symbols[symbol_idx]
|
||||
};
|
||||
let row = &symbol_diff.instruction_rows[row_index as usize];
|
||||
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||
diff::display::display_row(obj, symbol_idx, row, &diff_config, |text, idx| {
|
||||
segments.push(DiffTextSegment { text: DiffText::from(text), diff_index: idx.get() });
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
InstructionDiffRow { segments, diff_kind: InstructionDiffKind::from(row.kind) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<obj::SymbolKind> for SymbolKind {
|
||||
fn from(kind: obj::SymbolKind) -> Self {
|
||||
match kind {
|
||||
obj::SymbolKind::Unknown => SymbolKind::Unknown,
|
||||
obj::SymbolKind::Function => SymbolKind::Function,
|
||||
obj::SymbolKind::Object => SymbolKind::Object,
|
||||
obj::SymbolKind::Section => SymbolKind::Section,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<obj::SymbolFlagSet> for SymbolFlags {
|
||||
fn from(flags: obj::SymbolFlagSet) -> SymbolFlags {
|
||||
let mut out = SymbolFlags::empty();
|
||||
for flag in flags {
|
||||
out |= match flag {
|
||||
obj::SymbolFlag::Global => SymbolFlags::GLOBAL,
|
||||
obj::SymbolFlag::Local => SymbolFlags::LOCAL,
|
||||
obj::SymbolFlag::Weak => SymbolFlags::WEAK,
|
||||
obj::SymbolFlag::Common => SymbolFlags::COMMON,
|
||||
obj::SymbolFlag::Hidden => SymbolFlags::HIDDEN,
|
||||
obj::SymbolFlag::HasExtra => SymbolFlags::HAS_EXTRA,
|
||||
obj::SymbolFlag::SizeInferred => SymbolFlags::SIZE_INFERRED,
|
||||
};
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl From<diff::display::DiffText<'_>> for DiffText {
|
||||
fn from(text: diff::display::DiffText) -> Self {
|
||||
match text {
|
||||
diff::display::DiffText::Basic(v) => DiffText::Basic(v.to_string()),
|
||||
diff::display::DiffText::Line(v) => DiffText::Line(v),
|
||||
diff::display::DiffText::Address(v) => DiffText::Address(v),
|
||||
diff::display::DiffText::Opcode(n, op) => {
|
||||
DiffText::Opcode(DiffTextOpcode { mnemonic: n.to_string(), opcode: op })
|
||||
}
|
||||
diff::display::DiffText::Argument(s) => match s {
|
||||
obj::InstructionArgValue::Signed(v) => DiffText::Signed(*v),
|
||||
obj::InstructionArgValue::Unsigned(v) => DiffText::Unsigned(*v),
|
||||
obj::InstructionArgValue::Opaque(v) => DiffText::Opaque(v.to_string()),
|
||||
},
|
||||
diff::display::DiffText::BranchDest(v) => DiffText::BranchDest(v),
|
||||
diff::display::DiffText::Symbol(s) => DiffText::Symbol(DiffTextSymbol {
|
||||
name: s.name.clone(),
|
||||
demangled_name: s.demangled_name.clone(),
|
||||
}),
|
||||
diff::display::DiffText::Addend(v) => DiffText::Addend(v),
|
||||
diff::display::DiffText::Spacing(v) => DiffText::Spacing(v as u32),
|
||||
diff::display::DiffText::Eol => DiffText::Eol,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<diff::InstructionDiffKind> for InstructionDiffKind {
|
||||
fn from(kind: diff::InstructionDiffKind) -> Self {
|
||||
match kind {
|
||||
diff::InstructionDiffKind::None => InstructionDiffKind::None,
|
||||
diff::InstructionDiffKind::OpMismatch => InstructionDiffKind::OpMismatch,
|
||||
diff::InstructionDiffKind::ArgMismatch => InstructionDiffKind::ArgMismatch,
|
||||
diff::InstructionDiffKind::Replace => InstructionDiffKind::Replace,
|
||||
diff::InstructionDiffKind::Insert => InstructionDiffKind::Insert,
|
||||
diff::InstructionDiffKind::Delete => InstructionDiffKind::Delete,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestDiffConfig for ResourceDiffConfig {
|
||||
fn new() -> Self { Self(RefCell::new(diff::DiffObjConfig::default())) }
|
||||
|
||||
fn set_property(&self, key: String, value: String) -> Result<(), String> {
|
||||
let id = diff::ConfigPropertyId::from_str(&key)
|
||||
.map_err(|_| format!("Invalid property key {:?}", key))?;
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.set_property_value_str(id, &value)
|
||||
.map_err(|_| format!("Invalid property value {:?}", value))
|
||||
}
|
||||
|
||||
fn get_property(&self, key: String) -> Result<String, String> {
|
||||
let id = diff::ConfigPropertyId::from_str(&key)
|
||||
.map_err(|_| format!("Invalid property key {:?}", key))?;
|
||||
Ok(self.0.borrow().get_property_value(id).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestObject for ResourceObject {
|
||||
fn parse(data: Vec<u8>, diff_config: DiffConfigBorrow) -> Result<Object, String> {
|
||||
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||
obj::read::parse(&data, &diff_config)
|
||||
.map(|o| Object::new(ResourceObject(Rc::new(o))))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestObjectDiff for ResourceObjectDiff {
|
||||
fn find_symbol(&self, name: String, section_name: Option<String>) -> Option<SymbolRef> {
|
||||
let obj = self.0.as_ref();
|
||||
obj.symbols
|
||||
.iter()
|
||||
.position(|s| {
|
||||
s.name == name
|
||||
&& match section_name.as_deref() {
|
||||
Some(section_name) => {
|
||||
s.section.is_some_and(|n| obj.sections[n].name == section_name)
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
})
|
||||
.map(|i| i as SymbolRef)
|
||||
}
|
||||
}
|
||||
|
||||
export!(Component);
|
||||
@@ -0,0 +1,64 @@
|
||||
//! This module contains a canonical definition of the `cabi_realloc` function
|
||||
//! for the component model.
|
||||
//!
|
||||
//! The component model's canonical ABI for representing datatypes in memory
|
||||
//! makes use of this function when transferring lists and strings, for example.
|
||||
//! This function behaves like C's `realloc` but also takes alignment into
|
||||
//! account.
|
||||
//!
|
||||
//! Components are notably not required to export this function, but nearly
|
||||
//! all components end up doing so currently. This definition in the standard
|
||||
//! library removes the need for all compilations to define this themselves.
|
||||
//!
|
||||
//! More information about the canonical ABI can be found at
|
||||
//! <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md>
|
||||
//!
|
||||
//! Note that the name of this function is not standardized in the canonical ABI
|
||||
//! at this time. Instead it's a convention of the "componentization process"
|
||||
//! where a core wasm module is converted to a component to use this name.
|
||||
//! Additionally this is not the only possible definition of this function, so
|
||||
//! this is defined as a "weak" symbol. This means that other definitions are
|
||||
//! allowed to overwrite it if they are present in a compilation.
|
||||
|
||||
use alloc::{alloc, Layout};
|
||||
use core::ptr;
|
||||
|
||||
#[used]
|
||||
static FORCE_CODEGEN_OF_CABI_REALLOC: unsafe extern "C" fn(
|
||||
*mut u8,
|
||||
usize,
|
||||
usize,
|
||||
usize,
|
||||
) -> *mut u8 = cabi_realloc;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cabi_realloc(
|
||||
old_ptr: *mut u8,
|
||||
old_len: usize,
|
||||
align: usize,
|
||||
new_len: usize,
|
||||
) -> *mut u8 {
|
||||
let layout;
|
||||
let ptr = if old_len == 0 {
|
||||
if new_len == 0 {
|
||||
return ptr::without_provenance_mut(align);
|
||||
}
|
||||
layout = Layout::from_size_align_unchecked(new_len, align);
|
||||
alloc::alloc(layout)
|
||||
} else {
|
||||
debug_assert_ne!(new_len, 0, "non-zero old_len requires non-zero new_len!");
|
||||
layout = Layout::from_size_align_unchecked(old_len, align);
|
||||
alloc::realloc(old_ptr, layout, new_len)
|
||||
};
|
||||
if ptr.is_null() {
|
||||
// Print a nice message in debug mode, but in release mode don't
|
||||
// pull in so many dependencies related to printing so just emit an
|
||||
// `unreachable` instruction.
|
||||
if cfg!(debug_assertions) {
|
||||
alloc::handle_alloc_error(layout);
|
||||
} else {
|
||||
core::unreachable!("allocation failed")
|
||||
}
|
||||
}
|
||||
ptr
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import {ArgumentValue, InstructionDiff, RelocationTarget} from "../gen/diff_pb";
|
||||
|
||||
export type DiffText =
|
||||
DiffTextBasic
|
||||
| DiffTextBasicColor
|
||||
| DiffTextAddress
|
||||
| DiffTextLine
|
||||
| DiffTextOpcode
|
||||
| DiffTextArgument
|
||||
| DiffTextSymbol
|
||||
| DiffTextBranchDest
|
||||
| DiffTextSpacing;
|
||||
|
||||
type DiffTextBase = {
|
||||
diff_index?: number,
|
||||
};
|
||||
export type DiffTextBasic = DiffTextBase & {
|
||||
type: 'basic',
|
||||
text: string,
|
||||
};
|
||||
export type DiffTextBasicColor = DiffTextBase & {
|
||||
type: 'basic_color',
|
||||
text: string,
|
||||
index: number,
|
||||
};
|
||||
export type DiffTextAddress = DiffTextBase & {
|
||||
type: 'address',
|
||||
address: bigint,
|
||||
};
|
||||
export type DiffTextLine = DiffTextBase & {
|
||||
type: 'line',
|
||||
line_number: number,
|
||||
};
|
||||
export type DiffTextOpcode = DiffTextBase & {
|
||||
type: 'opcode',
|
||||
mnemonic: string,
|
||||
opcode: number,
|
||||
};
|
||||
export type DiffTextArgument = DiffTextBase & {
|
||||
type: 'argument',
|
||||
value: ArgumentValue,
|
||||
};
|
||||
export type DiffTextSymbol = DiffTextBase & {
|
||||
type: 'symbol',
|
||||
target: RelocationTarget,
|
||||
};
|
||||
export type DiffTextBranchDest = DiffTextBase & {
|
||||
type: 'branch_dest',
|
||||
address: bigint,
|
||||
};
|
||||
export type DiffTextSpacing = DiffTextBase & {
|
||||
type: 'spacing',
|
||||
count: number,
|
||||
};
|
||||
|
||||
// Native JavaScript implementation of objdiff_core::diff::display::display_diff
|
||||
export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text: DiffText) => void) {
|
||||
const ins = diff.instruction;
|
||||
if (!ins) {
|
||||
return;
|
||||
}
|
||||
if (ins.line_number != null) {
|
||||
cb({type: 'line', line_number: ins.line_number});
|
||||
}
|
||||
cb({type: 'address', address: ins.address - baseAddr});
|
||||
if (diff.branch_from) {
|
||||
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_from.branch_index});
|
||||
} else {
|
||||
cb({type: 'spacing', count: 4});
|
||||
}
|
||||
cb({type: 'opcode', mnemonic: ins.mnemonic, opcode: ins.opcode});
|
||||
let arg_diff_idx = 0; // non-PlainText argument index
|
||||
for (let i = 0; i < ins.arguments.length; i++) {
|
||||
if (i === 0) {
|
||||
cb({type: 'spacing', count: 1});
|
||||
}
|
||||
const arg = ins.arguments[i].value;
|
||||
let diff_index: number | undefined;
|
||||
if (arg.oneofKind !== 'plain_text') {
|
||||
diff_index = diff.arg_diff[arg_diff_idx]?.diff_index;
|
||||
arg_diff_idx++;
|
||||
}
|
||||
switch (arg.oneofKind) {
|
||||
case "plain_text":
|
||||
cb({type: 'basic', text: arg.plain_text, diff_index});
|
||||
break;
|
||||
case "argument":
|
||||
cb({type: 'argument', value: arg.argument, diff_index});
|
||||
break;
|
||||
case "relocation": {
|
||||
const reloc = ins.relocation!;
|
||||
cb({type: 'symbol', target: reloc.target!, diff_index});
|
||||
break;
|
||||
}
|
||||
case "branch_dest":
|
||||
if (arg.branch_dest < baseAddr) {
|
||||
cb({type: 'basic', text: '<unknown>', diff_index});
|
||||
} else {
|
||||
cb({type: 'branch_dest', address: arg.branch_dest - baseAddr, diff_index});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (diff.branch_to) {
|
||||
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_to.branch_index});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
mod api;
|
||||
#[cfg(target_os = "wasi")]
|
||||
mod logging;
|
||||
|
||||
#[cfg(all(target_os = "wasi", not(feature = "std")))]
|
||||
mod cabi_realloc;
|
||||
|
||||
#[cfg(all(target_family = "wasm", not(feature = "std")))]
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() };
|
||||
@@ -0,0 +1,52 @@
|
||||
wit_bindgen::generate!({
|
||||
world: "imports",
|
||||
path: "wit/deps/logging",
|
||||
});
|
||||
|
||||
use alloc::format;
|
||||
|
||||
pub use wasi::logging::logging as wasi_logging;
|
||||
|
||||
struct WasiLogger;
|
||||
|
||||
impl log::Log for WasiLogger {
|
||||
fn enabled(&self, metadata: &log::Metadata) -> bool { metadata.level() <= log::max_level() }
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
if !self.enabled(record.metadata()) {
|
||||
return;
|
||||
}
|
||||
let level = match record.level() {
|
||||
log::Level::Error => wasi_logging::Level::Error,
|
||||
log::Level::Warn => wasi_logging::Level::Warn,
|
||||
log::Level::Info => wasi_logging::Level::Info,
|
||||
log::Level::Debug => wasi_logging::Level::Debug,
|
||||
log::Level::Trace => wasi_logging::Level::Trace,
|
||||
};
|
||||
wasi_logging::log(level, record.target(), &format!("{}", record.args()));
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
static LOGGER: WasiLogger = WasiLogger;
|
||||
|
||||
pub fn init(level: wasi_logging::Level) {
|
||||
let _ = log::set_logger(&LOGGER);
|
||||
log::set_max_level(match level {
|
||||
wasi_logging::Level::Error => log::LevelFilter::Error,
|
||||
wasi_logging::Level::Warn => log::LevelFilter::Warn,
|
||||
wasi_logging::Level::Info => log::LevelFilter::Info,
|
||||
wasi_logging::Level::Debug => log::LevelFilter::Debug,
|
||||
wasi_logging::Level::Trace => log::LevelFilter::Trace,
|
||||
wasi_logging::Level::Critical => log::LevelFilter::Off,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
use alloc::string::ToString;
|
||||
wasi_logging::log(wasi_logging::Level::Critical, "objdiff_core::panic", &info.to_string());
|
||||
core::arch::wasm32::unreachable();
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import {DiffResult} from "../gen/diff_pb";
|
||||
import type {
|
||||
ConfigProperty,
|
||||
MappingConfig,
|
||||
SymbolMappings,
|
||||
} from '../pkg';
|
||||
import {AnyHandlerData, InMessage, OutMessage} from './worker';
|
||||
|
||||
// Export wasm types
|
||||
export {ConfigProperty, MappingConfig, SymbolMappings};
|
||||
|
||||
// Export protobuf types
|
||||
export * from '../gen/diff_pb';
|
||||
|
||||
// Export display types
|
||||
export * from './display';
|
||||
|
||||
interface PromiseCallbacks<T> {
|
||||
start: number;
|
||||
resolve: (value: T | PromiseLike<T>) => void;
|
||||
reject: (reason?: string) => void;
|
||||
}
|
||||
|
||||
let workerInit = false;
|
||||
let workerCallbacks: PromiseCallbacks<Worker>;
|
||||
const workerReady = new Promise<Worker>((resolve, reject) => {
|
||||
workerCallbacks = {start: performance.now(), resolve, reject};
|
||||
});
|
||||
|
||||
export async function initialize(data?: {
|
||||
workerUrl?: string | URL,
|
||||
wasmUrl?: string | URL, // Relative to worker URL
|
||||
}): Promise<Worker> {
|
||||
if (workerInit) {
|
||||
return workerReady;
|
||||
}
|
||||
workerInit = true;
|
||||
let {workerUrl, wasmUrl} = data || {};
|
||||
if (!workerUrl) {
|
||||
try {
|
||||
// Bundlers will convert this into an asset URL
|
||||
workerUrl = new URL('./worker.js', import.meta.url);
|
||||
} catch (_) {
|
||||
workerUrl = 'worker.js';
|
||||
}
|
||||
}
|
||||
if (!wasmUrl) {
|
||||
try {
|
||||
// Bundlers will convert this into an asset URL
|
||||
wasmUrl = new URL('./objdiff_core_bg.wasm', import.meta.url);
|
||||
} catch (_) {
|
||||
wasmUrl = 'objdiff_core_bg.js';
|
||||
}
|
||||
}
|
||||
const worker = new Worker(workerUrl, {
|
||||
name: 'objdiff',
|
||||
type: 'module',
|
||||
});
|
||||
worker.onmessage = onMessage;
|
||||
worker.onerror = (event) => {
|
||||
console.error("Worker error", event);
|
||||
workerCallbacks.reject("Worker failed to initialize, wrong URL?");
|
||||
};
|
||||
defer<void>({
|
||||
type: 'init',
|
||||
// URL can't be sent directly
|
||||
wasmUrl: wasmUrl.toString(),
|
||||
}, worker).then(() => {
|
||||
workerCallbacks.resolve(worker);
|
||||
}, (e) => {
|
||||
workerCallbacks.reject(e);
|
||||
});
|
||||
return workerReady;
|
||||
}
|
||||
|
||||
let globalMessageId = 0;
|
||||
const messageCallbacks = new Map<number, PromiseCallbacks<never>>();
|
||||
|
||||
function onMessage(event: MessageEvent<OutMessage>) {
|
||||
switch (event.data.type) {
|
||||
case 'result': {
|
||||
const {result, error, messageId} = event.data;
|
||||
const callbacks = messageCallbacks.get(messageId);
|
||||
if (callbacks) {
|
||||
const end = performance.now();
|
||||
console.debug(`Message ${messageId} took ${end - callbacks.start}ms`);
|
||||
messageCallbacks.delete(messageId);
|
||||
if (error != null) {
|
||||
callbacks.reject(error);
|
||||
} else {
|
||||
callbacks.resolve(result as never);
|
||||
}
|
||||
} else {
|
||||
console.warn(`Unknown message ID ${messageId}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function defer<T>(message: AnyHandlerData, worker?: Worker): Promise<T> {
|
||||
worker = worker || await initialize();
|
||||
const messageId = globalMessageId++;
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
messageCallbacks.set(messageId, {start: performance.now(), resolve, reject});
|
||||
});
|
||||
worker.postMessage({
|
||||
...message,
|
||||
messageId
|
||||
} as InMessage);
|
||||
return promise;
|
||||
}
|
||||
|
||||
export async function runDiff(
|
||||
left: Uint8Array | null | undefined,
|
||||
right: Uint8Array | null | undefined,
|
||||
properties?: ConfigProperty[],
|
||||
mappingConfig?: MappingConfig,
|
||||
): Promise<DiffResult> {
|
||||
const data = await defer<Uint8Array>({
|
||||
type: 'run_diff_proto',
|
||||
left,
|
||||
right,
|
||||
properties,
|
||||
mappingConfig,
|
||||
});
|
||||
const parseStart = performance.now();
|
||||
const result = DiffResult.fromBinary(data, {readUnknownField: false});
|
||||
const end = performance.now();
|
||||
console.debug(`Parsing message took ${end - parseStart}ms`);
|
||||
return result;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import wasmInit, * as exports from '../pkg';
|
||||
|
||||
const handlers = {
|
||||
init: init,
|
||||
run_diff_proto: run_diff_proto,
|
||||
} as const;
|
||||
type ExtractData<T> = T extends (arg: infer U) => Promise<unknown> ? U : never;
|
||||
type HandlerData = {
|
||||
[K in keyof typeof handlers]: { type: K } & ExtractData<typeof handlers[K]>;
|
||||
};
|
||||
|
||||
let wasmReady: Promise<void> | null = null;
|
||||
|
||||
async function init({wasmUrl}: { wasmUrl?: string }): Promise<void> {
|
||||
if (wasmReady != null) {
|
||||
throw new Error('Already initialized');
|
||||
}
|
||||
wasmReady = wasmInit({module_or_path: wasmUrl})
|
||||
.then(() => {
|
||||
});
|
||||
return wasmReady;
|
||||
}
|
||||
|
||||
async function initIfNeeded() {
|
||||
if (wasmReady == null) {
|
||||
await init({});
|
||||
}
|
||||
return wasmReady;
|
||||
}
|
||||
|
||||
async function run_diff_proto({left, right, properties, mappingConfig}: {
|
||||
left: Uint8Array | null | undefined,
|
||||
right: Uint8Array | null | undefined,
|
||||
properties?: exports.ConfigProperty[],
|
||||
mappingConfig?: exports.MappingConfig,
|
||||
}): Promise<Uint8Array> {
|
||||
const diffConfig = exports.config_from_properties(properties || []);
|
||||
const leftObj = left ? exports.parse_object(left, diffConfig) : null;
|
||||
const rightObj = right ? exports.parse_object(right, diffConfig) : null;
|
||||
return exports.run_diff(leftObj, rightObj, diffConfig, mappingConfig || {});
|
||||
}
|
||||
|
||||
export type AnyHandlerData = HandlerData[keyof HandlerData];
|
||||
export type InMessage = AnyHandlerData & { messageId: number };
|
||||
|
||||
export type OutMessage = {
|
||||
type: 'result',
|
||||
result: unknown | null,
|
||||
error: string | null,
|
||||
messageId: number,
|
||||
};
|
||||
|
||||
self.onmessage = (event: MessageEvent<InMessage>) => {
|
||||
const data = event.data;
|
||||
const messageId = data?.messageId;
|
||||
(async () => {
|
||||
if (!data) {
|
||||
throw new Error('No data');
|
||||
}
|
||||
const handler = handlers[data.type];
|
||||
if (handler) {
|
||||
if (data.type !== 'init') {
|
||||
await initIfNeeded();
|
||||
}
|
||||
const start = performance.now();
|
||||
const result = await handler(data as never);
|
||||
const end = performance.now();
|
||||
console.debug(`Worker message ${data.messageId} took ${end - start}ms`);
|
||||
let transfer: Transferable[] = [];
|
||||
if (result instanceof Uint8Array) {
|
||||
console.log("Transferring!", result.byteLength);
|
||||
transfer = [result.buffer];
|
||||
} else {
|
||||
console.log("Didn't transfer", typeof result);
|
||||
}
|
||||
self.postMessage({
|
||||
type: 'result',
|
||||
result: result,
|
||||
error: null,
|
||||
messageId,
|
||||
} as OutMessage, {transfer});
|
||||
} else {
|
||||
throw new Error(`No handler for ${data.type}`);
|
||||
}
|
||||
})().catch(error => {
|
||||
self.postMessage({
|
||||
type: 'result',
|
||||
result: null,
|
||||
error: error.toString(),
|
||||
messageId,
|
||||
} as OutMessage);
|
||||
});
|
||||
};
|
||||
@@ -1,9 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"lib": ["DOM", "ES2023"],
|
||||
"target": "ES2022",
|
||||
}
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"include": ["lib"]
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import {defineConfig} from 'tsup';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
export default defineConfig([
|
||||
// Build main library
|
||||
{
|
||||
entry: ['src/main.ts'],
|
||||
clean: true,
|
||||
dts: true,
|
||||
format: 'esm',
|
||||
outDir: 'dist',
|
||||
skipNodeModulesBundle: true,
|
||||
sourcemap: true,
|
||||
splitting: false,
|
||||
target: 'es2022',
|
||||
},
|
||||
// Build web worker
|
||||
{
|
||||
entry: ['src/worker.ts'],
|
||||
clean: true,
|
||||
dts: true,
|
||||
format: 'esm', // type: 'module'
|
||||
minify: true,
|
||||
outDir: 'dist',
|
||||
sourcemap: true,
|
||||
splitting: false,
|
||||
target: 'es2022',
|
||||
// https://github.com/egoist/tsup/issues/278
|
||||
async onSuccess() {
|
||||
await fs.copyFile('pkg/objdiff_core_bg.wasm', 'dist/objdiff_core_bg.wasm');
|
||||
await fs.copyFile('../objdiff-core/config-schema.json', 'dist/config-schema.json');
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -0,0 +1 @@
|
||||
deps/
|
||||
@@ -0,0 +1,4 @@
|
||||
[logging]
|
||||
url = "https://github.com/WebAssembly/wasi-logging/archive/d31c41d0d9eed81aabe02333d0025d42acf3fb75.tar.gz"
|
||||
sha256 = "ad81d8b7f7a8ceb729cf551f1d24586f0de9560a43eea57a9bb031d2175804e1"
|
||||
sha512 = "1687ad9a02ab3e689443e67d1a0605f58fc5dea828d2e4d2c7825c6002714fac9bd4289b1a68b61a37dcca6c3b421f4c8ed4b1e6cc29f6460e0913cf1bf11c04"
|
||||
@@ -0,0 +1 @@
|
||||
logging = "https://github.com/WebAssembly/wasi-logging/archive/d31c41d0d9eed81aabe02333d0025d42acf3fb75.tar.gz"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user