mirror of
https://github.com/encounter/objdiff.git
synced 2025-06-07 15:13:47 +00:00
Make objdiff-core no_std + huge WASM rework
This commit is contained in:
parent
d938988d43
commit
e8de35b78e
320
Cargo.lock
generated
320
Cargo.lock
generated
@ -152,11 +152,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arm-attr"
|
name = "arm-attr"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d0cabd3a7d2dfa96ab3faa7b532a83c5e090061bf6d83197ca2bc91f5afac6c"
|
checksum = "5790583a9ebcc63c55f142a85d85fe63ec0e033c941fcf00a3605f81dada2e32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror 1.0.69",
|
"thiserror 2.0.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -395,15 +395,6 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bimap"
|
|
||||||
version = "0.6.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@ -688,16 +679,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console_log"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
|
|
||||||
dependencies = [
|
|
||||||
"log",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const_format"
|
name = "const_format"
|
||||||
version = "0.2.34"
|
version = "0.2.34"
|
||||||
@ -862,11 +843,10 @@ checksum = "c2e06f9bce634a3c898eb1e5cb949ff63133cbb218af93cc9b38b31d6f3ea285"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cwextab"
|
name = "cwextab"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/encounter/cwextab.git#15c344ac3302c32adbb8777c70f5ce739f432a6b"
|
||||||
checksum = "003567b96ff9d8ac3275831650385891bca370092937be625157778b1e58f755"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror 1.0.69",
|
"thiserror 2.0.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1336,12 +1316,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fallible-iterator"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@ -1632,10 +1606,6 @@ name = "gimli"
|
|||||||
version = "0.31.1"
|
version = "0.31.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||||
dependencies = [
|
|
||||||
"fallible-iterator",
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gl_generator"
|
name = "gl_generator"
|
||||||
@ -2112,6 +2082,12 @@ dependencies = [
|
|||||||
"syn 2.0.96",
|
"syn 2.0.96",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id-arena"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ident_case"
|
name = "ident_case"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -2159,6 +2135,7 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2342,6 +2319,15 @@ name = "lazy_static"
|
|||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
dependencies = [
|
||||||
|
"spin",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
@ -2539,11 +2525,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "msvc-demangler"
|
name = "msvc-demangler"
|
||||||
version = "0.10.1"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4c25a3bb7d880e8eceab4822f3141ad0700d20f025991c1f03bd3d00219a5fc"
|
checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3000,6 +2987,7 @@ dependencies = [
|
|||||||
"time",
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"typed-path",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3008,10 +2996,7 @@ version = "2.7.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arm-attr",
|
"arm-attr",
|
||||||
"bimap",
|
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"console_error_panic_hook",
|
|
||||||
"console_log",
|
|
||||||
"cpp_demangle",
|
"cpp_demangle",
|
||||||
"cwdemangle",
|
"cwdemangle",
|
||||||
"cwextab",
|
"cwextab",
|
||||||
@ -3043,17 +3028,18 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
|
||||||
"shell-escape",
|
"shell-escape",
|
||||||
"similar",
|
"similar",
|
||||||
|
"spin",
|
||||||
"strum",
|
"strum",
|
||||||
"syn 2.0.96",
|
"syn 2.0.96",
|
||||||
|
"talc",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"time",
|
"time",
|
||||||
"tsify-next",
|
"typed-path",
|
||||||
"unarm",
|
"unarm",
|
||||||
"wasm-bindgen",
|
|
||||||
"winapi",
|
"winapi",
|
||||||
|
"wit-bindgen",
|
||||||
"yaxpeax-arch",
|
"yaxpeax-arch",
|
||||||
"yaxpeax-arm",
|
"yaxpeax-arm",
|
||||||
]
|
]
|
||||||
@ -3095,6 +3081,7 @@ dependencies = [
|
|||||||
"time",
|
"time",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"tracing-wasm",
|
"tracing-wasm",
|
||||||
|
"typed-path",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
@ -4097,17 +4084,6 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde-wasm-bindgen"
|
|
||||||
version = "0.6.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
|
||||||
dependencies = [
|
|
||||||
"js-sys",
|
|
||||||
"serde",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.217"
|
version = "1.0.217"
|
||||||
@ -4119,17 +4095,6 @@ dependencies = [
|
|||||||
"syn 2.0.96",
|
"syn 2.0.96",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive_internals"
|
|
||||||
version = "0.29.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.96",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.135"
|
version = "1.0.135"
|
||||||
@ -4174,19 +4139,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_yaml"
|
|
||||||
version = "0.9.34+deprecated"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"itoa",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
"unsafe-libyaml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@ -4246,9 +4198,11 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
version = "2.6.0"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/encounter/similar.git?branch=no_std#20f3537abfefa4cfb8c4b981c8aab99c7b53130d"
|
||||||
checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e"
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
@ -4329,11 +4283,23 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spdx"
|
||||||
|
version = "0.10.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "58b69356da67e2fc1f542c71ea7e654a361a79c938e4424392ecf4fa065d2193"
|
||||||
|
dependencies = [
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spirv"
|
name = "spirv"
|
||||||
@ -4441,6 +4407,15 @@ dependencies = [
|
|||||||
"syn 2.0.96",
|
"syn 2.0.96",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "talc"
|
||||||
|
version = "4.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fcad3be1cfe36eb7d716a04791eba36a197da9d9b6ea1e28e64ac569da3701d"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tap"
|
name = "tap"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -4786,30 +4761,6 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tsify-next"
|
|
||||||
version = "0.5.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2f4a645dca4ee0800f5ab60ce166deba2db6a0315de795a2691e138a3d55d756"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde-wasm-bindgen",
|
|
||||||
"tsify-next-macros",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tsify-next-macros"
|
|
||||||
version = "0.5.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d5c06f8a51d759bb58129e30b2631739e7e1e4579fad1f30ac09a6c88e488a6"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"serde_derive_internals",
|
|
||||||
"syn 2.0.96",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
@ -4825,6 +4776,12 @@ dependencies = [
|
|||||||
"rustc-hash 1.1.0",
|
"rustc-hash 1.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typed-path"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41713888c5ccfd99979fcd1afd47b71652e331b3d4a0e19d30769e80fec76cce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uds_windows"
|
name = "uds_windows"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -4838,9 +4795,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unarm"
|
name = "unarm"
|
||||||
version = "1.6.7"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a34c5159ddf1e715c3144fd31571bd2d9b63ac70ce1df51d7145ade4532d0c78"
|
checksum = "dff0b9c752e29548c4bf614faa49a5f2d222c6333f9f70e3eb5cd84387f6a333"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
@ -4889,12 +4846,6 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unsafe-libyaml"
|
|
||||||
version = "0.2.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -5065,6 +5016,45 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.224.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7249cf8cb0c6b9cb42bce90c0a5feb276fbf963fa385ff3d818ab3d90818ed6"
|
||||||
|
dependencies = [
|
||||||
|
"leb128",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-metadata"
|
||||||
|
version = "0.224.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79d13d93febc749413cb6f327e4fdba8c84e4d03bd69fcc4a220c66f113c8de1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"spdx",
|
||||||
|
"url",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.224.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "65881a664fdd43646b647bb27bf186ab09c05bf56779d40aed4c6dce47d423f5"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.8.0",
|
||||||
|
"hashbrown",
|
||||||
|
"indexmap",
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-backend"
|
name = "wayland-backend"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -5750,6 +5740,104 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.38.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b550e454e4cce8984398539a94a0226511e1f295b14afdc8f08b4e2e2ff9de3a"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
"wit-bindgen-rust-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-core"
|
||||||
|
version = "0.38.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70e2f98d49960a416074c5d72889f810ed3032a32ffef5e4760094426fefbfe8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.38.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed6f8d372a2d4a1227f2556e051cc24b2a5f15768d53451c84ff91e2527139e3"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.8.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust"
|
||||||
|
version = "0.38.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cc49091f84e4f2ace078bbc86082b57e667b9e789baece4b1184e0963382b6e"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"indexmap",
|
||||||
|
"prettyplease",
|
||||||
|
"syn 2.0.96",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-component",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust-macro"
|
||||||
|
version = "0.38.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3545a699dc9d72298b2064ce71b771fc10fc6b757d29306b1e54a4283a75abba"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.96",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-bindgen-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-component"
|
||||||
|
version = "0.224.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad555ab4f4e676474df746d937823c7279c2d6dd36c3e97a61db893d4ef64ee5"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags 2.8.0",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wasmparser",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.224.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23e2925a7365d2c6709ae17bdbb5777ffd8154fd70906b413fc01b75f0dba59e"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "write16"
|
name = "write16"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -5855,9 +5943,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaxpeax-arm"
|
name = "yaxpeax-arm"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1c6a2af41f88546a08df3bc77aadf7263884d6dffdac5b32dea7dc2df23f241"
|
checksum = "1db82aac85bc577d19b6255bf54ad97241c436eeb997ba159f399adacc5fb69e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"yaxpeax-arch",
|
"yaxpeax-arch",
|
||||||
|
@ -28,6 +28,7 @@ supports-color = "3.0"
|
|||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
typed-path = "0.10"
|
||||||
|
|
||||||
[target.'cfg(target_env = "musl")'.dependencies]
|
[target.'cfg(target_env = "musl")'.dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs,
|
|
||||||
io::stdout,
|
io::stdout,
|
||||||
mem,
|
mem,
|
||||||
path::{Path, PathBuf},
|
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
@ -27,7 +25,11 @@ use objdiff_core::{
|
|||||||
watcher::{create_watcher, Watcher},
|
watcher::{create_watcher, Watcher},
|
||||||
BuildConfig,
|
BuildConfig,
|
||||||
},
|
},
|
||||||
config::{build_globset, ProjectConfig, ProjectObject},
|
config::{
|
||||||
|
build_globset,
|
||||||
|
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||||
|
ProjectConfig, ProjectObject, ProjectObjectMetadata,
|
||||||
|
},
|
||||||
diff,
|
diff,
|
||||||
diff::{
|
diff::{
|
||||||
ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig, ObjDiff,
|
ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig, ObjDiff,
|
||||||
@ -40,6 +42,7 @@ use objdiff_core::{
|
|||||||
obj::ObjInfo,
|
obj::ObjInfo,
|
||||||
};
|
};
|
||||||
use ratatui::prelude::*;
|
use ratatui::prelude::*;
|
||||||
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
util::{
|
util::{
|
||||||
@ -53,21 +56,21 @@ use crate::{
|
|||||||
/// Diff two object files. (Interactive or one-shot mode)
|
/// Diff two object files. (Interactive or one-shot mode)
|
||||||
#[argp(subcommand, name = "diff")]
|
#[argp(subcommand, name = "diff")]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[argp(option, short = '1')]
|
#[argp(option, short = '1', from_str_fn(platform_path))]
|
||||||
/// Target object file
|
/// Target object file
|
||||||
target: Option<PathBuf>,
|
target: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = '2')]
|
#[argp(option, short = '2', from_str_fn(platform_path))]
|
||||||
/// Base object file
|
/// Base object file
|
||||||
base: Option<PathBuf>,
|
base: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'p')]
|
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||||
/// Project directory
|
/// Project directory
|
||||||
project: Option<PathBuf>,
|
project: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'u')]
|
#[argp(option, short = 'u')]
|
||||||
/// Unit name within project
|
/// Unit name within project
|
||||||
unit: Option<String>,
|
unit: Option<String>,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file (one-shot mode) ("-" for stdout)
|
/// Output file (one-shot mode) ("-" for stdout)
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option)]
|
#[argp(option)]
|
||||||
/// Output format (json, json-pretty, proto) (default: json)
|
/// Output format (json, json-pretty, proto) (default: json)
|
||||||
format: Option<String>,
|
format: Option<String>,
|
||||||
@ -89,86 +92,61 @@ pub struct Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(args: Args) -> Result<()> {
|
pub fn run(args: Args) -> Result<()> {
|
||||||
let (target_path, base_path, project_config) = match (
|
let (target_path, base_path, project_config) =
|
||||||
&args.target,
|
match (&args.target, &args.base, &args.project, &args.unit) {
|
||||||
&args.base,
|
|
||||||
&args.project,
|
|
||||||
&args.unit,
|
|
||||||
) {
|
|
||||||
(Some(_), Some(_), None, None)
|
(Some(_), Some(_), None, None)
|
||||||
| (Some(_), None, None, None)
|
| (Some(_), None, None, None)
|
||||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
||||||
(None, None, p, u) => {
|
(None, None, p, u) => {
|
||||||
let project = match p {
|
let project = match p {
|
||||||
Some(project) => project.clone(),
|
Some(project) => project.clone(),
|
||||||
_ => std::env::current_dir().context("Failed to get the current directory")?,
|
_ => check_path_buf(
|
||||||
|
std::env::current_dir().context("Failed to get the current directory")?,
|
||||||
|
)
|
||||||
|
.context("Current directory is not valid UTF-8")?,
|
||||||
};
|
};
|
||||||
let Some((project_config, project_config_info)) =
|
let Some((project_config, project_config_info)) =
|
||||||
objdiff_core::config::try_project_config(&project)
|
objdiff_core::config::try_project_config(project.as_ref())
|
||||||
else {
|
else {
|
||||||
bail!("Project config not found in {}", &project.display())
|
bail!("Project config not found in {}", &project)
|
||||||
};
|
};
|
||||||
let mut project_config = project_config.with_context(|| {
|
let project_config = project_config.with_context(|| {
|
||||||
format!("Reading project config {}", project_config_info.path.display())
|
format!("Reading project config {}", project_config_info.path.display())
|
||||||
})?;
|
})?;
|
||||||
let object = {
|
let target_obj_dir = project_config
|
||||||
let resolve_paths = |o: &mut ProjectObject| {
|
.target_dir
|
||||||
o.resolve_paths(
|
.as_ref()
|
||||||
&project,
|
.map(|p| project.join(p.with_platform_encoding()));
|
||||||
project_config.target_dir.as_deref(),
|
let base_obj_dir = project_config
|
||||||
project_config.base_dir.as_deref(),
|
.base_dir
|
||||||
)
|
.as_ref()
|
||||||
};
|
.map(|p| project.join(p.with_platform_encoding()));
|
||||||
if let Some(u) = u {
|
let objects = project_config
|
||||||
let unit_path =
|
|
||||||
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
|
|
||||||
|
|
||||||
let Some(object) = project_config
|
|
||||||
.units
|
.units
|
||||||
.as_deref_mut()
|
.iter()
|
||||||
.unwrap_or_default()
|
.flatten()
|
||||||
.iter_mut()
|
.map(|o| {
|
||||||
.find_map(|obj| {
|
ObjectConfig::new(
|
||||||
if obj.name.as_deref() == Some(u) {
|
o,
|
||||||
resolve_paths(obj);
|
&project,
|
||||||
return Some(obj);
|
target_obj_dir.as_deref(),
|
||||||
}
|
base_obj_dir.as_deref(),
|
||||||
|
)
|
||||||
let up = unit_path.as_deref()?;
|
|
||||||
|
|
||||||
resolve_paths(obj);
|
|
||||||
|
|
||||||
if [&obj.base_path, &obj.target_path]
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok()))
|
|
||||||
.any(|p| p == up)
|
|
||||||
{
|
|
||||||
return Some(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
})
|
})
|
||||||
else {
|
.collect::<Vec<_>>();
|
||||||
bail!("Unit not found: {}", u)
|
let object = if let Some(u) = u {
|
||||||
};
|
objects
|
||||||
|
.iter()
|
||||||
object
|
.find(|obj| obj.name == *u)
|
||||||
|
.ok_or_else(|| anyhow!("Unit not found: {}", u))?
|
||||||
} else if let Some(symbol_name) = &args.symbol {
|
} else if let Some(symbol_name) = &args.symbol {
|
||||||
let mut idx = None;
|
let mut idx = None;
|
||||||
let mut count = 0usize;
|
let mut count = 0usize;
|
||||||
for (i, obj) in project_config
|
for (i, obj) in objects.iter().enumerate() {
|
||||||
.units
|
|
||||||
.as_deref_mut()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.iter_mut()
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
resolve_paths(obj);
|
|
||||||
|
|
||||||
if obj
|
if obj
|
||||||
.target_path
|
.target_path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|o| obj::read::has_function(o, symbol_name))
|
.map(|o| obj::read::has_function(o.as_ref(), symbol_name))
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
@ -181,7 +159,7 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
match (count, idx) {
|
match (count, idx) {
|
||||||
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
||||||
(1, Some(i)) => &mut project_config.units_mut()[i],
|
(1, Some(i)) => &objects[i],
|
||||||
(2.., Some(_)) => bail!(
|
(2.., Some(_)) => bail!(
|
||||||
"Multiple instances of {} were found, try specifying a unit",
|
"Multiple instances of {} were found, try specifying a unit",
|
||||||
symbol_name
|
symbol_name
|
||||||
@ -190,7 +168,6 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let target_path = object.target_path.clone();
|
let target_path = object.target_path.clone();
|
||||||
let base_path = object.base_path.clone();
|
let base_path = object.base_path.clone();
|
||||||
@ -245,20 +222,20 @@ fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)>
|
|||||||
|
|
||||||
fn run_oneshot(
|
fn run_oneshot(
|
||||||
args: &Args,
|
args: &Args,
|
||||||
output: &Path,
|
output: &Utf8PlatformPath,
|
||||||
target_path: Option<&Path>,
|
target_path: Option<&Utf8PlatformPath>,
|
||||||
base_path: Option<&Path>,
|
base_path: Option<&Utf8PlatformPath>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
||||||
let target = target_path
|
let target = target_path
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let base = base_path
|
let base = base_path
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let result =
|
let result =
|
||||||
@ -272,10 +249,10 @@ fn run_oneshot(
|
|||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub jobs: JobQueue,
|
pub jobs: JobQueue,
|
||||||
pub waker: Arc<TermWaker>,
|
pub waker: Arc<TermWaker>,
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||||
pub project_config: Option<ProjectConfig>,
|
pub project_config: Option<ProjectConfig>,
|
||||||
pub target_path: Option<PathBuf>,
|
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub left_obj: Option<(ObjInfo, ObjDiff)>,
|
pub left_obj: Option<(ObjInfo, ObjDiff)>,
|
||||||
pub right_obj: Option<(ObjInfo, ObjDiff)>,
|
pub right_obj: Option<(ObjInfo, ObjDiff)>,
|
||||||
pub prev_obj: Option<(ObjInfo, ObjDiff)>,
|
pub prev_obj: Option<(ObjInfo, ObjDiff)>,
|
||||||
@ -315,6 +292,53 @@ fn create_objdiff_config(state: &AppState) -> ObjDiffConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The configuration for a single object file.
|
||||||
|
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ObjectConfig {
|
||||||
|
pub name: String,
|
||||||
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
|
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
pub metadata: ProjectObjectMetadata,
|
||||||
|
pub complete: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectConfig {
|
||||||
|
pub fn new(
|
||||||
|
object: &ProjectObject,
|
||||||
|
project_dir: &Utf8PlatformPath,
|
||||||
|
target_obj_dir: Option<&Utf8PlatformPath>,
|
||||||
|
base_obj_dir: Option<&Utf8PlatformPath>,
|
||||||
|
) -> Self {
|
||||||
|
let target_path = if let (Some(target_obj_dir), Some(path), None) =
|
||||||
|
(target_obj_dir, &object.path, &object.target_path)
|
||||||
|
{
|
||||||
|
Some(target_obj_dir.join(path.with_platform_encoding()))
|
||||||
|
} else if let Some(path) = &object.target_path {
|
||||||
|
Some(project_dir.join(path.with_platform_encoding()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let base_path = if let (Some(base_obj_dir), Some(path), None) =
|
||||||
|
(base_obj_dir, &object.path, &object.base_path)
|
||||||
|
{
|
||||||
|
Some(base_obj_dir.join(path.with_platform_encoding()))
|
||||||
|
} else if let Some(path) = &object.base_path {
|
||||||
|
Some(project_dir.join(path.with_platform_encoding()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
name: object.name().to_string(),
|
||||||
|
target_path,
|
||||||
|
base_path,
|
||||||
|
metadata: object.metadata.clone().unwrap_or_default(),
|
||||||
|
complete: object.complete(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
fn reload(&mut self) -> Result<()> {
|
fn reload(&mut self) -> Result<()> {
|
||||||
let config = create_objdiff_config(self);
|
let config = create_objdiff_config(self);
|
||||||
@ -355,8 +379,8 @@ impl Wake for TermWaker {
|
|||||||
|
|
||||||
fn run_interactive(
|
fn run_interactive(
|
||||||
args: Args,
|
args: Args,
|
||||||
target_path: Option<PathBuf>,
|
target_path: Option<Utf8PlatformPathBuf>,
|
||||||
base_path: Option<PathBuf>,
|
base_path: Option<Utf8PlatformPathBuf>,
|
||||||
project_config: Option<ProjectConfig>,
|
project_config: Option<ProjectConfig>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
||||||
@ -384,7 +408,7 @@ fn run_interactive(
|
|||||||
let watch_patterns = project_config.build_watch_patterns()?;
|
let watch_patterns = project_config.build_watch_patterns()?;
|
||||||
state.watcher = Some(create_watcher(
|
state.watcher = Some(create_watcher(
|
||||||
state.modified.clone(),
|
state.modified.clone(),
|
||||||
project_dir,
|
project_dir.as_ref(),
|
||||||
build_globset(&watch_patterns)?,
|
build_globset(&watch_patterns)?,
|
||||||
Waker::from(state.waker.clone()),
|
Waker::from(state.waker.clone()),
|
||||||
)?);
|
)?);
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
use std::{
|
use std::{collections::HashSet, fs::File, io::Read, time::Instant};
|
||||||
collections::HashSet,
|
|
||||||
fs::File,
|
|
||||||
io::Read,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use argp::FromArgs;
|
use argp::FromArgs;
|
||||||
@ -14,15 +8,19 @@ use objdiff_core::{
|
|||||||
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
||||||
REPORT_VERSION,
|
REPORT_VERSION,
|
||||||
},
|
},
|
||||||
config::ProjectObject,
|
config::path::platform_path,
|
||||||
diff, obj,
|
diff, obj,
|
||||||
obj::{ObjSectionKind, ObjSymbolFlags},
|
obj::{ObjSectionKind, ObjSymbolFlags},
|
||||||
};
|
};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::util::output::{write_output, OutputFormat};
|
use crate::{
|
||||||
|
cmd::diff::ObjectConfig,
|
||||||
|
util::output::{write_output, OutputFormat},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
/// Generate a progress report for a project.
|
/// Generate a progress report for a project.
|
||||||
@ -43,12 +41,12 @@ pub enum SubCommand {
|
|||||||
/// Generate a progress report for a project.
|
/// Generate a progress report for a project.
|
||||||
#[argp(subcommand, name = "generate")]
|
#[argp(subcommand, name = "generate")]
|
||||||
pub struct GenerateArgs {
|
pub struct GenerateArgs {
|
||||||
#[argp(option, short = 'p')]
|
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||||
/// Project directory
|
/// Project directory
|
||||||
project: Option<PathBuf>,
|
project: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file
|
/// Output file
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(switch, short = 'd')]
|
#[argp(switch, short = 'd')]
|
||||||
/// Deduplicate global and weak symbols (runs single-threaded)
|
/// Deduplicate global and weak symbols (runs single-threaded)
|
||||||
deduplicate: bool,
|
deduplicate: bool,
|
||||||
@ -61,15 +59,15 @@ pub struct GenerateArgs {
|
|||||||
/// List any changes from a previous report.
|
/// List any changes from a previous report.
|
||||||
#[argp(subcommand, name = "changes")]
|
#[argp(subcommand, name = "changes")]
|
||||||
pub struct ChangesArgs {
|
pub struct ChangesArgs {
|
||||||
#[argp(positional)]
|
#[argp(positional, from_str_fn(platform_path))]
|
||||||
/// Previous report file
|
/// Previous report file
|
||||||
previous: PathBuf,
|
previous: Utf8PlatformPathBuf,
|
||||||
#[argp(positional)]
|
#[argp(positional, from_str_fn(platform_path))]
|
||||||
/// Current report file
|
/// Current report file
|
||||||
current: PathBuf,
|
current: Utf8PlatformPathBuf,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file
|
/// Output file
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'f')]
|
#[argp(option, short = 'f')]
|
||||||
/// Output format (json, json-pretty, proto) (default: json)
|
/// Output format (json, json-pretty, proto) (default: json)
|
||||||
format: Option<String>,
|
format: Option<String>,
|
||||||
@ -84,10 +82,10 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
|
|
||||||
fn generate(args: GenerateArgs) -> Result<()> {
|
fn generate(args: GenerateArgs) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Path::new("."));
|
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
||||||
info!("Loading project {}", project_dir.display());
|
info!("Loading project {}", project_dir);
|
||||||
|
|
||||||
let mut project = match objdiff_core::config::try_project_config(project_dir) {
|
let project = match objdiff_core::config::try_project_config(project_dir.as_ref()) {
|
||||||
Some((Ok(config), _)) => config,
|
Some((Ok(config), _)) => config,
|
||||||
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
||||||
None => bail!("No project configuration found"),
|
None => bail!("No project configuration found"),
|
||||||
@ -98,37 +96,33 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let target_obj_dir =
|
||||||
|
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
let base_obj_dir =
|
||||||
|
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
let objects = project
|
||||||
|
.units
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|o| {
|
||||||
|
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut units = vec![];
|
let mut units = vec![];
|
||||||
let mut existing_functions: HashSet<String> = HashSet::new();
|
let mut existing_functions: HashSet<String> = HashSet::new();
|
||||||
if args.deduplicate {
|
if args.deduplicate {
|
||||||
// If deduplicating, we need to run single-threaded
|
// If deduplicating, we need to run single-threaded
|
||||||
for object in project.units.as_deref_mut().unwrap_or_default() {
|
for object in &objects {
|
||||||
if let Some(unit) = report_object(
|
if let Some(unit) = report_object(object, Some(&mut existing_functions))? {
|
||||||
object,
|
|
||||||
project_dir,
|
|
||||||
project.target_dir.as_deref(),
|
|
||||||
project.base_dir.as_deref(),
|
|
||||||
Some(&mut existing_functions),
|
|
||||||
)? {
|
|
||||||
units.push(unit);
|
units.push(unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let vec = project
|
let vec = objects
|
||||||
.units
|
.par_iter()
|
||||||
.as_deref_mut()
|
.map(|object| report_object(object, None))
|
||||||
.unwrap_or_default()
|
|
||||||
.par_iter_mut()
|
|
||||||
.map(|object| {
|
|
||||||
report_object(
|
|
||||||
object,
|
|
||||||
project_dir,
|
|
||||||
project.target_dir.as_deref(),
|
|
||||||
project.base_dir.as_deref(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
||||||
units = vec.into_iter().flatten().collect();
|
units = vec.into_iter().flatten().collect();
|
||||||
}
|
}
|
||||||
@ -151,20 +145,16 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn report_object(
|
fn report_object(
|
||||||
object: &mut ProjectObject,
|
object: &ObjectConfig,
|
||||||
project_dir: &Path,
|
|
||||||
target_dir: Option<&Path>,
|
|
||||||
base_dir: Option<&Path>,
|
|
||||||
mut existing_functions: Option<&mut HashSet<String>>,
|
mut existing_functions: Option<&mut HashSet<String>>,
|
||||||
) -> Result<Option<ReportUnit>> {
|
) -> Result<Option<ReportUnit>> {
|
||||||
object.resolve_paths(project_dir, target_dir, base_dir);
|
|
||||||
match (&object.target_path, &object.base_path) {
|
match (&object.target_path, &object.base_path) {
|
||||||
(None, Some(_)) if !object.complete().unwrap_or(false) => {
|
(None, Some(_)) if !object.complete.unwrap_or(false) => {
|
||||||
warn!("Skipping object without target: {}", object.name());
|
warn!("Skipping object without target: {}", object.name);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
warn!("Skipping object without target or base: {}", object.name());
|
warn!("Skipping object without target or base: {}", object.name);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -178,35 +168,31 @@ fn report_object(
|
|||||||
.target_path
|
.target_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &diff_config)
|
obj::read::read(p.as_ref(), &diff_config)
|
||||||
.with_context(|| format!("Failed to open {}", p.display()))
|
.with_context(|| format!("Failed to open {}", p))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let base = object
|
let base = object
|
||||||
.base_path
|
.base_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &diff_config)
|
obj::read::read(p.as_ref(), &diff_config)
|
||||||
.with_context(|| format!("Failed to open {}", p.display()))
|
.with_context(|| format!("Failed to open {}", p))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let result =
|
let result =
|
||||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
||||||
|
|
||||||
let metadata = ReportUnitMetadata {
|
let metadata = ReportUnitMetadata {
|
||||||
complete: object.complete(),
|
complete: object.metadata.complete,
|
||||||
module_name: target
|
module_name: target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|o| o.split_meta.as_ref())
|
.and_then(|o| o.split_meta.as_ref())
|
||||||
.and_then(|m| m.module_name.clone()),
|
.and_then(|m| m.module_name.clone()),
|
||||||
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
||||||
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()),
|
source_path: object.metadata.source_path.as_ref().map(|p| p.to_string()),
|
||||||
progress_categories: object
|
progress_categories: object.metadata.progress_categories.clone().unwrap_or_default(),
|
||||||
.metadata
|
auto_generated: object.metadata.auto_generated,
|
||||||
.as_ref()
|
|
||||||
.and_then(|m| m.progress_categories.clone())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
|
||||||
};
|
};
|
||||||
let mut measures = Measures { total_units: 1, ..Default::default() };
|
let mut measures = Measures { total_units: 1, ..Default::default() };
|
||||||
let mut sections = vec![];
|
let mut sections = vec![];
|
||||||
@ -218,7 +204,7 @@ fn report_object(
|
|||||||
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete().unwrap_or(false) {
|
if object.complete.unwrap_or(false) {
|
||||||
100.0
|
100.0
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
@ -260,7 +246,7 @@ fn report_object(
|
|||||||
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete().unwrap_or(false) {
|
if object.complete.unwrap_or(false) {
|
||||||
100.0
|
100.0
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
@ -294,7 +280,7 @@ fn report_object(
|
|||||||
measures.calc_fuzzy_match_percent();
|
measures.calc_fuzzy_match_percent();
|
||||||
measures.calc_matched_percent();
|
measures.calc_matched_percent();
|
||||||
Ok(Some(ReportUnit {
|
Ok(Some(ReportUnit {
|
||||||
name: object.name().to_string(),
|
name: object.name.clone(),
|
||||||
measures: Some(measures),
|
measures: Some(measures),
|
||||||
sections,
|
sections,
|
||||||
functions,
|
functions,
|
||||||
@ -304,7 +290,7 @@ fn report_object(
|
|||||||
|
|
||||||
fn changes(args: ChangesArgs) -> Result<()> {
|
fn changes(args: ChangesArgs) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let (previous, current) = if args.previous == Path::new("-") && args.current == Path::new("-") {
|
let (previous, current) = if args.previous == "-" && args.current == "-" {
|
||||||
// Special case for comparing two reports from stdin
|
// Special case for comparing two reports from stdin
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
std::io::stdin().read_to_end(&mut data)?;
|
std::io::stdin().read_to_end(&mut data)?;
|
||||||
@ -419,15 +405,14 @@ fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_report(path: &Path) -> Result<Report> {
|
fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
|
||||||
if path == Path::new("-") {
|
if path == Utf8PlatformPath::new("-") {
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
std::io::stdin().read_to_end(&mut data)?;
|
std::io::stdin().read_to_end(&mut data)?;
|
||||||
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
||||||
}
|
}
|
||||||
let file = File::open(path).with_context(|| format!("Failed to open {}", path.display()))?;
|
let file = File::open(path).with_context(|| format!("Failed to open {}", path))?;
|
||||||
let mmap = unsafe { memmap2::Mmap::map(&file) }
|
let mmap =
|
||||||
.with_context(|| format!("Failed to map {}", path.display()))?;
|
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {}", path))?;
|
||||||
Report::parse(mmap.as_ref())
|
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {}", path))
|
||||||
.with_context(|| format!("Failed to load report {}", path.display()))
|
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,12 @@ impl OutputFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_output<T>(input: &T, output: Option<&Path>, format: OutputFormat) -> Result<()>
|
pub fn write_output<T, P>(input: &T, output: Option<P>, format: OutputFormat) -> Result<()>
|
||||||
where T: serde::Serialize + prost::Message {
|
where
|
||||||
match output {
|
T: serde::Serialize + prost::Message,
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
match output.as_ref().map(|p| p.as_ref()) {
|
||||||
Some(output) if output != Path::new("-") => {
|
Some(output) if output != Path::new("-") => {
|
||||||
info!("Writing to {}", output.display());
|
info!("Writing to {}", output.display());
|
||||||
let file = File::options()
|
let file = File::options()
|
||||||
|
@ -16,12 +16,14 @@ documentation = "https://docs.rs/objdiff-core"
|
|||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["std"]
|
||||||
all = [
|
all = [
|
||||||
# Features
|
# Features
|
||||||
"bindings",
|
"bindings",
|
||||||
"build",
|
"build",
|
||||||
"config",
|
"config",
|
||||||
"dwarf",
|
"dwarf",
|
||||||
|
"serde",
|
||||||
# Architectures
|
# Architectures
|
||||||
"mips",
|
"mips",
|
||||||
"ppc",
|
"ppc",
|
||||||
@ -31,30 +33,21 @@ all = [
|
|||||||
]
|
]
|
||||||
# Implicit, used to check if any arch is enabled
|
# Implicit, used to check if any arch is enabled
|
||||||
any-arch = [
|
any-arch = [
|
||||||
"config",
|
|
||||||
"dep:bimap",
|
|
||||||
"dep:byteorder",
|
"dep:byteorder",
|
||||||
"dep:flagset",
|
"dep:flagset",
|
||||||
"dep:heck",
|
"dep:heck",
|
||||||
"dep:log",
|
"dep:log",
|
||||||
"dep:memmap2",
|
|
||||||
"dep:num-traits",
|
"dep:num-traits",
|
||||||
"dep:prettyplease",
|
"dep:prettyplease",
|
||||||
"dep:proc-macro2",
|
"dep:proc-macro2",
|
||||||
"dep:quote",
|
"dep:quote",
|
||||||
"dep:serde",
|
|
||||||
"dep:serde_json",
|
|
||||||
"dep:similar",
|
"dep:similar",
|
||||||
"dep:strum",
|
"dep:strum",
|
||||||
"dep:syn",
|
"dep:syn",
|
||||||
]
|
]
|
||||||
bindings = [
|
bindings = [
|
||||||
"dep:pbjson",
|
|
||||||
"dep:pbjson-build",
|
|
||||||
"dep:prost",
|
"dep:prost",
|
||||||
"dep:prost-build",
|
"dep:prost-build",
|
||||||
"dep:serde",
|
|
||||||
"dep:serde_json",
|
|
||||||
]
|
]
|
||||||
build = [
|
build = [
|
||||||
"dep:notify",
|
"dep:notify",
|
||||||
@ -68,15 +61,31 @@ build = [
|
|||||||
"dep:winapi",
|
"dep:winapi",
|
||||||
]
|
]
|
||||||
config = [
|
config = [
|
||||||
"dep:bimap",
|
|
||||||
"dep:filetime",
|
|
||||||
"dep:globset",
|
"dep:globset",
|
||||||
"dep:semver",
|
"dep:semver",
|
||||||
"dep:serde",
|
"dep:typed-path",
|
||||||
"dep:serde_json",
|
|
||||||
"dep:serde_yaml",
|
|
||||||
]
|
]
|
||||||
dwarf = ["dep:gimli"]
|
dwarf = ["dep:gimli"]
|
||||||
|
serde = [
|
||||||
|
"dep:pbjson",
|
||||||
|
"dep:pbjson-build",
|
||||||
|
"dep:serde",
|
||||||
|
"dep:serde_json",
|
||||||
|
]
|
||||||
|
std = [
|
||||||
|
"anyhow/std",
|
||||||
|
"byteorder?/std",
|
||||||
|
"flagset?/std",
|
||||||
|
"log?/std",
|
||||||
|
"num-traits?/std",
|
||||||
|
"object/std",
|
||||||
|
"prost?/std",
|
||||||
|
"serde?/std",
|
||||||
|
"strum?/std",
|
||||||
|
"typed-path?/std",
|
||||||
|
"dep:filetime",
|
||||||
|
"dep:memmap2",
|
||||||
|
]
|
||||||
mips = [
|
mips = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:rabbitizer",
|
"dep:rabbitizer",
|
||||||
@ -108,65 +117,66 @@ arm64 = [
|
|||||||
wasm = [
|
wasm = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"bindings",
|
"bindings",
|
||||||
"dep:console_error_panic_hook",
|
|
||||||
"dep:console_log",
|
|
||||||
"dep:log",
|
"dep:log",
|
||||||
"dep:tsify-next",
|
"dep:talc",
|
||||||
"dep:wasm-bindgen",
|
"dep:spin",
|
||||||
|
"dep:wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["all"]
|
features = ["all"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = { version = "1.0", default-features = false }
|
||||||
bimap = { version = "0.6", features = ["serde"], optional = true }
|
byteorder = { version = "1.5", default-features = false, optional = true }
|
||||||
byteorder = { version = "1.5", optional = true }
|
|
||||||
filetime = { version = "0.2", optional = true }
|
filetime = { version = "0.2", optional = true }
|
||||||
flagset = { version = "0.4", optional = true }
|
flagset = { version = "0.4", default-features = false, optional = true }
|
||||||
log = { version = "0.4", optional = true }
|
log = { version = "0.4", default-features = false, optional = true }
|
||||||
memmap2 = { version = "0.9", optional = true }
|
memmap2 = { version = "0.9", optional = true }
|
||||||
num-traits = { version = "0.2", optional = true }
|
num-traits = { version = "0.2", default-features = false, optional = true }
|
||||||
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
object = { version = "0.36", default-features = false, features = ["read_core", "elf", "pe"] }
|
||||||
pbjson = { version = "0.7", optional = true }
|
pbjson = { version = "0.7", default-features = false, optional = true }
|
||||||
prost = { version = "0.13", optional = true }
|
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||||
similar = { version = "2.6", default-features = false, optional = true }
|
similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
|
||||||
strum = { version = "0.26", features = ["derive"], optional = true }
|
strum = { version = "0.26", default-features = false, features = ["derive"], optional = true }
|
||||||
wasm-bindgen = { version = "0.2", optional = true }
|
typed-path = { version = "0.10", default-features = false, optional = true }
|
||||||
tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
|
|
||||||
console_log = { version = "1.0", optional = true }
|
|
||||||
console_error_panic_hook = { version = "0.1", optional = true }
|
|
||||||
|
|
||||||
# config
|
# config
|
||||||
globset = { version = "0.4", features = ["serde1"], optional = true }
|
globset = { version = "0.4", default-features = false, optional = true }
|
||||||
semver = { version = "1.0", optional = true }
|
semver = { version = "1.0", default-features = false, optional = true }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
||||||
serde_yaml = { version = "0.9", optional = true }
|
|
||||||
|
|
||||||
# dwarf
|
# dwarf
|
||||||
gimli = { version = "0.31", default-features = false, features = ["read-all"], optional = true }
|
gimli = { version = "0.31", default-features = false, features = ["read"], optional = true }
|
||||||
|
|
||||||
# ppc
|
# ppc
|
||||||
cwdemangle = { version = "1.0", optional = true }
|
cwdemangle = { version = "1.0", optional = true }
|
||||||
cwextab = { version = "1.0", optional = true }
|
cwextab = { version = "1.0", optional = true, git = "https://github.com/encounter/cwextab.git" }
|
||||||
ppc750cl = { version = "0.3", optional = true }
|
ppc750cl = { version = "0.3", optional = true }
|
||||||
|
|
||||||
# mips
|
# mips
|
||||||
rabbitizer = { version = "1.12", optional = true }
|
rabbitizer = { version = "1.12", optional = true }
|
||||||
|
|
||||||
# x86
|
# x86
|
||||||
cpp_demangle = { version = "0.4", optional = true }
|
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
|
||||||
iced-x86 = { version = "1.21", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true }
|
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
||||||
msvc-demangler = { version = "0.10", optional = true }
|
msvc-demangler = { version = "0.11", optional = true }
|
||||||
|
|
||||||
# arm
|
# arm
|
||||||
unarm = { version = "1.6", optional = true }
|
unarm = { version = "1.7", optional = true }
|
||||||
arm-attr = { version = "0.1", optional = true }
|
arm-attr = { version = "0.2", optional = true }
|
||||||
|
|
||||||
# arm64
|
# arm64
|
||||||
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
|
||||||
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
||||||
|
|
||||||
|
# wasm
|
||||||
|
#console_error_panic_hook = { version = "0.1", optional = true }
|
||||||
|
#console_log = { version = "1.0", optional = true }
|
||||||
|
talc = { version = "4.4", optional = true }
|
||||||
|
spin = { version = "0.9", optional = true }
|
||||||
|
wit-bindgen = { version = "0.38", default-features = false, features = ["macros"], optional = true }
|
||||||
|
|
||||||
# build
|
# build
|
||||||
notify = { version = "8.0.0", optional = true }
|
notify = { version = "8.0.0", optional = true }
|
||||||
@ -196,6 +206,6 @@ prettyplease = { version = "0.2", optional = true }
|
|||||||
proc-macro2 = { version = "1.0", optional = true }
|
proc-macro2 = { version = "1.0", optional = true }
|
||||||
prost-build = { version = "0.13", optional = true }
|
prost-build = { version = "0.13", optional = true }
|
||||||
quote = { version = "1.0", optional = true }
|
quote = { version = "1.0", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0" }
|
||||||
syn = { version = "2.0", optional = true }
|
syn = { version = "2.0", optional = true }
|
||||||
|
@ -54,6 +54,8 @@ fn compile_protos() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
{
|
||||||
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
||||||
pbjson_build::Builder::new()
|
pbjson_build::Builder::new()
|
||||||
.register_descriptors(&descriptor_set)
|
.register_descriptors(&descriptor_set)
|
||||||
@ -62,3 +64,4 @@ fn compile_protos() {
|
|||||||
.build(&[".objdiff"])
|
.build(&[".objdiff"])
|
||||||
.expect("Failed to build pbjson");
|
.expect("Failed to build pbjson");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -100,7 +100,7 @@ pub fn generate_diff_config() {
|
|||||||
}
|
}
|
||||||
let value = &item.value;
|
let value = &item.value;
|
||||||
variants.extend(quote! {
|
variants.extend(quote! {
|
||||||
#[serde(rename = #value, alias = #variant_name)]
|
#[cfg_attr(feature = "serde", serde(rename = #value, alias = #variant_name))]
|
||||||
#variant_ident,
|
#variant_ident,
|
||||||
});
|
});
|
||||||
full_variants.extend(quote! { #enum_ident::#variant_ident, });
|
full_variants.extend(quote! { #enum_ident::#variant_ident, });
|
||||||
@ -134,8 +134,8 @@ pub fn generate_diff_config() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
enums.extend(quote! {
|
enums.extend(quote! {
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum #enum_ident {
|
pub enum #enum_ident {
|
||||||
#variants
|
#variants
|
||||||
}
|
}
|
||||||
@ -168,7 +168,7 @@ pub fn generate_diff_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::str::FromStr for #enum_ident {
|
impl core::str::FromStr for #enum_ident {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
#variant_from_str
|
#variant_from_str
|
||||||
@ -244,7 +244,7 @@ pub fn generate_diff_config() {
|
|||||||
let default = b.default;
|
let default = b.default;
|
||||||
if default {
|
if default {
|
||||||
property_fields.extend(quote! {
|
property_fields.extend(quote! {
|
||||||
#[serde(default = "default_true")]
|
#[cfg_attr(feature = "serde", serde(default = "default_true"))]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
property_fields.extend(quote! {
|
property_fields.extend(quote! {
|
||||||
@ -412,7 +412,7 @@ pub fn generate_diff_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::str::FromStr for ConfigPropertyId {
|
impl core::str::FromStr for ConfigPropertyId {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
#config_property_id_from_str
|
#config_property_id_from_str
|
||||||
@ -433,6 +433,7 @@ pub fn generate_diff_config() {
|
|||||||
Choice(&'static str),
|
Choice(&'static str),
|
||||||
}
|
}
|
||||||
impl ConfigPropertyValue {
|
impl ConfigPropertyValue {
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
pub fn to_json(&self) -> serde_json::Value {
|
pub fn to_json(&self) -> serde_json::Value {
|
||||||
match self {
|
match self {
|
||||||
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
|
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
|
||||||
@ -440,17 +441,25 @@ pub fn generate_diff_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl core::fmt::Display for ConfigPropertyValue {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ConfigPropertyValue::Boolean(value) => write!(f, "{}", value),
|
||||||
|
ConfigPropertyValue::Choice(value) => write!(f, "{}", value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ConfigPropertyKind {
|
pub enum ConfigPropertyKind {
|
||||||
Boolean,
|
Boolean,
|
||||||
Choice(&'static [ConfigEnumVariantInfo]),
|
Choice(&'static [ConfigEnumVariantInfo]),
|
||||||
}
|
}
|
||||||
#enums
|
#enums
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn default_true() -> bool { true }
|
fn default_true() -> bool { true }
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Clone, Debug)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
|
||||||
#[serde(default)]
|
|
||||||
pub struct DiffObjConfig {
|
pub struct DiffObjConfig {
|
||||||
#property_fields
|
#property_fields
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
use std::{
|
use alloc::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::{BTreeMap, HashMap},
|
collections::BTreeMap,
|
||||||
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
|
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
|
||||||
use object::{
|
use object::{
|
||||||
elf::{self, SHT_ARM_ATTRIBUTES},
|
elf::{self, SHT_ARM_ATTRIBUTES},
|
||||||
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
|
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionKind,
|
||||||
SectionKind, Symbol, SymbolKind,
|
Symbol, SymbolKind,
|
||||||
};
|
};
|
||||||
use unarm::{
|
use unarm::{
|
||||||
args::{Argument, OffsetImm, OffsetReg, Register},
|
args::{Argument, OffsetImm, OffsetReg, Register},
|
||||||
@ -24,7 +28,7 @@ use crate::{
|
|||||||
|
|
||||||
pub struct ObjArchArm {
|
pub struct ObjArchArm {
|
||||||
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
|
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
|
||||||
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
|
disasm_modes: BTreeMap<usize, Vec<DisasmMode>>,
|
||||||
detected_version: Option<ArmVersion>,
|
detected_version: Option<ArmVersion>,
|
||||||
endianness: object::Endianness,
|
endianness: object::Endianness,
|
||||||
}
|
}
|
||||||
@ -78,7 +82,7 @@ impl ObjArchArm {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elf_get_mapping_symbols(file: &File) -> HashMap<SectionIndex, Vec<DisasmMode>> {
|
fn elf_get_mapping_symbols(file: &File) -> BTreeMap<usize, Vec<DisasmMode>> {
|
||||||
file.sections()
|
file.sections()
|
||||||
.filter(|s| s.kind() == SectionKind::Text)
|
.filter(|s| s.kind() == SectionKind::Text)
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
@ -89,7 +93,7 @@ impl ObjArchArm {
|
|||||||
.filter_map(|s| DisasmMode::from_symbol(&s))
|
.filter_map(|s| DisasmMode::from_symbol(&s))
|
||||||
.collect();
|
.collect();
|
||||||
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
||||||
(s.index(), mapping_symbols)
|
(s.index().0, mapping_symbols)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@ -121,7 +125,7 @@ impl ObjArch for ObjArchArm {
|
|||||||
let fallback_mappings = [DisasmMode { address: start_addr, mapping: ParseMode::Arm }];
|
let fallback_mappings = [DisasmMode { address: start_addr, mapping: ParseMode::Arm }];
|
||||||
let mapping_symbols = self
|
let mapping_symbols = self
|
||||||
.disasm_modes
|
.disasm_modes
|
||||||
.get(&SectionIndex(section_index))
|
.get(§ion_index)
|
||||||
.map(|x| x.as_slice())
|
.map(|x| x.as_slice())
|
||||||
.unwrap_or(&fallback_mappings);
|
.unwrap_or(&fallback_mappings);
|
||||||
let first_mapping_idx = mapping_symbols
|
let first_mapping_idx = mapping_symbols
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap};
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
collections::BTreeMap,
|
||||||
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::cmp::Ordering;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use object::{elf, File, Relocation, RelocationFlags};
|
use object::{elf, File, Relocation, RelocationFlags};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
|
use alloc::{borrow::Cow, collections::BTreeMap, format, vec::Vec};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use object::{
|
use object::{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap, ffi::CStr};
|
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, format, string::String, vec::Vec};
|
||||||
|
use core::ffi::CStr;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use byteorder::ByteOrder;
|
use byteorder::ByteOrder;
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
use std::{
|
use alloc::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::{BTreeMap, HashMap, HashSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, ensure, Result};
|
use anyhow::{bail, ensure, Result};
|
||||||
@ -479,7 +483,7 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
|||||||
|
|
||||||
// Remove the relocation we're keeping track of in a particular register when an instruction reuses
|
// Remove the relocation we're keeping track of in a particular register when an instruction reuses
|
||||||
// that register to hold some other value, unrelated to pool relocation addresses.
|
// that register to hold some other value, unrelated to pool relocation addresses.
|
||||||
fn clear_overwritten_gprs(ins: Ins, gpr_pool_relocs: &mut HashMap<u8, ObjReloc>) {
|
fn clear_overwritten_gprs(ins: Ins, gpr_pool_relocs: &mut BTreeMap<u8, ObjReloc>) {
|
||||||
let mut def_args = Arguments::default();
|
let mut def_args = Arguments::default();
|
||||||
ins.parse_defs(&mut def_args);
|
ins.parse_defs(&mut def_args);
|
||||||
for arg in def_args {
|
for arg in def_args {
|
||||||
@ -576,11 +580,11 @@ fn generate_fake_pool_reloc_for_addr_mapping(
|
|||||||
func_address: u64,
|
func_address: u64,
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
relocations: &[ObjReloc],
|
relocations: &[ObjReloc],
|
||||||
) -> HashMap<u32, ObjReloc> {
|
) -> BTreeMap<u32, ObjReloc> {
|
||||||
let mut visited_ins_addrs = HashSet::new();
|
let mut visited_ins_addrs = BTreeSet::new();
|
||||||
let mut pool_reloc_for_addr = HashMap::new();
|
let mut pool_reloc_for_addr = BTreeMap::new();
|
||||||
let mut ins_iters_with_gpr_state =
|
let mut ins_iters_with_gpr_state =
|
||||||
vec![(InsIter::new(code, func_address as u32), HashMap::new())];
|
vec![(InsIter::new(code, func_address as u32), BTreeMap::new())];
|
||||||
let mut gpr_state_at_bctr = BTreeMap::new();
|
let mut gpr_state_at_bctr = BTreeMap::new();
|
||||||
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
|
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
|
||||||
for (cur_addr, ins) in ins_iter {
|
for (cur_addr, ins) in ins_iter {
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap};
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
boxed::Box,
|
||||||
|
collections::BTreeMap,
|
||||||
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Result};
|
use anyhow::{anyhow, bail, ensure, Result};
|
||||||
use iced_x86::{
|
use iced_x86::{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||||
|
|
||||||
|
use alloc::string::ToString;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::{
|
diff::{
|
||||||
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
||||||
@ -13,6 +16,7 @@ use crate::{
|
|||||||
|
|
||||||
// Protobuf diff types
|
// Protobuf diff types
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
|
||||||
|
|
||||||
impl DiffResult {
|
impl DiffResult {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
pub mod wasm;
|
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||||
use std::ops::AddAssign;
|
|
||||||
|
use alloc::{
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::ops::AddAssign;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use serde_json::error::Category;
|
|
||||||
|
|
||||||
// Protobuf report types
|
// Protobuf report types
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
||||||
|
|
||||||
pub const REPORT_VERSION: u32 = 2;
|
pub const REPORT_VERSION: u32 = 2;
|
||||||
@ -15,23 +21,30 @@ impl Report {
|
|||||||
/// Attempts to parse the report as binary protobuf or JSON.
|
/// Attempts to parse the report as binary protobuf or JSON.
|
||||||
pub fn parse(data: &[u8]) -> Result<Self> {
|
pub fn parse(data: &[u8]) -> Result<Self> {
|
||||||
if data.is_empty() {
|
if data.is_empty() {
|
||||||
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
|
bail!("Empty data");
|
||||||
}
|
}
|
||||||
let report = if data[0] == b'{' {
|
let report = if data[0] == b'{' {
|
||||||
// Load as JSON
|
// Load as JSON
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
{
|
||||||
Self::from_json(data)?
|
Self::from_json(data)?
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "serde"))]
|
||||||
|
bail!("JSON report parsing requires the `serde` feature")
|
||||||
} else {
|
} else {
|
||||||
// Load as binary protobuf
|
// Load as binary protobuf
|
||||||
Self::decode(data)?
|
Self::decode(data).map_err(|e| anyhow::Error::msg(e.to_string()))?
|
||||||
};
|
};
|
||||||
Ok(report)
|
Ok(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
|
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
|
||||||
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
||||||
match serde_json::from_slice::<Self>(bytes) {
|
match serde_json::from_slice::<Self>(bytes) {
|
||||||
Ok(report) => Ok(report),
|
Ok(report) => Ok(report),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
use serde_json::error::Category;
|
||||||
match e.classify() {
|
match e.classify() {
|
||||||
Category::Io | Category::Eof | Category::Syntax => Err(e),
|
Category::Io | Category::Eof | Category::Syntax => Err(e),
|
||||||
Category::Data => {
|
Category::Data => {
|
||||||
@ -304,7 +317,8 @@ impl FromIterator<Measures> for Measures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Older JSON report types
|
// Older JSON report types
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
struct LegacyReport {
|
struct LegacyReport {
|
||||||
fuzzy_match_percent: f32,
|
fuzzy_match_percent: f32,
|
||||||
total_code: u64,
|
total_code: u64,
|
||||||
@ -341,7 +355,8 @@ impl From<LegacyReport> for Report {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
struct LegacyReportUnit {
|
struct LegacyReportUnit {
|
||||||
name: String,
|
name: String,
|
||||||
fuzzy_match_percent: f32,
|
fuzzy_match_percent: f32,
|
||||||
@ -351,11 +366,11 @@ struct LegacyReportUnit {
|
|||||||
matched_data: u64,
|
matched_data: u64,
|
||||||
total_functions: u32,
|
total_functions: u32,
|
||||||
matched_functions: u32,
|
matched_functions: u32,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
complete: Option<bool>,
|
complete: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
module_name: Option<String>,
|
module_name: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
module_id: Option<u32>,
|
module_id: Option<u32>,
|
||||||
sections: Vec<LegacyReportItem>,
|
sections: Vec<LegacyReportItem>,
|
||||||
functions: Vec<LegacyReportItem>,
|
functions: Vec<LegacyReportItem>,
|
||||||
@ -389,16 +404,20 @@ impl From<LegacyReportUnit> for ReportUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
struct LegacyReportItem {
|
struct LegacyReportItem {
|
||||||
name: String,
|
name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
demangled_name: Option<String>,
|
demangled_name: Option<String>,
|
||||||
#[serde(
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(
|
||||||
default,
|
default,
|
||||||
skip_serializing_if = "Option::is_none",
|
skip_serializing_if = "Option::is_none",
|
||||||
serialize_with = "serialize_hex",
|
serialize_with = "serialize_hex",
|
||||||
deserialize_with = "deserialize_hex"
|
deserialize_with = "deserialize_hex"
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
address: Option<u64>,
|
address: Option<u64>,
|
||||||
size: u64,
|
size: u64,
|
||||||
@ -419,6 +438,7 @@ impl From<LegacyReportItem> for ReportItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
|
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
|
||||||
where S: serde::Serializer {
|
where S: serde::Serializer {
|
||||||
if let Some(x) = x {
|
if let Some(x) = x {
|
||||||
@ -428,6 +448,7 @@ where S: serde::Serializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error>
|
fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error>
|
||||||
where D: serde::Deserializer<'de> {
|
where D: serde::Deserializer<'de> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
use prost::Message;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
use crate::{bindings::diff::DiffResult, diff, obj};
|
|
||||||
|
|
||||||
fn parse_object(
|
|
||||||
data: Option<Box<[u8]>>,
|
|
||||||
config: &diff::DiffObjConfig,
|
|
||||||
) -> Result<Option<obj::ObjInfo>, JsError> {
|
|
||||||
data.as_ref().map(|data| obj::read::parse(data, config)).transpose().to_js()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_and_run_diff(
|
|
||||||
left: Option<Box<[u8]>>,
|
|
||||||
right: Option<Box<[u8]>>,
|
|
||||||
diff_config: diff::DiffObjConfig,
|
|
||||||
mapping_config: diff::MappingConfig,
|
|
||||||
) -> Result<DiffResult, JsError> {
|
|
||||||
let target = parse_object(left, &diff_config)?;
|
|
||||||
let base = parse_object(right, &diff_config)?;
|
|
||||||
run_diff(target.as_ref(), base.as_ref(), diff_config, mapping_config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_diff(
|
|
||||||
left: Option<&obj::ObjInfo>,
|
|
||||||
right: Option<&obj::ObjInfo>,
|
|
||||||
diff_config: diff::DiffObjConfig,
|
|
||||||
mapping_config: diff::MappingConfig,
|
|
||||||
) -> Result<DiffResult, JsError> {
|
|
||||||
log::debug!("Running diff with config: {:?}", diff_config);
|
|
||||||
let result = diff::diff_objs(&diff_config, &mapping_config, left, right, None).to_js()?;
|
|
||||||
let left = left.and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
|
||||||
let right = right.and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
|
||||||
Ok(DiffResult::new(left, right))
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[wasm_bindgen]
|
|
||||||
// pub fn run_diff_json(
|
|
||||||
// left: Option<Box<[u8]>>,
|
|
||||||
// right: Option<Box<[u8]>>,
|
|
||||||
// config: diff::DiffObjConfig,
|
|
||||||
// ) -> Result<String, JsError> {
|
|
||||||
// let out = run_diff_opt_box(left, right, config)?;
|
|
||||||
// serde_json::to_string(&out).map_err(|e| JsError::new(&e.to_string()))
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn run_diff_proto(
|
|
||||||
left: Option<Box<[u8]>>,
|
|
||||||
right: Option<Box<[u8]>>,
|
|
||||||
diff_config: diff::DiffObjConfig,
|
|
||||||
mapping_config: diff::MappingConfig,
|
|
||||||
) -> Result<Box<[u8]>, JsError> {
|
|
||||||
let out = parse_and_run_diff(left, right, diff_config, mapping_config)?;
|
|
||||||
Ok(out.encode_to_vec().into_boxed_slice())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
|
||||||
fn start() -> Result<(), JsError> {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
console_log::init_with_level(log::Level::Debug).to_js()?;
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
console_log::init_with_level(log::Level::Info).to_js()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn to_js_error(e: impl std::fmt::Display) -> JsError { JsError::new(&e.to_string()) }
|
|
||||||
|
|
||||||
trait ToJsResult {
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
fn to_js(self) -> Result<Self::Output, JsError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E: std::fmt::Display> ToJsResult for Result<T, E> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn to_js(self) -> Result<T, JsError> { self.map_err(to_js_error) }
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
pub mod watcher;
|
pub mod watcher;
|
||||||
|
|
||||||
use std::{
|
use std::process::Command;
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::Command,
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
};
|
|
||||||
|
|
||||||
pub struct BuildStatus {
|
pub struct BuildStatus {
|
||||||
pub success: bool,
|
pub success: bool,
|
||||||
@ -25,14 +24,14 @@ impl Default for BuildStatus {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BuildConfig {
|
pub struct BuildConfig {
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
|
pub fn run_make(config: &BuildConfig, arg: &str) -> BuildStatus {
|
||||||
let Some(cwd) = &config.project_dir else {
|
let Some(cwd) = &config.project_dir else {
|
||||||
return BuildStatus {
|
return BuildStatus {
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -1,37 +1,47 @@
|
|||||||
use std::{
|
pub mod path;
|
||||||
|
|
||||||
|
use alloc::{
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
fs,
|
string::{String, ToString},
|
||||||
fs::File,
|
vec::Vec,
|
||||||
io::{BufReader, BufWriter, Read},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use filetime::FileTime;
|
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
use path::unix_path_serde_option;
|
||||||
|
use typed_path::Utf8UnixPathBuf;
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Clone)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
pub struct ProjectConfig {
|
pub struct ProjectConfig {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub min_version: Option<String>,
|
pub min_version: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub target_dir: Option<PathBuf>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
pub base_dir: Option<PathBuf>,
|
)]
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub target_dir: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub base_dir: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub build_base: Option<bool>,
|
pub build_base: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub build_target: Option<bool>,
|
pub build_target: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub watch_patterns: Option<Vec<String>>,
|
pub watch_patterns: Option<Vec<String>>,
|
||||||
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(alias = "objects", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
pub units: Option<Vec<ProjectObject>>,
|
pub units: Option<Vec<ProjectObject>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,11 +49,6 @@ impl ProjectConfig {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
|
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn units_mut(&mut self) -> &mut Vec<ProjectObject> {
|
|
||||||
self.units.get_or_insert_with(Vec::new)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
|
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
|
||||||
self.progress_categories.as_deref().unwrap_or_default()
|
self.progress_categories.as_deref().unwrap_or_default()
|
||||||
@ -66,55 +71,62 @@ impl ProjectConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Clone)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
pub struct ProjectObject {
|
pub struct ProjectObject {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub path: Option<PathBuf>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
pub target_path: Option<PathBuf>,
|
)]
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub path: Option<Utf8UnixPathBuf>,
|
||||||
pub base_path: Option<PathBuf>,
|
#[cfg_attr(
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
feature = "serde",
|
||||||
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub target_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub base_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
#[deprecated(note = "Use metadata.complete")]
|
#[deprecated(note = "Use metadata.complete")]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub scratch: Option<ScratchConfig>,
|
pub scratch: Option<ScratchConfig>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub metadata: Option<ProjectObjectMetadata>,
|
pub metadata: Option<ProjectObjectMetadata>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub symbol_mappings: Option<SymbolMappings>,
|
pub symbol_mappings: Option<BTreeMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "wasm", tsify_next::declare)]
|
#[derive(Default, Clone)]
|
||||||
pub type SymbolMappings = BTreeMap<String, String>;
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
|
||||||
pub struct ProjectObjectMetadata {
|
pub struct ProjectObjectMetadata {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub source_path: Option<String>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub source_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub progress_categories: Option<Vec<String>>,
|
pub progress_categories: Option<Vec<String>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub auto_generated: Option<bool>,
|
pub auto_generated: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Clone)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
pub struct ProjectProgressCategory {
|
pub struct ProjectProgressCategory {
|
||||||
#[serde(default)]
|
|
||||||
pub id: String,
|
pub id: String,
|
||||||
#[serde(default)]
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,33 +135,12 @@ impl ProjectObject {
|
|||||||
if let Some(name) = &self.name {
|
if let Some(name) = &self.name {
|
||||||
name
|
name
|
||||||
} else if let Some(path) = &self.path {
|
} else if let Some(path) = &self.path {
|
||||||
path.to_str().unwrap_or("[invalid path]")
|
path.as_str()
|
||||||
} else {
|
} else {
|
||||||
"[unknown]"
|
"[unknown]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_paths(
|
|
||||||
&mut self,
|
|
||||||
project_dir: &Path,
|
|
||||||
target_obj_dir: Option<&Path>,
|
|
||||||
base_obj_dir: Option<&Path>,
|
|
||||||
) {
|
|
||||||
if let (Some(target_obj_dir), Some(path), None) =
|
|
||||||
(target_obj_dir, &self.path, &self.target_path)
|
|
||||||
{
|
|
||||||
self.target_path = Some(target_obj_dir.join(path));
|
|
||||||
} else if let Some(path) = &self.target_path {
|
|
||||||
self.target_path = Some(project_dir.join(path));
|
|
||||||
}
|
|
||||||
if let (Some(base_obj_dir), Some(path), None) = (base_obj_dir, &self.path, &self.base_path)
|
|
||||||
{
|
|
||||||
self.base_path = Some(base_obj_dir.join(path));
|
|
||||||
} else if let Some(path) = &self.base_path {
|
|
||||||
self.base_path = Some(project_dir.join(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn complete(&self) -> Option<bool> {
|
pub fn complete(&self) -> Option<bool> {
|
||||||
#[expect(deprecated)]
|
#[expect(deprecated)]
|
||||||
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
||||||
@ -164,25 +155,36 @@ impl ProjectObject {
|
|||||||
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_path(&self) -> Option<&String> {
|
pub fn source_path(&self) -> Option<&Utf8UnixPathBuf> {
|
||||||
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
|
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn progress_categories(&self) -> &[String] {
|
||||||
|
self.metadata.as_ref().and_then(|m| m.progress_categories.as_deref()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auto_generated(&self) -> Option<bool> {
|
||||||
|
self.metadata.as_ref().and_then(|m| m.auto_generated)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
|
||||||
pub struct ScratchConfig {
|
pub struct ScratchConfig {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub platform: Option<String>,
|
pub platform: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub compiler: Option<String>,
|
pub compiler: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub c_flags: Option<String>,
|
pub c_flags: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub ctx_path: Option<PathBuf>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub ctx_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub build_ctx: Option<bool>,
|
pub build_ctx: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub preset_id: Option<u32>,
|
pub preset_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,16 +199,20 @@ pub fn default_watch_patterns() -> Vec<Glob> {
|
|||||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct ProjectConfigInfo {
|
pub struct ProjectConfigInfo {
|
||||||
pub path: PathBuf,
|
pub path: std::path::PathBuf,
|
||||||
pub timestamp: Option<FileTime>,
|
pub timestamp: Option<filetime::FileTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
#[cfg(feature = "std")]
|
||||||
|
pub fn try_project_config(
|
||||||
|
dir: &std::path::Path,
|
||||||
|
) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
||||||
for filename in CONFIG_FILENAMES.iter() {
|
for filename in CONFIG_FILENAMES.iter() {
|
||||||
let config_path = dir.join(filename);
|
let config_path = dir.join(filename);
|
||||||
let Ok(file) = File::open(&config_path) else {
|
let Ok(file) = std::fs::File::open(&config_path) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let metadata = file.metadata();
|
let metadata = file.metadata();
|
||||||
@ -214,12 +220,9 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
|||||||
if !metadata.is_file() {
|
if !metadata.is_file() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let ts = FileTime::from_last_modification_time(&metadata);
|
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||||
let mut reader = BufReader::new(file);
|
let mut reader = std::io::BufReader::new(file);
|
||||||
let mut result = match filename.contains("json") {
|
let mut result = read_json_config(&mut reader);
|
||||||
true => read_json_config(&mut reader),
|
|
||||||
false => read_yml_config(&mut reader),
|
|
||||||
};
|
|
||||||
if let Ok(config) = &result {
|
if let Ok(config) = &result {
|
||||||
// Validate min_version if present
|
// Validate min_version if present
|
||||||
if let Err(e) = validate_min_version(config) {
|
if let Err(e) = validate_min_version(config) {
|
||||||
@ -232,40 +235,42 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
pub fn save_project_config(
|
pub fn save_project_config(
|
||||||
config: &ProjectConfig,
|
config: &ProjectConfig,
|
||||||
info: &ProjectConfigInfo,
|
info: &ProjectConfigInfo,
|
||||||
) -> Result<ProjectConfigInfo> {
|
) -> Result<ProjectConfigInfo> {
|
||||||
if let Some(last_ts) = info.timestamp {
|
if let Some(last_ts) = info.timestamp {
|
||||||
// Check if the file has changed since we last read it
|
// Check if the file has changed since we last read it
|
||||||
if let Ok(metadata) = fs::metadata(&info.path) {
|
if let Ok(metadata) = std::fs::metadata(&info.path) {
|
||||||
let ts = FileTime::from_last_modification_time(&metadata);
|
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||||
if ts != last_ts {
|
if ts != last_ts {
|
||||||
return Err(anyhow!("Config file has changed since last read"));
|
return Err(anyhow!("Config file has changed since last read"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut writer =
|
let mut writer = std::io::BufWriter::new(
|
||||||
BufWriter::new(File::create(&info.path).context("Failed to create config file")?);
|
std::fs::File::create(&info.path).context("Failed to create config file")?,
|
||||||
|
);
|
||||||
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
|
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
|
||||||
match ext {
|
match ext {
|
||||||
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
|
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
|
||||||
"yml" | "yaml" => {
|
|
||||||
serde_yaml::to_writer(&mut writer, config).context("Failed to write YAML")
|
|
||||||
}
|
|
||||||
_ => Err(anyhow!("Unknown config file extension: {ext}")),
|
_ => Err(anyhow!("Unknown config file extension: {ext}")),
|
||||||
}?;
|
}?;
|
||||||
let file = writer.into_inner().context("Failed to flush file")?;
|
let file = writer.into_inner().context("Failed to flush file")?;
|
||||||
let metadata = file.metadata().context("Failed to get file metadata")?;
|
let metadata = file.metadata().context("Failed to get file metadata")?;
|
||||||
let ts = FileTime::from_last_modification_time(&metadata);
|
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||||
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
|
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
||||||
let Some(min_version) = &config.min_version else { return Ok(()) };
|
let Some(min_version) = &config.min_version else { return Ok(()) };
|
||||||
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
||||||
|
.map_err(|e| anyhow::Error::msg(e.to_string()))
|
||||||
.context("Failed to parse package version")?;
|
.context("Failed to parse package version")?;
|
||||||
let min_version = semver::Version::parse(min_version).context("Failed to parse min_version")?;
|
let min_version = semver::Version::parse(min_version)
|
||||||
|
.map_err(|e| anyhow::Error::msg(e.to_string()))
|
||||||
|
.context("Failed to parse min_version")?;
|
||||||
if version >= min_version {
|
if version >= min_version {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -273,15 +278,12 @@ fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_yml_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
|
#[cfg(feature = "std")]
|
||||||
Ok(serde_yaml::from_reader(reader)?)
|
fn read_json_config<R: std::io::Read>(reader: &mut R) -> Result<ProjectConfig> {
|
||||||
}
|
|
||||||
|
|
||||||
fn read_json_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
|
|
||||||
Ok(serde_json::from_reader(reader)?)
|
Ok(serde_json::from_reader(reader)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> {
|
pub fn build_globset(vec: &[Glob]) -> Result<GlobSet, globset::Error> {
|
||||||
let mut builder = GlobSetBuilder::new();
|
let mut builder = GlobSetBuilder::new();
|
||||||
for glob in vec {
|
for glob in vec {
|
||||||
builder.add(glob.clone());
|
builder.add(glob.clone());
|
||||||
|
65
objdiff-core/src/config/path.rs
Normal file
65
objdiff-core/src/config/path.rs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// For argp::FromArgs
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn platform_path(value: &str) -> Result<typed_path::Utf8PlatformPathBuf, String> {
|
||||||
|
Ok(typed_path::Utf8PlatformPathBuf::from(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8PlatformPath`].
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn check_path(
|
||||||
|
path: &std::path::Path,
|
||||||
|
) -> Result<&typed_path::Utf8PlatformPath, core::str::Utf8Error> {
|
||||||
|
typed_path::Utf8PlatformPath::from_bytes_path(typed_path::PlatformPath::new(
|
||||||
|
path.as_os_str().as_encoded_bytes(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8NativePathBuf`].
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn check_path_buf(
|
||||||
|
path: std::path::PathBuf,
|
||||||
|
) -> Result<typed_path::Utf8PlatformPathBuf, alloc::string::FromUtf8Error> {
|
||||||
|
typed_path::Utf8PlatformPathBuf::from_bytes_path_buf(typed_path::PlatformPathBuf::from(
|
||||||
|
path.into_os_string().into_encoded_bytes(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub mod unix_path_serde_option {
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
use typed_path::Utf8UnixPathBuf;
|
||||||
|
|
||||||
|
pub fn serialize<S>(path: &Option<Utf8UnixPathBuf>, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: Serializer {
|
||||||
|
if let Some(path) = path {
|
||||||
|
s.serialize_some(path.as_str())
|
||||||
|
} else {
|
||||||
|
s.serialize_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8UnixPathBuf>, D::Error>
|
||||||
|
where D: Deserializer<'de> {
|
||||||
|
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8UnixPathBuf::from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "serde", feature = "std"))]
|
||||||
|
pub mod platform_path_serde_option {
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
|
pub fn serialize<S>(path: &Option<Utf8PlatformPathBuf>, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: Serializer {
|
||||||
|
if let Some(path) = path {
|
||||||
|
s.serialize_some(path.as_str())
|
||||||
|
} else {
|
||||||
|
s.serialize_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8PlatformPathBuf>, D::Error>
|
||||||
|
where D: Deserializer<'de> {
|
||||||
|
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8PlatformPathBuf::from))
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,12 @@
|
|||||||
use std::{cmp::max, collections::BTreeMap};
|
use alloc::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use similar::{capture_diff_slices_deadline, Algorithm};
|
use similar::{capture_diff_slices, Algorithm};
|
||||||
|
|
||||||
use super::FunctionRelocDiffs;
|
use super::FunctionRelocDiffs;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -118,8 +123,7 @@ fn diff_instructions(
|
|||||||
left_code: &ProcessCodeResult,
|
left_code: &ProcessCodeResult,
|
||||||
right_code: &ProcessCodeResult,
|
right_code: &ProcessCodeResult,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ops =
|
let ops = capture_diff_slices(Algorithm::Patience, &left_code.ops, &right_code.ops);
|
||||||
capture_diff_slices_deadline(Algorithm::Patience, &left_code.ops, &right_code.ops, None);
|
|
||||||
if ops.is_empty() {
|
if ops.is_empty() {
|
||||||
left_diff.extend(
|
left_diff.extend(
|
||||||
left_code
|
left_code
|
||||||
@ -138,7 +142,7 @@ fn diff_instructions(
|
|||||||
|
|
||||||
for op in ops {
|
for op in ops {
|
||||||
let (_tag, left_range, right_range) = op.as_tag_tuple();
|
let (_tag, left_range, right_range) = op.as_tag_tuple();
|
||||||
let len = max(left_range.len(), right_range.len());
|
let len = left_range.len().max(right_range.len());
|
||||||
left_diff.extend(
|
left_diff.extend(
|
||||||
left_code.insts[left_range.clone()]
|
left_code.insts[left_range.clone()]
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
use std::{
|
use alloc::{vec, vec::Vec};
|
||||||
cmp::{max, min, Ordering},
|
use core::{cmp::Ordering, ops::Range};
|
||||||
ops::Range,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use similar::{capture_diff_slices_deadline, get_diff_ratio, Algorithm};
|
use similar::{capture_diff_slices, get_diff_ratio, Algorithm};
|
||||||
|
|
||||||
use super::code::{address_eq, section_name_eq};
|
use super::code::{address_eq, section_name_eq};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -142,7 +140,7 @@ pub fn diff_data_section(
|
|||||||
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size);
|
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size);
|
||||||
let left_data = &left.data[..left_max as usize];
|
let left_data = &left.data[..left_max as usize];
|
||||||
let right_data = &right.data[..right_max as usize];
|
let right_data = &right.data[..right_max as usize];
|
||||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
|
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||||
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
||||||
|
|
||||||
let mut left_diff = Vec::<ObjDataDiff>::new();
|
let mut left_diff = Vec::<ObjDataDiff>::new();
|
||||||
@ -151,27 +149,27 @@ pub fn diff_data_section(
|
|||||||
let (tag, left_range, right_range) = op.as_tag_tuple();
|
let (tag, left_range, right_range) = op.as_tag_tuple();
|
||||||
let left_len = left_range.len();
|
let left_len = left_range.len();
|
||||||
let right_len = right_range.len();
|
let right_len = right_range.len();
|
||||||
let mut len = max(left_len, right_len);
|
let mut len = left_len.max(right_len);
|
||||||
let kind = match tag {
|
let kind = match tag {
|
||||||
similar::DiffTag::Equal => ObjDataDiffKind::None,
|
similar::DiffTag::Equal => ObjDataDiffKind::None,
|
||||||
similar::DiffTag::Delete => ObjDataDiffKind::Delete,
|
similar::DiffTag::Delete => ObjDataDiffKind::Delete,
|
||||||
similar::DiffTag::Insert => ObjDataDiffKind::Insert,
|
similar::DiffTag::Insert => ObjDataDiffKind::Insert,
|
||||||
similar::DiffTag::Replace => {
|
similar::DiffTag::Replace => {
|
||||||
// Ensure replacements are equal length
|
// Ensure replacements are equal length
|
||||||
len = min(left_len, right_len);
|
len = left_len.min(right_len);
|
||||||
ObjDataDiffKind::Replace
|
ObjDataDiffKind::Replace
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let left_data = &left.data[left_range];
|
let left_data = &left.data[left_range];
|
||||||
let right_data = &right.data[right_range];
|
let right_data = &right.data[right_range];
|
||||||
left_diff.push(ObjDataDiff {
|
left_diff.push(ObjDataDiff {
|
||||||
data: left_data[..min(len, left_data.len())].to_vec(),
|
data: left_data[..len.min(left_data.len())].to_vec(),
|
||||||
kind,
|
kind,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_diff.push(ObjDataDiff {
|
||||||
data: right_data[..min(len, right_data.len())].to_vec(),
|
data: right_data[..len.min(right_data.len())].to_vec(),
|
||||||
kind,
|
kind,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -283,7 +281,7 @@ pub fn diff_data_symbol(
|
|||||||
right_range,
|
right_range,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
|
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||||
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
|
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
|
||||||
|
|
||||||
let mut match_ratio = bytes_match_ratio;
|
let mut match_ratio = bytes_match_ratio;
|
||||||
@ -375,7 +373,7 @@ pub fn diff_bss_section(
|
|||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
||||||
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
||||||
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
||||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, &left_sizes, &right_sizes, None);
|
let ops = capture_diff_slices(Algorithm::Patience, &left_sizes, &right_sizes);
|
||||||
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
|
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
|
||||||
|
|
||||||
// Use the highest match percent between two options:
|
// Use the highest match percent between two options:
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use alloc::string::{String, ToString};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::{ObjInsArgDiff, ObjInsDiff},
|
diff::{ObjInsArgDiff, ObjInsDiff},
|
||||||
obj::{ObjInsArg, ObjInsArgValue, ObjReloc, ObjSymbol},
|
obj::{ObjInsArg, ObjInsArgValue, ObjReloc, ObjSymbol},
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
use std::{collections::HashSet, ops::Range};
|
use alloc::{
|
||||||
|
collections::{BTreeMap, BTreeSet},
|
||||||
|
string::String,
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::ops::Range;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::SymbolMappings,
|
|
||||||
diff::{
|
diff::{
|
||||||
code::{diff_code, no_diff_code, process_code_symbol},
|
code::{diff_code, no_diff_code, process_code_symbol},
|
||||||
data::{
|
data::{
|
||||||
@ -473,12 +478,11 @@ struct SectionMatch {
|
|||||||
section_kind: ObjSectionKind,
|
section_kind: ObjSectionKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
#[serde(default)]
|
|
||||||
pub struct MappingConfig {
|
pub struct MappingConfig {
|
||||||
/// Manual symbol mappings
|
/// Manual symbol mappings
|
||||||
pub mappings: SymbolMappings,
|
pub mappings: BTreeMap<String, String>,
|
||||||
/// The right object symbol name that we're selecting a left symbol for
|
/// The right object symbol name that we're selecting a left symbol for
|
||||||
pub selecting_left: Option<String>,
|
pub selecting_left: Option<String>,
|
||||||
/// The left object symbol name that we're selecting a right symbol for
|
/// The left object symbol name that we're selecting a right symbol for
|
||||||
@ -500,8 +504,8 @@ fn apply_symbol_mappings(
|
|||||||
left: &ObjInfo,
|
left: &ObjInfo,
|
||||||
right: &ObjInfo,
|
right: &ObjInfo,
|
||||||
mapping_config: &MappingConfig,
|
mapping_config: &MappingConfig,
|
||||||
left_used: &mut HashSet<SymbolRef>,
|
left_used: &mut BTreeSet<SymbolRef>,
|
||||||
right_used: &mut HashSet<SymbolRef>,
|
right_used: &mut BTreeSet<SymbolRef>,
|
||||||
matches: &mut Vec<SymbolMatch>,
|
matches: &mut Vec<SymbolMatch>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// If we're selecting a symbol to use as a comparison, mark it as used
|
// If we're selecting a symbol to use as a comparison, mark it as used
|
||||||
@ -563,8 +567,8 @@ fn matching_symbols(
|
|||||||
mappings: &MappingConfig,
|
mappings: &MappingConfig,
|
||||||
) -> Result<Vec<SymbolMatch>> {
|
) -> Result<Vec<SymbolMatch>> {
|
||||||
let mut matches = Vec::new();
|
let mut matches = Vec::new();
|
||||||
let mut left_used = HashSet::new();
|
let mut left_used = BTreeSet::new();
|
||||||
let mut right_used = HashSet::new();
|
let mut right_used = BTreeSet::new();
|
||||||
if let Some(left) = left {
|
if let Some(left) = left {
|
||||||
if let Some(right) = right {
|
if let Some(right) = right {
|
||||||
apply_symbol_mappings(
|
apply_symbol_mappings(
|
||||||
@ -645,7 +649,7 @@ fn matching_symbols(
|
|||||||
fn unmatched_symbols<'section, 'used>(
|
fn unmatched_symbols<'section, 'used>(
|
||||||
section: &'section ObjSection,
|
section: &'section ObjSection,
|
||||||
section_idx: usize,
|
section_idx: usize,
|
||||||
used: Option<&'used HashSet<SymbolRef>>,
|
used: Option<&'used BTreeSet<SymbolRef>>,
|
||||||
) -> impl Iterator<Item = (usize, &'section ObjSymbol)> + 'used
|
) -> impl Iterator<Item = (usize, &'section ObjSymbol)> + 'used
|
||||||
where
|
where
|
||||||
'section: 'used,
|
'section: 'used,
|
||||||
@ -660,7 +664,7 @@ fn find_symbol(
|
|||||||
obj: Option<&ObjInfo>,
|
obj: Option<&ObjInfo>,
|
||||||
in_symbol: &ObjSymbol,
|
in_symbol: &ObjSymbol,
|
||||||
in_section: &ObjSection,
|
in_section: &ObjSection,
|
||||||
used: Option<&HashSet<SymbolRef>>,
|
used: Option<&BTreeSet<SymbolRef>>,
|
||||||
) -> Option<SymbolRef> {
|
) -> Option<SymbolRef> {
|
||||||
let obj = obj?;
|
let obj = obj?;
|
||||||
// Try to find an exact name match
|
// Try to find an exact name match
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::{fs, path::PathBuf, sync::mpsc::Receiver, task::Waker};
|
use std::{fs, sync::mpsc::Receiver, task::Waker};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
build::{run_make, BuildConfig, BuildStatus},
|
build::{run_make, BuildConfig, BuildStatus},
|
||||||
@ -10,7 +11,7 @@ use crate::{
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CreateScratchConfig {
|
pub struct CreateScratchConfig {
|
||||||
pub build_config: BuildConfig,
|
pub build_config: BuildConfig,
|
||||||
pub context_path: Option<PathBuf>,
|
pub context_path: Option<Utf8UnixPathBuf>,
|
||||||
pub build_context: bool,
|
pub build_context: bool,
|
||||||
|
|
||||||
// Scratch fields
|
// Scratch fields
|
||||||
@ -18,7 +19,7 @@ pub struct CreateScratchConfig {
|
|||||||
pub platform: String,
|
pub platform: String,
|
||||||
pub compiler_flags: String,
|
pub compiler_flags: String,
|
||||||
pub function_name: String,
|
pub function_name: String,
|
||||||
pub target_obj: PathBuf,
|
pub target_obj: Utf8PlatformPathBuf,
|
||||||
pub preset_id: Option<u32>,
|
pub preset_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,26 +48,25 @@ fn run_create_scratch(
|
|||||||
if let Some(context_path) = &config.context_path {
|
if let Some(context_path) = &config.context_path {
|
||||||
if config.build_context {
|
if config.build_context {
|
||||||
update_status(status, "Building context".to_string(), 0, 2, &cancel)?;
|
update_status(status, "Building context".to_string(), 0, 2, &cancel)?;
|
||||||
match run_make(&config.build_config, context_path) {
|
match run_make(&config.build_config, context_path.as_ref()) {
|
||||||
BuildStatus { success: true, .. } => {}
|
BuildStatus { success: true, .. } => {}
|
||||||
BuildStatus { success: false, stdout, stderr, .. } => {
|
BuildStatus { success: false, stdout, stderr, .. } => {
|
||||||
bail!("Failed to build context:\n{stdout}\n{stderr}")
|
bail!("Failed to build context:\n{stdout}\n{stderr}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let context_path = project_dir.join(context_path);
|
let context_path = project_dir.join(context_path.with_platform_encoding());
|
||||||
context = Some(
|
context = Some(
|
||||||
fs::read_to_string(&context_path)
|
fs::read_to_string(&context_path)
|
||||||
.map_err(|e| anyhow!("Failed to read {}: {}", context_path.display(), e))?,
|
.map_err(|e| anyhow!("Failed to read {}: {}", context_path, e))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?;
|
update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?;
|
||||||
let diff_flags = [format!("--disassemble={}", config.function_name)];
|
let diff_flags = [format!("--disassemble={}", config.function_name)];
|
||||||
let diff_flags = serde_json::to_string(&diff_flags)?;
|
let diff_flags = serde_json::to_string(&diff_flags)?;
|
||||||
let obj_path = project_dir.join(&config.target_obj);
|
let file = reqwest::blocking::multipart::Part::file(&config.target_obj)
|
||||||
let file = reqwest::blocking::multipart::Part::file(&obj_path)
|
.with_context(|| format!("Failed to open {}", config.target_obj))?;
|
||||||
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
|
|
||||||
let mut form = reqwest::blocking::multipart::Form::new()
|
let mut form = reqwest::blocking::multipart::Form::new()
|
||||||
.text("compiler", config.compiler.clone())
|
.text("compiler", config.compiler.clone())
|
||||||
.text("platform", config.platform.clone())
|
.text("platform", config.platform.clone())
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use std::{path::PathBuf, sync::mpsc::Receiver, task::Waker};
|
use std::{sync::mpsc::Receiver, task::Waker};
|
||||||
|
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
build::{run_make, BuildConfig, BuildStatus},
|
build::{run_make, BuildConfig, BuildStatus},
|
||||||
@ -14,8 +15,8 @@ pub struct ObjDiffConfig {
|
|||||||
pub build_config: BuildConfig,
|
pub build_config: BuildConfig,
|
||||||
pub build_base: bool,
|
pub build_base: bool,
|
||||||
pub build_target: bool,
|
pub build_target: bool,
|
||||||
pub target_path: Option<PathBuf>,
|
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub diff_obj_config: DiffObjConfig,
|
pub diff_obj_config: DiffObjConfig,
|
||||||
pub mapping_config: MappingConfig,
|
pub mapping_config: MappingConfig,
|
||||||
}
|
}
|
||||||
@ -43,20 +44,12 @@ fn run_build(
|
|||||||
.ok_or_else(|| Error::msg("Missing project dir"))?;
|
.ok_or_else(|| Error::msg("Missing project dir"))?;
|
||||||
if let Some(target_path) = &config.target_path {
|
if let Some(target_path) = &config.target_path {
|
||||||
target_path_rel = Some(target_path.strip_prefix(project_dir).map_err(|_| {
|
target_path_rel = Some(target_path.strip_prefix(project_dir).map_err(|_| {
|
||||||
anyhow!(
|
anyhow!("Target path '{}' doesn't begin with '{}'", target_path, project_dir)
|
||||||
"Target path '{}' doesn't begin with '{}'",
|
|
||||||
target_path.display(),
|
|
||||||
project_dir.display()
|
|
||||||
)
|
|
||||||
})?);
|
})?);
|
||||||
}
|
}
|
||||||
if let Some(base_path) = &config.base_path {
|
if let Some(base_path) = &config.base_path {
|
||||||
base_path_rel = Some(base_path.strip_prefix(project_dir).map_err(|_| {
|
base_path_rel = Some(base_path.strip_prefix(project_dir).map_err(|_| {
|
||||||
anyhow!(
|
anyhow!("Base path '{}' doesn't begin with '{}'", base_path, project_dir)
|
||||||
"Base path '{}' doesn't begin with '{}'",
|
|
||||||
base_path.display(),
|
|
||||||
project_dir.display()
|
|
||||||
)
|
|
||||||
})?);
|
})?);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -80,13 +73,13 @@ fn run_build(
|
|||||||
Some(target_path_rel) if config.build_target => {
|
Some(target_path_rel) if config.build_target => {
|
||||||
update_status(
|
update_status(
|
||||||
context,
|
context,
|
||||||
format!("Building target {}", target_path_rel.display()),
|
format!("Building target {}", target_path_rel),
|
||||||
step_idx,
|
step_idx,
|
||||||
total,
|
total,
|
||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
step_idx += 1;
|
step_idx += 1;
|
||||||
run_make(&config.build_config, target_path_rel)
|
run_make(&config.build_config, target_path_rel.as_ref())
|
||||||
}
|
}
|
||||||
_ => BuildStatus::default(),
|
_ => BuildStatus::default(),
|
||||||
};
|
};
|
||||||
@ -95,13 +88,13 @@ fn run_build(
|
|||||||
Some(base_path_rel) if config.build_base => {
|
Some(base_path_rel) if config.build_base => {
|
||||||
update_status(
|
update_status(
|
||||||
context,
|
context,
|
||||||
format!("Building base {}", base_path_rel.display()),
|
format!("Building base {}", base_path_rel),
|
||||||
step_idx,
|
step_idx,
|
||||||
total,
|
total,
|
||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
step_idx += 1;
|
step_idx += 1;
|
||||||
run_make(&config.build_config, base_path_rel)
|
run_make(&config.build_config, base_path_rel.as_ref())
|
||||||
}
|
}
|
||||||
_ => BuildStatus::default(),
|
_ => BuildStatus::default(),
|
||||||
};
|
};
|
||||||
@ -112,18 +105,18 @@ fn run_build(
|
|||||||
Some(target_path) if first_status.success => {
|
Some(target_path) if first_status.success => {
|
||||||
update_status(
|
update_status(
|
||||||
context,
|
context,
|
||||||
format!("Loading target {}", target_path.display()),
|
format!("Loading target {}", target_path),
|
||||||
step_idx,
|
step_idx,
|
||||||
total,
|
total,
|
||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
step_idx += 1;
|
step_idx += 1;
|
||||||
match read::read(target_path, &config.diff_obj_config) {
|
match read::read(target_path.as_ref(), &config.diff_obj_config) {
|
||||||
Ok(obj) => Some(obj),
|
Ok(obj) => Some(obj),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
first_status = BuildStatus {
|
first_status = BuildStatus {
|
||||||
success: false,
|
success: false,
|
||||||
stdout: format!("Loading object '{}'", target_path.display()),
|
stdout: format!("Loading object '{}'", target_path),
|
||||||
stderr: format!("{:#}", e),
|
stderr: format!("{:#}", e),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@ -142,18 +135,18 @@ fn run_build(
|
|||||||
Some(base_path) if second_status.success => {
|
Some(base_path) if second_status.success => {
|
||||||
update_status(
|
update_status(
|
||||||
context,
|
context,
|
||||||
format!("Loading base {}", base_path.display()),
|
format!("Loading base {}", base_path),
|
||||||
step_idx,
|
step_idx,
|
||||||
total,
|
total,
|
||||||
&cancel,
|
&cancel,
|
||||||
)?;
|
)?;
|
||||||
step_idx += 1;
|
step_idx += 1;
|
||||||
match read::read(base_path, &config.diff_obj_config) {
|
match read::read(base_path.as_ref(), &config.diff_obj_config) {
|
||||||
Ok(obj) => Some(obj),
|
Ok(obj) => Some(obj),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
second_status = BuildStatus {
|
second_status = BuildStatus {
|
||||||
success: false,
|
success: false,
|
||||||
stdout: format!("Loading object '{}'", base_path.display()),
|
stdout: format!("Loading object '{}'", base_path),
|
||||||
stderr: format!("{:#}", e),
|
stderr: format!("{:#}", e),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod arch;
|
pub mod arch;
|
||||||
#[cfg(feature = "bindings")]
|
#[cfg(feature = "bindings")]
|
||||||
@ -14,3 +17,5 @@ pub mod jobs;
|
|||||||
pub mod obj;
|
pub mod obj;
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
#[cfg(feature = "wasm")]
|
||||||
|
pub mod wasm;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
pub mod read;
|
pub mod read;
|
||||||
pub mod split_meta;
|
pub mod split_meta;
|
||||||
|
|
||||||
use std::{borrow::Cow, collections::BTreeMap, fmt, path::PathBuf};
|
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, string::String, vec::Vec};
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
use filetime::FileTime;
|
|
||||||
use flagset::{flags, FlagSet};
|
use flagset::{flags, FlagSet};
|
||||||
use object::RelocationFlags;
|
use object::RelocationFlags;
|
||||||
use split_meta::SplitMeta;
|
use split_meta::SplitMeta;
|
||||||
@ -152,8 +152,9 @@ pub struct ObjSymbol {
|
|||||||
|
|
||||||
pub struct ObjInfo {
|
pub struct ObjInfo {
|
||||||
pub arch: Box<dyn ObjArch>,
|
pub arch: Box<dyn ObjArch>,
|
||||||
pub path: Option<PathBuf>,
|
pub path: Option<String>,
|
||||||
pub timestamp: Option<FileTime>,
|
#[cfg(feature = "std")]
|
||||||
|
pub timestamp: Option<filetime::FileTime>,
|
||||||
pub sections: Vec<ObjSection>,
|
pub sections: Vec<ObjSection>,
|
||||||
/// Common BSS symbols
|
/// Common BSS symbols
|
||||||
pub common: Vec<ObjSymbol>,
|
pub common: Vec<ObjSymbol>,
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
use std::{
|
use alloc::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
fs,
|
format,
|
||||||
io::Cursor,
|
string::{String, ToString},
|
||||||
mem::size_of,
|
vec,
|
||||||
path::Path,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
use filetime::FileTime;
|
|
||||||
use flagset::Flags;
|
use flagset::Flags;
|
||||||
use object::{
|
use object::{
|
||||||
endian::LittleEndian as LE,
|
endian::LittleEndian as LE,
|
||||||
@ -160,7 +159,7 @@ fn symbols_by_section(
|
|||||||
section: &ObjSection,
|
section: &ObjSection,
|
||||||
section_symbols: &[Symbol<'_, '_>],
|
section_symbols: &[Symbol<'_, '_>],
|
||||||
split_meta: Option<&SplitMeta>,
|
split_meta: Option<&SplitMeta>,
|
||||||
name_counts: &mut HashMap<String, u32>,
|
name_counts: &mut BTreeMap<String, u32>,
|
||||||
) -> Result<Vec<ObjSymbol>> {
|
) -> Result<Vec<ObjSymbol>> {
|
||||||
let mut result = Vec::<ObjSymbol>::new();
|
let mut result = Vec::<ObjSymbol>::new();
|
||||||
for symbol in section_symbols {
|
for symbol in section_symbols {
|
||||||
@ -377,33 +376,37 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
|||||||
// DWARF 1.1
|
// DWARF 1.1
|
||||||
if let Some(section) = obj_file.section_by_name(".line") {
|
if let Some(section) = obj_file.section_by_name(".line") {
|
||||||
let data = section.uncompressed_data()?;
|
let data = section.uncompressed_data()?;
|
||||||
let mut reader = Cursor::new(data.as_ref());
|
let mut reader: &[u8] = data.as_ref();
|
||||||
|
|
||||||
let mut text_sections = obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
let mut text_sections = obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
||||||
while reader.position() < data.len() as u64 {
|
while !reader.is_empty() {
|
||||||
let text_section_index = text_sections
|
let text_section_index = text_sections
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| anyhow!("Next text section not found for line info"))?
|
.ok_or_else(|| anyhow!("Next text section not found for line info"))?
|
||||||
.index()
|
.index()
|
||||||
.0;
|
.0;
|
||||||
let start = reader.position();
|
|
||||||
let size = read_u32(obj_file, &mut reader)?;
|
let mut section_data = &reader[..];
|
||||||
let base_address = read_u32(obj_file, &mut reader)? as u64;
|
let size = read_u32(obj_file, &mut section_data)? as usize;
|
||||||
|
if size > reader.len() {
|
||||||
|
bail!("Line info size {size} exceeds remaining size {}", reader.len());
|
||||||
|
}
|
||||||
|
(section_data, reader) = reader.split_at(size);
|
||||||
|
|
||||||
|
let base_address = read_u32(obj_file, &mut section_data)? as u64;
|
||||||
let Some(out_section) =
|
let Some(out_section) =
|
||||||
sections.iter_mut().find(|s| s.orig_index == text_section_index)
|
sections.iter_mut().find(|s| s.orig_index == text_section_index)
|
||||||
else {
|
else {
|
||||||
// Skip line info for sections we filtered out
|
// Skip line info for sections we filtered out
|
||||||
reader.set_position(start + size as u64);
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let end = start + size as u64;
|
while !section_data.is_empty() {
|
||||||
while reader.position() < end {
|
let line_number = read_u32(obj_file, &mut section_data)?;
|
||||||
let line_number = read_u32(obj_file, &mut reader)?;
|
let statement_pos = read_u16(obj_file, &mut section_data)?;
|
||||||
let statement_pos = read_u16(obj_file, &mut reader)?;
|
|
||||||
if statement_pos != 0xFFFF {
|
if statement_pos != 0xFFFF {
|
||||||
log::warn!("Unhandled statement pos {}", statement_pos);
|
log::warn!("Unhandled statement pos {}", statement_pos);
|
||||||
}
|
}
|
||||||
let address_delta = read_u32(obj_file, &mut reader)? as u64;
|
let address_delta = read_u32(obj_file, &mut section_data)? as u64;
|
||||||
out_section.line_info.insert(base_address + address_delta, line_number);
|
out_section.line_info.insert(base_address + address_delta, line_number);
|
||||||
log::debug!("Line: {:#x} -> {}", base_address + address_delta, line_number);
|
log::debug!("Line: {:#x} -> {}", base_address + address_delta, line_number);
|
||||||
}
|
}
|
||||||
@ -413,22 +416,24 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
|||||||
// DWARF 2+
|
// DWARF 2+
|
||||||
#[cfg(feature = "dwarf")]
|
#[cfg(feature = "dwarf")]
|
||||||
{
|
{
|
||||||
|
fn gimli_error(e: gimli::Error) -> anyhow::Error { anyhow::anyhow!("DWARF error: {e:?}") }
|
||||||
let dwarf_cow = gimli::DwarfSections::load(|id| {
|
let dwarf_cow = gimli::DwarfSections::load(|id| {
|
||||||
Ok::<_, gimli::Error>(
|
Ok::<_, gimli::Error>(
|
||||||
obj_file
|
obj_file
|
||||||
.section_by_name(id.name())
|
.section_by_name(id.name())
|
||||||
.and_then(|section| section.uncompressed_data().ok())
|
.and_then(|section| section.uncompressed_data().ok())
|
||||||
.unwrap_or(std::borrow::Cow::Borrowed(&[][..])),
|
.unwrap_or(alloc::borrow::Cow::Borrowed(&[][..])),
|
||||||
)
|
)
|
||||||
})?;
|
})
|
||||||
|
.map_err(gimli_error)?;
|
||||||
let endian = match obj_file.endianness() {
|
let endian = match obj_file.endianness() {
|
||||||
object::Endianness::Little => gimli::RunTimeEndian::Little,
|
object::Endianness::Little => gimli::RunTimeEndian::Little,
|
||||||
object::Endianness::Big => gimli::RunTimeEndian::Big,
|
object::Endianness::Big => gimli::RunTimeEndian::Big,
|
||||||
};
|
};
|
||||||
let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian));
|
let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian));
|
||||||
let mut iter = dwarf.units();
|
let mut iter = dwarf.units();
|
||||||
if let Some(header) = iter.next()? {
|
if let Some(header) = iter.next().map_err(gimli_error)? {
|
||||||
let unit = dwarf.unit(header)?;
|
let unit = dwarf.unit(header).map_err(gimli_error)?;
|
||||||
if let Some(program) = unit.line_program.clone() {
|
if let Some(program) = unit.line_program.clone() {
|
||||||
let mut text_sections =
|
let mut text_sections =
|
||||||
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
||||||
@ -438,7 +443,7 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
|||||||
.map(|s| &mut s.line_info);
|
.map(|s| &mut s.line_info);
|
||||||
|
|
||||||
let mut rows = program.rows();
|
let mut rows = program.rows();
|
||||||
while let Some((_header, row)) = rows.next_row()? {
|
while let Some((_header, row)) = rows.next_row().map_err(gimli_error)? {
|
||||||
if let (Some(line), Some(lines)) = (row.line(), &mut lines) {
|
if let (Some(line), Some(lines)) = (row.line(), &mut lines) {
|
||||||
lines.insert(row.address(), line.get() as u32);
|
lines.insert(row.address(), line.get() as u32);
|
||||||
}
|
}
|
||||||
@ -453,7 +458,7 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if iter.next()?.is_some() {
|
if iter.next().map_err(gimli_error)?.is_some() {
|
||||||
log::warn!("Multiple units found in DWARF data, only processing the first");
|
log::warn!("Multiple units found in DWARF data, only processing the first");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -638,7 +643,7 @@ fn combine_sections(section: ObjSection, combine: ObjSection) -> Result<ObjSecti
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn combine_data_sections(sections: &mut Vec<ObjSection>) -> Result<()> {
|
fn combine_data_sections(sections: &mut Vec<ObjSection>) -> Result<()> {
|
||||||
let names_to_combine: HashSet<_> = sections
|
let names_to_combine: BTreeSet<_> = sections
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|s| s.kind == ObjSectionKind::Data)
|
.filter(|s| s.kind == ObjSectionKind::Data)
|
||||||
.map(|s| s.name.clone())
|
.map(|s| s.name.clone())
|
||||||
@ -677,14 +682,15 @@ fn combine_data_sections(sections: &mut Vec<ObjSection>) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(obj_path: &Path, config: &DiffObjConfig) -> Result<ObjInfo> {
|
#[cfg(feature = "std")]
|
||||||
|
pub fn read(obj_path: &std::path::Path, config: &DiffObjConfig) -> Result<ObjInfo> {
|
||||||
let (data, timestamp) = {
|
let (data, timestamp) = {
|
||||||
let file = fs::File::open(obj_path)?;
|
let file = std::fs::File::open(obj_path)?;
|
||||||
let timestamp = FileTime::from_last_modification_time(&file.metadata()?);
|
let timestamp = filetime::FileTime::from_last_modification_time(&file.metadata()?);
|
||||||
(unsafe { memmap2::Mmap::map(&file) }?, timestamp)
|
(unsafe { memmap2::Mmap::map(&file) }?, timestamp)
|
||||||
};
|
};
|
||||||
let mut obj = parse(&data, config)?;
|
let mut obj = parse(&data, config)?;
|
||||||
obj.path = Some(obj_path.to_owned());
|
obj.path = Some(obj_path.to_string_lossy().into_owned());
|
||||||
obj.timestamp = Some(timestamp);
|
obj.timestamp = Some(timestamp);
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
}
|
}
|
||||||
@ -710,7 +716,7 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
|
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
|
||||||
let mut section_name_counts: HashMap<String, u32> = HashMap::new();
|
let mut section_name_counts: BTreeMap<String, u32> = BTreeMap::new();
|
||||||
for section in &mut sections {
|
for section in &mut sections {
|
||||||
section.symbols = symbols_by_section(
|
section.symbols = symbols_by_section(
|
||||||
arch.as_ref(),
|
arch.as_ref(),
|
||||||
@ -733,12 +739,21 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
|
|||||||
}
|
}
|
||||||
line_info(&obj_file, &mut sections, data)?;
|
line_info(&obj_file, &mut sections, data)?;
|
||||||
let common = common_symbols(arch.as_ref(), &obj_file, split_meta.as_ref())?;
|
let common = common_symbols(arch.as_ref(), &obj_file, split_meta.as_ref())?;
|
||||||
Ok(ObjInfo { arch, path: None, timestamp: None, sections, common, split_meta })
|
Ok(ObjInfo {
|
||||||
|
arch,
|
||||||
|
path: None,
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
timestamp: None,
|
||||||
|
sections,
|
||||||
|
common,
|
||||||
|
split_meta,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_function(obj_path: &Path, symbol_name: &str) -> Result<bool> {
|
#[cfg(feature = "std")]
|
||||||
|
pub fn has_function(obj_path: &std::path::Path, symbol_name: &str) -> Result<bool> {
|
||||||
let data = {
|
let data = {
|
||||||
let file = fs::File::open(obj_path)?;
|
let file = std::fs::File::open(obj_path)?;
|
||||||
unsafe { memmap2::Mmap::map(&file) }?
|
unsafe { memmap2::Mmap::map(&file) }?
|
||||||
};
|
};
|
||||||
Ok(File::parse(&*data)?
|
Ok(File::parse(&*data)?
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::{io, io::Write};
|
use alloc::{string::String, vec, vec::Vec};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use object::{elf::SHT_NOTE, Endian, ObjectSection};
|
use object::{elf::SHT_NOTE, Endian, ObjectSection};
|
||||||
|
|
||||||
pub const SPLITMETA_SECTION: &str = ".note.split";
|
pub const SPLITMETA_SECTION: &str = ".note.split";
|
||||||
@ -27,10 +28,10 @@ const NT_SPLIT_MODULE_ID: u32 = u32::from_be_bytes(*b"MODI");
|
|||||||
const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT");
|
const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT");
|
||||||
|
|
||||||
impl SplitMeta {
|
impl SplitMeta {
|
||||||
pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> io::Result<Self>
|
pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> Result<Self>
|
||||||
where E: Endian {
|
where E: Endian {
|
||||||
let mut result = SplitMeta::default();
|
let mut result = SplitMeta::default();
|
||||||
let data = section.uncompressed_data().map_err(object_io_error)?;
|
let data = section.uncompressed_data().map_err(object_error)?;
|
||||||
let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?;
|
let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?;
|
||||||
while let Some(note) = iter.next(e)? {
|
while let Some(note) = iter.next(e)? {
|
||||||
if note.name != ELF_NOTE_SPLIT {
|
if note.name != ELF_NOTE_SPLIT {
|
||||||
@ -39,19 +40,18 @@ impl SplitMeta {
|
|||||||
match note.n_type {
|
match note.n_type {
|
||||||
NT_SPLIT_GENERATOR => {
|
NT_SPLIT_GENERATOR => {
|
||||||
let string = String::from_utf8(note.desc.to_vec())
|
let string = String::from_utf8(note.desc.to_vec())
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
.map_err(|e| anyhow::Error::from(e))?;
|
||||||
result.generator = Some(string);
|
result.generator = Some(string);
|
||||||
}
|
}
|
||||||
NT_SPLIT_MODULE_NAME => {
|
NT_SPLIT_MODULE_NAME => {
|
||||||
let string = String::from_utf8(note.desc.to_vec())
|
let string = String::from_utf8(note.desc.to_vec())
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
.map_err(|e| anyhow::Error::from(e))?;
|
||||||
result.module_name = Some(string);
|
result.module_name = Some(string);
|
||||||
}
|
}
|
||||||
NT_SPLIT_MODULE_ID => {
|
NT_SPLIT_MODULE_ID => {
|
||||||
result.module_id =
|
result.module_id = Some(e.read_u32_bytes(
|
||||||
Some(e.read_u32_bytes(note.desc.try_into().map_err(|_| {
|
note.desc.try_into().map_err(|_| anyhow!("Invalid module ID size"))?,
|
||||||
io::Error::new(io::ErrorKind::InvalidData, "Invalid module ID size")
|
));
|
||||||
})?));
|
|
||||||
}
|
}
|
||||||
NT_SPLIT_VIRTUAL_ADDRESSES => {
|
NT_SPLIT_VIRTUAL_ADDRESSES => {
|
||||||
let vec = if is_64 {
|
let vec = if is_64 {
|
||||||
@ -79,10 +79,11 @@ impl SplitMeta {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> io::Result<()>
|
#[cfg(feature = "std")]
|
||||||
|
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> std::io::Result<()>
|
||||||
where
|
where
|
||||||
E: Endian,
|
E: Endian,
|
||||||
W: Write + ?Sized,
|
W: std::io::Write + ?Sized,
|
||||||
{
|
{
|
||||||
if let Some(generator) = &self.generator {
|
if let Some(generator) = &self.generator {
|
||||||
write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
|
write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
|
||||||
@ -137,10 +138,9 @@ impl SplitMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert an object::read::Error to an io::Error.
|
/// Convert an object::read::Error to a String.
|
||||||
fn object_io_error(err: object::read::Error) -> io::Error {
|
#[inline]
|
||||||
io::Error::new(io::ErrorKind::InvalidData, err)
|
fn object_error(err: object::read::Error) -> anyhow::Error { anyhow::Error::new(err) }
|
||||||
}
|
|
||||||
|
|
||||||
/// An ELF note entry.
|
/// An ELF note entry.
|
||||||
struct Note<'data> {
|
struct Note<'data> {
|
||||||
@ -161,27 +161,27 @@ where E: Endian
|
|||||||
impl<'data, E> NoteIterator<'data, E>
|
impl<'data, E> NoteIterator<'data, E>
|
||||||
where E: Endian
|
where E: Endian
|
||||||
{
|
{
|
||||||
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> io::Result<Self> {
|
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> Result<Self> {
|
||||||
Ok(if is_64 {
|
Ok(if is_64 {
|
||||||
NoteIterator::B64(
|
NoteIterator::B64(
|
||||||
object::read::elf::NoteIterator::new(e, align, data).map_err(object_io_error)?,
|
object::read::elf::NoteIterator::new(e, align, data).map_err(object_error)?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
NoteIterator::B32(
|
NoteIterator::B32(
|
||||||
object::read::elf::NoteIterator::new(e, align as u32, data)
|
object::read::elf::NoteIterator::new(e, align as u32, data)
|
||||||
.map_err(object_io_error)?,
|
.map_err(object_error)?,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&mut self, e: E) -> io::Result<Option<Note<'data>>> {
|
fn next(&mut self, e: E) -> Result<Option<Note<'data>>> {
|
||||||
match self {
|
match self {
|
||||||
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note {
|
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
|
||||||
n_type: note.n_type(e),
|
n_type: note.n_type(e),
|
||||||
name: note.name(),
|
name: note.name(),
|
||||||
desc: note.desc(),
|
desc: note.desc(),
|
||||||
})),
|
})),
|
||||||
NoteIterator::B64(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note {
|
NoteIterator::B64(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
|
||||||
n_type: note.n_type(e),
|
n_type: note.n_type(e),
|
||||||
name: note.name(),
|
name: note.name(),
|
||||||
desc: note.desc(),
|
desc: note.desc(),
|
||||||
@ -192,7 +192,8 @@ where E: Endian
|
|||||||
|
|
||||||
fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
|
fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
|
||||||
|
|
||||||
fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<()> {
|
#[cfg(feature = "std")]
|
||||||
|
fn align_data_to_4<W: std::io::Write + ?Sized>(writer: &mut W, len: usize) -> std::io::Result<()> {
|
||||||
const ALIGN_BYTES: &[u8] = &[0; 4];
|
const ALIGN_BYTES: &[u8] = &[0; 4];
|
||||||
if len % 4 != 0 {
|
if len % 4 != 0 {
|
||||||
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
||||||
@ -208,10 +209,11 @@ fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<
|
|||||||
// Desc | variable size, padded to a 4 byte boundary
|
// Desc | variable size, padded to a 4 byte boundary
|
||||||
const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3);
|
const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3);
|
||||||
|
|
||||||
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> io::Result<()>
|
#[cfg(feature = "std")]
|
||||||
|
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> std::io::Result<()>
|
||||||
where
|
where
|
||||||
E: Endian,
|
E: Endian,
|
||||||
W: Write + ?Sized,
|
W: std::io::Write + ?Sized,
|
||||||
{
|
{
|
||||||
writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size
|
writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size
|
||||||
writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size
|
writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
use std::{
|
use alloc::format;
|
||||||
fmt::{LowerHex, UpperHex},
|
use core::fmt;
|
||||||
io::Read,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use byteorder::{NativeEndian, ReadBytesExt};
|
|
||||||
use num_traits::PrimInt;
|
use num_traits::PrimInt;
|
||||||
use object::{Endian, Object};
|
use object::{Endian, Object};
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
|
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
|
||||||
pub struct ReallySigned<N: PrimInt>(pub(crate) N);
|
pub struct ReallySigned<N: PrimInt>(pub(crate) N);
|
||||||
|
|
||||||
impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
impl<N: PrimInt> fmt::LowerHex for ReallySigned<N> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let num = self.0.to_i64().unwrap();
|
let num = self.0.to_i64().unwrap();
|
||||||
let prefix = if f.alternate() { "0x" } else { "" };
|
let prefix = if f.alternate() { "0x" } else { "" };
|
||||||
let bare_hex = format!("{:x}", num.abs());
|
let bare_hex = format!("{:x}", num.abs());
|
||||||
@ -20,8 +17,8 @@ impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: PrimInt> UpperHex for ReallySigned<N> {
|
impl<N: PrimInt> fmt::UpperHex for ReallySigned<N> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let num = self.0.to_i64().unwrap();
|
let num = self.0.to_i64().unwrap();
|
||||||
let prefix = if f.alternate() { "0x" } else { "" };
|
let prefix = if f.alternate() { "0x" } else { "" };
|
||||||
let bare_hex = format!("{:X}", num.abs());
|
let bare_hex = format!("{:X}", num.abs());
|
||||||
@ -29,10 +26,18 @@ impl<N: PrimInt> UpperHex for ReallySigned<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_u32<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u32> {
|
pub fn read_u32(obj_file: &object::File, reader: &mut &[u8]) -> Result<u32> {
|
||||||
Ok(obj_file.endianness().read_u32(reader.read_u32::<NativeEndian>()?))
|
if reader.len() < 4 {
|
||||||
|
return Err(anyhow::anyhow!("Not enough bytes to read u32"));
|
||||||
|
}
|
||||||
|
let value = u32::from_ne_bytes(reader[..4].try_into()?);
|
||||||
|
Ok(obj_file.endianness().read_u32(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_u16<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u16> {
|
pub fn read_u16(obj_file: &object::File, reader: &mut &[u8]) -> Result<u16> {
|
||||||
Ok(obj_file.endianness().read_u16(reader.read_u16::<NativeEndian>()?))
|
if reader.len() < 2 {
|
||||||
|
return Err(anyhow::anyhow!("Not enough bytes to read u16"));
|
||||||
|
}
|
||||||
|
let value = u16::from_ne_bytes(reader[..2].try_into()?);
|
||||||
|
Ok(obj_file.endianness().read_u16(value))
|
||||||
}
|
}
|
||||||
|
99
objdiff-core/src/wasm/api.rs
Normal file
99
objdiff-core/src/wasm/api.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
use alloc::{
|
||||||
|
format,
|
||||||
|
str::FromStr,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
use prost::Message;
|
||||||
|
|
||||||
|
use crate::{bindings::diff::DiffResult, diff, obj};
|
||||||
|
|
||||||
|
wit_bindgen::generate!({
|
||||||
|
world: "api",
|
||||||
|
});
|
||||||
|
|
||||||
|
use exports::objdiff::core::diff::{
|
||||||
|
DiffConfigBorrow, Guest as GuestTypes, GuestDiffConfig, GuestObject, Object, ObjectBorrow,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Component;
|
||||||
|
|
||||||
|
impl Guest for Component {
|
||||||
|
fn init() -> Result<(), String> {
|
||||||
|
// console_error_panic_hook::set_once();
|
||||||
|
// #[cfg(debug_assertions)]
|
||||||
|
// console_log::init_with_level(log::Level::Debug).map_err(|e| e.to_string())?;
|
||||||
|
// #[cfg(not(debug_assertions))]
|
||||||
|
// console_log::init_with_level(log::Level::Info).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version() -> String { env!("CARGO_PKG_VERSION").to_string() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
struct ResourceDiffConfig(RefCell<diff::DiffObjConfig>);
|
||||||
|
|
||||||
|
impl GuestTypes for Component {
|
||||||
|
type DiffConfig = ResourceDiffConfig;
|
||||||
|
type Object = obj::ObjInfo;
|
||||||
|
|
||||||
|
fn run_diff(
|
||||||
|
left: Option<ObjectBorrow>,
|
||||||
|
right: Option<ObjectBorrow>,
|
||||||
|
diff_config: DiffConfigBorrow,
|
||||||
|
) -> Result<Vec<u8>, String> {
|
||||||
|
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||||
|
let result = run_diff_internal(
|
||||||
|
left.as_ref().map(|o| o.get()),
|
||||||
|
right.as_ref().map(|o| o.get()),
|
||||||
|
&diff_config,
|
||||||
|
&diff::MappingConfig::default(),
|
||||||
|
)
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(result.encode_to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 obj::ObjInfo {
|
||||||
|
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(o)).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_diff_internal(
|
||||||
|
left: Option<&obj::ObjInfo>,
|
||||||
|
right: Option<&obj::ObjInfo>,
|
||||||
|
diff_config: &diff::DiffObjConfig,
|
||||||
|
mapping_config: &diff::MappingConfig,
|
||||||
|
) -> anyhow::Result<DiffResult> {
|
||||||
|
log::debug!("Running diff with config: {:?}", diff_config);
|
||||||
|
let result = diff::diff_objs(diff_config, mapping_config, left, right, None)?;
|
||||||
|
let left = left.and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
||||||
|
let right = right.and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
||||||
|
Ok(DiffResult::new(left, right))
|
||||||
|
}
|
||||||
|
|
||||||
|
export!(Component);
|
64
objdiff-core/src/wasm/cabi_realloc.rs
Normal file
64
objdiff-core/src/wasm/cabi_realloc.rs
Normal file
@ -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
|
||||||
|
}
|
18
objdiff-core/src/wasm/mod.rs
Normal file
18
objdiff-core/src/wasm/mod.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
mod api;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
mod cabi_realloc;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
static mut ARENA: [u8; 10000] = [0; 10000];
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOCATOR: talc::Talck<spin::Mutex<()>, talc::ClaimOnOom> = talc::Talc::new(unsafe {
|
||||||
|
talc::ClaimOnOom::new(talc::Span::from_array(core::ptr::addr_of!(ARENA) as *mut [u8; 10000]))
|
||||||
|
})
|
||||||
|
.lock();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }
|
29
objdiff-core/wit/objdiff.wit
Normal file
29
objdiff-core/wit/objdiff.wit
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package objdiff:core;
|
||||||
|
|
||||||
|
interface diff {
|
||||||
|
resource diff-config {
|
||||||
|
constructor();
|
||||||
|
set-property: func(id: string, value: string) -> result<_, string>;
|
||||||
|
get-property: func(id: string) -> result<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
resource object {
|
||||||
|
parse: static func(
|
||||||
|
data: list<u8>,
|
||||||
|
config: borrow<diff-config>,
|
||||||
|
) -> result<object, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
run-diff: func(
|
||||||
|
left: option<borrow<object>>,
|
||||||
|
right: option<borrow<object>>,
|
||||||
|
config: borrow<diff-config>,
|
||||||
|
) -> result<list<u8>, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
world api {
|
||||||
|
export diff;
|
||||||
|
|
||||||
|
export init: func() -> result<_, string>;
|
||||||
|
export version: func() -> string;
|
||||||
|
}
|
@ -29,7 +29,7 @@ bytes = "1.9"
|
|||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
const_format = "0.2"
|
const_format = "0.2"
|
||||||
cwdemangle = "1.0"
|
cwdemangle = "1.0"
|
||||||
cwextab = "1.0"
|
cwextab = { version = "1.0", git = "https://github.com/encounter/cwextab.git" }
|
||||||
dirs = "5.0"
|
dirs = "5.0"
|
||||||
egui = "0.30"
|
egui = "0.30"
|
||||||
egui_extras = "0.30"
|
egui_extras = "0.30"
|
||||||
@ -51,6 +51,7 @@ serde_json = "1.0"
|
|||||||
shell-escape = "0.1"
|
shell-escape = "0.1"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
|
typed-path = "0.10"
|
||||||
|
|
||||||
# Keep version in sync with egui
|
# Keep version in sync with egui
|
||||||
[dependencies.eframe]
|
[dependencies.eframe]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
default::Default,
|
default::Default,
|
||||||
fs,
|
fs,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@ -15,13 +16,15 @@ use globset::Glob;
|
|||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
build::watcher::{create_watcher, Watcher},
|
build::watcher::{create_watcher, Watcher},
|
||||||
config::{
|
config::{
|
||||||
build_globset, default_watch_patterns, save_project_config, ProjectConfig,
|
build_globset, default_watch_patterns, path::platform_path_serde_option,
|
||||||
ProjectConfigInfo, ProjectObject, ScratchConfig, SymbolMappings, DEFAULT_WATCH_PATTERNS,
|
save_project_config, ProjectConfig, ProjectConfigInfo, ProjectObject, ScratchConfig,
|
||||||
|
DEFAULT_WATCH_PATTERNS,
|
||||||
},
|
},
|
||||||
diff::DiffObjConfig,
|
diff::DiffObjConfig,
|
||||||
jobs::{Job, JobQueue, JobResult},
|
jobs::{Job, JobQueue, JobResult},
|
||||||
};
|
};
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_config::{deserialize_config, AppConfigVersion},
|
app_config::{deserialize_config, AppConfigVersion},
|
||||||
@ -90,26 +93,57 @@ impl Default for ViewState {
|
|||||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ObjectConfig {
|
pub struct ObjectConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub target_path: Option<PathBuf>,
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
pub base_path: Option<PathBuf>,
|
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
pub scratch: Option<ScratchConfig>,
|
|
||||||
pub source_path: Option<String>,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub symbol_mappings: SymbolMappings,
|
pub hidden: bool,
|
||||||
|
pub scratch: Option<ScratchConfig>,
|
||||||
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
|
pub source_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub symbol_mappings: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ProjectObject> for ObjectConfig {
|
impl ObjectConfig {
|
||||||
fn from(object: &ProjectObject) -> Self {
|
pub fn new(
|
||||||
|
object: &ProjectObject,
|
||||||
|
project_dir: &Utf8PlatformPath,
|
||||||
|
target_obj_dir: Option<&Utf8PlatformPath>,
|
||||||
|
base_obj_dir: Option<&Utf8PlatformPath>,
|
||||||
|
) -> Self {
|
||||||
|
let target_path = if let (Some(target_obj_dir), Some(path), None) =
|
||||||
|
(target_obj_dir, &object.path, &object.target_path)
|
||||||
|
{
|
||||||
|
Some(target_obj_dir.join(path.with_platform_encoding()))
|
||||||
|
} else if let Some(path) = &object.target_path {
|
||||||
|
Some(project_dir.join(path.with_platform_encoding()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let base_path = if let (Some(base_obj_dir), Some(path), None) =
|
||||||
|
(base_obj_dir, &object.path, &object.base_path)
|
||||||
|
{
|
||||||
|
Some(base_obj_dir.join(path.with_platform_encoding()))
|
||||||
|
} else if let Some(path) = &object.base_path {
|
||||||
|
Some(project_dir.join(path.with_platform_encoding()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let source_path =
|
||||||
|
object.source_path().map(|s| project_dir.join(s.with_platform_encoding()));
|
||||||
Self {
|
Self {
|
||||||
name: object.name().to_string(),
|
name: object.name().to_string(),
|
||||||
target_path: object.target_path.clone(),
|
target_path,
|
||||||
base_path: object.base_path.clone(),
|
base_path,
|
||||||
reverse_fn_order: object.reverse_fn_order(),
|
reverse_fn_order: object.reverse_fn_order(),
|
||||||
complete: object.complete(),
|
complete: object.complete(),
|
||||||
|
hidden: object.hidden(),
|
||||||
scratch: object.scratch.clone(),
|
scratch: object.scratch.clone(),
|
||||||
source_path: object.source_path().cloned(),
|
source_path,
|
||||||
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
|
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,7 +154,7 @@ fn bool_true() -> bool { true }
|
|||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub config: AppConfig,
|
pub config: AppConfig,
|
||||||
pub objects: Vec<ProjectObject>,
|
pub objects: Vec<ObjectConfig>,
|
||||||
pub object_nodes: Vec<ProjectObjectNode>,
|
pub object_nodes: Vec<ProjectObjectNode>,
|
||||||
pub watcher_change: bool,
|
pub watcher_change: bool,
|
||||||
pub config_change: bool,
|
pub config_change: bool,
|
||||||
@ -170,12 +204,12 @@ pub struct AppConfig {
|
|||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
pub target_obj_dir: Option<PathBuf>,
|
pub target_obj_dir: Option<Utf8PlatformPathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default, with = "platform_path_serde_option")]
|
||||||
pub base_obj_dir: Option<PathBuf>,
|
pub base_obj_dir: Option<Utf8PlatformPathBuf>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_obj: Option<ObjectConfig>,
|
pub selected_obj: Option<ObjectConfig>,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
@ -189,7 +223,7 @@ pub struct AppConfig {
|
|||||||
#[serde(default = "default_watch_patterns")]
|
#[serde(default = "default_watch_patterns")]
|
||||||
pub watch_patterns: Vec<Glob>,
|
pub watch_patterns: Vec<Glob>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub recent_projects: Vec<PathBuf>,
|
pub recent_projects: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub diff_obj_config: DiffObjConfig,
|
pub diff_obj_config: DiffObjConfig,
|
||||||
}
|
}
|
||||||
@ -217,12 +251,12 @@ impl Default for AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn set_project_dir(&mut self, path: PathBuf) {
|
pub fn set_project_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||||
self.config.recent_projects.retain(|p| p != &path);
|
self.config.recent_projects.retain(|p| p != &path);
|
||||||
if self.config.recent_projects.len() > 9 {
|
if self.config.recent_projects.len() > 9 {
|
||||||
self.config.recent_projects.truncate(9);
|
self.config.recent_projects.truncate(9);
|
||||||
}
|
}
|
||||||
self.config.recent_projects.insert(0, path.clone());
|
self.config.recent_projects.insert(0, path.to_string());
|
||||||
self.config.project_dir = Some(path);
|
self.config.project_dir = Some(path);
|
||||||
self.config.target_obj_dir = None;
|
self.config.target_obj_dir = None;
|
||||||
self.config.base_obj_dir = None;
|
self.config.base_obj_dir = None;
|
||||||
@ -240,7 +274,7 @@ impl AppState {
|
|||||||
self.selecting_right = None;
|
self.selecting_right = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
pub fn set_target_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||||
self.config.target_obj_dir = Some(path);
|
self.config.target_obj_dir = Some(path);
|
||||||
self.config.selected_obj = None;
|
self.config.selected_obj = None;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
@ -249,7 +283,7 @@ impl AppState {
|
|||||||
self.selecting_right = None;
|
self.selecting_right = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
pub fn set_base_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||||
self.config.base_obj_dir = Some(path);
|
self.config.base_obj_dir = Some(path);
|
||||||
self.config.selected_obj = None;
|
self.config.selected_obj = None;
|
||||||
self.obj_change = true;
|
self.obj_change = true;
|
||||||
@ -360,14 +394,8 @@ impl AppState {
|
|||||||
Some(object.symbol_mappings.clone())
|
Some(object.symbol_mappings.clone())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if let Some(existing) =
|
if let Some(existing) = self.objects.iter_mut().find(|u| u.name == object.name) {
|
||||||
self.objects.iter_mut().find(|u| u.name.as_ref().is_some_and(|n| n == &object.name))
|
existing.symbol_mappings = object.symbol_mappings.clone();
|
||||||
{
|
|
||||||
existing.symbol_mappings = if object.symbol_mappings.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(object.symbol_mappings.clone())
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Save the updated project config
|
// Save the updated project config
|
||||||
@ -530,7 +558,12 @@ impl App {
|
|||||||
match build_globset(&state.config.watch_patterns)
|
match build_globset(&state.config.watch_patterns)
|
||||||
.map_err(anyhow::Error::new)
|
.map_err(anyhow::Error::new)
|
||||||
.and_then(|globset| {
|
.and_then(|globset| {
|
||||||
create_watcher(self.modified.clone(), project_dir, globset, egui_waker(ctx))
|
create_watcher(
|
||||||
|
self.modified.clone(),
|
||||||
|
project_dir.as_ref(),
|
||||||
|
globset,
|
||||||
|
egui_waker(ctx),
|
||||||
|
)
|
||||||
.map_err(anyhow::Error::new)
|
.map_err(anyhow::Error::new)
|
||||||
}) {
|
}) {
|
||||||
Ok(watcher) => self.watcher = Some(watcher),
|
Ok(watcher) => self.watcher = Some(watcher),
|
||||||
@ -672,8 +705,11 @@ impl eframe::App for App {
|
|||||||
};
|
};
|
||||||
ui.separator();
|
ui.separator();
|
||||||
for path in recent_projects {
|
for path in recent_projects {
|
||||||
if ui.button(format!("{}", path.display())).clicked() {
|
if ui.button(&path).clicked() {
|
||||||
state.write().unwrap().set_project_dir(path);
|
state
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.set_project_dir(Utf8PlatformPathBuf::from(path));
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -776,8 +812,8 @@ impl eframe::App for App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn file_modified(path: &Path, last_ts: FileTime) -> bool {
|
fn file_modified<P: AsRef<Path>>(path: P, last_ts: FileTime) -> bool {
|
||||||
if let Ok(metadata) = fs::metadata(path) {
|
if let Ok(metadata) = fs::metadata(path.as_ref()) {
|
||||||
FileTime::from_last_modification_time(&metadata) != last_ts
|
FileTime::from_last_modification_time(&metadata) != last_ts
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
use std::path::PathBuf;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use eframe::Storage;
|
use eframe::Storage;
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
config::{ScratchConfig, SymbolMappings},
|
config::ScratchConfig,
|
||||||
diff::{
|
diff::{
|
||||||
ArmArchVersion, ArmR9Usage, DiffObjConfig, FunctionRelocDiffs, MipsAbi, MipsInstrCategory,
|
ArmArchVersion, ArmR9Usage, DiffObjConfig, FunctionRelocDiffs, MipsAbi, MipsInstrCategory,
|
||||||
X86Formatter,
|
X86Formatter,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
|
||||||
|
|
||||||
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
|
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ pub struct ScratchConfigV2 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub c_flags: Option<String>,
|
pub c_flags: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ctx_path: Option<PathBuf>,
|
pub ctx_path: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub build_ctx: Option<bool>,
|
pub build_ctx: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -75,7 +76,7 @@ impl ScratchConfigV2 {
|
|||||||
platform: self.platform,
|
platform: self.platform,
|
||||||
compiler: self.compiler,
|
compiler: self.compiler,
|
||||||
c_flags: self.c_flags,
|
c_flags: self.c_flags,
|
||||||
ctx_path: self.ctx_path,
|
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
|
||||||
build_ctx: self.build_ctx,
|
build_ctx: self.build_ctx,
|
||||||
preset_id: self.preset_id,
|
preset_id: self.preset_id,
|
||||||
}
|
}
|
||||||
@ -85,26 +86,27 @@ impl ScratchConfigV2 {
|
|||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ObjectConfigV2 {
|
pub struct ObjectConfigV2 {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub target_path: Option<PathBuf>,
|
pub target_path: Option<String>,
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<String>,
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
pub scratch: Option<ScratchConfigV2>,
|
pub scratch: Option<ScratchConfigV2>,
|
||||||
pub source_path: Option<String>,
|
pub source_path: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub symbol_mappings: SymbolMappings,
|
pub symbol_mappings: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectConfigV2 {
|
impl ObjectConfigV2 {
|
||||||
fn into_config(self) -> ObjectConfig {
|
fn into_config(self) -> ObjectConfig {
|
||||||
ObjectConfig {
|
ObjectConfig {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
target_path: self.target_path,
|
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
|
||||||
base_path: self.base_path,
|
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
|
||||||
reverse_fn_order: self.reverse_fn_order,
|
reverse_fn_order: self.reverse_fn_order,
|
||||||
complete: self.complete,
|
complete: self.complete,
|
||||||
|
hidden: false,
|
||||||
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||||
source_path: self.source_path,
|
source_path: None,
|
||||||
symbol_mappings: self.symbol_mappings,
|
symbol_mappings: self.symbol_mappings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,11 +122,11 @@ pub struct AppConfigV2 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub target_obj_dir: Option<PathBuf>,
|
pub target_obj_dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub base_obj_dir: Option<PathBuf>,
|
pub base_obj_dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_obj: Option<ObjectConfigV2>,
|
pub selected_obj: Option<ObjectConfigV2>,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
@ -138,7 +140,7 @@ pub struct AppConfigV2 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub watch_patterns: Vec<Glob>,
|
pub watch_patterns: Vec<Glob>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub recent_projects: Vec<PathBuf>,
|
pub recent_projects: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub diff_obj_config: DiffObjConfigV1,
|
pub diff_obj_config: DiffObjConfigV1,
|
||||||
}
|
}
|
||||||
@ -150,9 +152,9 @@ impl AppConfigV2 {
|
|||||||
custom_make: self.custom_make,
|
custom_make: self.custom_make,
|
||||||
custom_args: self.custom_args,
|
custom_args: self.custom_args,
|
||||||
selected_wsl_distro: self.selected_wsl_distro,
|
selected_wsl_distro: self.selected_wsl_distro,
|
||||||
project_dir: self.project_dir,
|
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||||
target_obj_dir: self.target_obj_dir,
|
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
base_obj_dir: self.base_obj_dir,
|
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||||
build_base: self.build_base,
|
build_base: self.build_base,
|
||||||
build_target: self.build_target,
|
build_target: self.build_target,
|
||||||
@ -175,7 +177,7 @@ pub struct ScratchConfigV1 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub c_flags: Option<String>,
|
pub c_flags: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ctx_path: Option<PathBuf>,
|
pub ctx_path: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub build_ctx: bool,
|
pub build_ctx: bool,
|
||||||
}
|
}
|
||||||
@ -186,7 +188,7 @@ impl ScratchConfigV1 {
|
|||||||
platform: self.platform,
|
platform: self.platform,
|
||||||
compiler: self.compiler,
|
compiler: self.compiler,
|
||||||
c_flags: self.c_flags,
|
c_flags: self.c_flags,
|
||||||
ctx_path: self.ctx_path,
|
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
|
||||||
build_ctx: self.build_ctx.then_some(true),
|
build_ctx: self.build_ctx.then_some(true),
|
||||||
preset_id: None,
|
preset_id: None,
|
||||||
}
|
}
|
||||||
@ -196,8 +198,8 @@ impl ScratchConfigV1 {
|
|||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ObjectConfigV1 {
|
pub struct ObjectConfigV1 {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub target_path: Option<PathBuf>,
|
pub target_path: Option<String>,
|
||||||
pub base_path: Option<PathBuf>,
|
pub base_path: Option<String>,
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
pub scratch: Option<ScratchConfigV1>,
|
pub scratch: Option<ScratchConfigV1>,
|
||||||
@ -208,12 +210,12 @@ impl ObjectConfigV1 {
|
|||||||
fn into_config(self) -> ObjectConfig {
|
fn into_config(self) -> ObjectConfig {
|
||||||
ObjectConfig {
|
ObjectConfig {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
target_path: self.target_path,
|
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
|
||||||
base_path: self.base_path,
|
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
|
||||||
reverse_fn_order: self.reverse_fn_order,
|
reverse_fn_order: self.reverse_fn_order,
|
||||||
complete: self.complete,
|
complete: self.complete,
|
||||||
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||||
source_path: self.source_path,
|
source_path: None,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,11 +300,11 @@ pub struct AppConfigV1 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub target_obj_dir: Option<PathBuf>,
|
pub target_obj_dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub base_obj_dir: Option<PathBuf>,
|
pub base_obj_dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_obj: Option<ObjectConfigV1>,
|
pub selected_obj: Option<ObjectConfigV1>,
|
||||||
#[serde(default = "bool_true")]
|
#[serde(default = "bool_true")]
|
||||||
@ -316,7 +318,7 @@ pub struct AppConfigV1 {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub watch_patterns: Vec<Glob>,
|
pub watch_patterns: Vec<Glob>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub recent_projects: Vec<PathBuf>,
|
pub recent_projects: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub diff_obj_config: DiffObjConfigV1,
|
pub diff_obj_config: DiffObjConfigV1,
|
||||||
}
|
}
|
||||||
@ -328,9 +330,9 @@ impl AppConfigV1 {
|
|||||||
custom_make: self.custom_make,
|
custom_make: self.custom_make,
|
||||||
custom_args: self.custom_args,
|
custom_args: self.custom_args,
|
||||||
selected_wsl_distro: self.selected_wsl_distro,
|
selected_wsl_distro: self.selected_wsl_distro,
|
||||||
project_dir: self.project_dir,
|
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||||
target_obj_dir: self.target_obj_dir,
|
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
base_obj_dir: self.base_obj_dir,
|
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||||
build_base: self.build_base,
|
build_base: self.build_base,
|
||||||
build_target: self.build_target,
|
build_target: self.build_target,
|
||||||
@ -347,8 +349,8 @@ impl AppConfigV1 {
|
|||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct ObjectConfigV0 {
|
pub struct ObjectConfigV0 {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub target_path: PathBuf,
|
pub target_path: String,
|
||||||
pub base_path: PathBuf,
|
pub base_path: String,
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,8 +358,8 @@ impl ObjectConfigV0 {
|
|||||||
fn into_config(self) -> ObjectConfig {
|
fn into_config(self) -> ObjectConfig {
|
||||||
ObjectConfig {
|
ObjectConfig {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
target_path: Some(self.target_path),
|
target_path: Some(Utf8PlatformPathBuf::from(self.target_path)),
|
||||||
base_path: Some(self.base_path),
|
base_path: Some(Utf8PlatformPathBuf::from(self.base_path)),
|
||||||
reverse_fn_order: self.reverse_fn_order,
|
reverse_fn_order: self.reverse_fn_order,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
@ -368,9 +370,9 @@ impl ObjectConfigV0 {
|
|||||||
pub struct AppConfigV0 {
|
pub struct AppConfigV0 {
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
pub selected_wsl_distro: Option<String>,
|
pub selected_wsl_distro: Option<String>,
|
||||||
pub project_dir: Option<PathBuf>,
|
pub project_dir: Option<String>,
|
||||||
pub target_obj_dir: Option<PathBuf>,
|
pub target_obj_dir: Option<String>,
|
||||||
pub base_obj_dir: Option<PathBuf>,
|
pub base_obj_dir: Option<String>,
|
||||||
pub selected_obj: Option<ObjectConfigV0>,
|
pub selected_obj: Option<ObjectConfigV0>,
|
||||||
pub build_target: bool,
|
pub build_target: bool,
|
||||||
pub auto_update_check: bool,
|
pub auto_update_check: bool,
|
||||||
@ -383,9 +385,9 @@ impl AppConfigV0 {
|
|||||||
AppConfig {
|
AppConfig {
|
||||||
custom_make: self.custom_make,
|
custom_make: self.custom_make,
|
||||||
selected_wsl_distro: self.selected_wsl_distro,
|
selected_wsl_distro: self.selected_wsl_distro,
|
||||||
project_dir: self.project_dir,
|
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||||
target_obj_dir: self.target_obj_dir,
|
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
base_obj_dir: self.base_obj_dir,
|
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||||
build_target: self.build_target,
|
build_target: self.build_target,
|
||||||
auto_update_check: self.auto_update_check,
|
auto_update_check: self.auto_update_check,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::path::{Component, Path};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS};
|
use objdiff_core::config::{try_project_config, DEFAULT_WATCH_PATTERNS};
|
||||||
|
use typed_path::{Utf8UnixComponent, Utf8UnixPath};
|
||||||
|
|
||||||
use crate::app::{AppState, ObjectConfig};
|
use crate::app::{AppState, ObjectConfig};
|
||||||
|
|
||||||
@ -47,32 +46,19 @@ fn find_dir<'a>(
|
|||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_nodes(
|
fn build_nodes(units: &mut [ObjectConfig]) -> Vec<ProjectObjectNode> {
|
||||||
units: &mut [ProjectObject],
|
|
||||||
project_dir: &Path,
|
|
||||||
target_obj_dir: Option<&Path>,
|
|
||||||
base_obj_dir: Option<&Path>,
|
|
||||||
) -> Vec<ProjectObjectNode> {
|
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
for (idx, unit) in units.iter_mut().enumerate() {
|
for (idx, unit) in units.iter_mut().enumerate() {
|
||||||
unit.resolve_paths(project_dir, target_obj_dir, base_obj_dir);
|
|
||||||
let mut out_nodes = &mut nodes;
|
let mut out_nodes = &mut nodes;
|
||||||
let path = if let Some(name) = &unit.name {
|
let path = Utf8UnixPath::new(&unit.name);
|
||||||
Path::new(name)
|
|
||||||
} else if let Some(path) = &unit.path {
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
for component in parent.components() {
|
for component in parent.components() {
|
||||||
if let Component::Normal(name) = component {
|
if let Utf8UnixComponent::Normal(name) = component {
|
||||||
let name = name.to_str().unwrap();
|
|
||||||
out_nodes = find_dir(name, out_nodes);
|
out_nodes = find_dir(name, out_nodes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
let filename = path.file_name().unwrap().to_string();
|
||||||
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
|
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
|
||||||
}
|
}
|
||||||
// Within the top-level module directories, join paths. Leave the
|
// Within the top-level module directories, join paths. Leave the
|
||||||
@ -90,13 +76,18 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
|||||||
let Some(project_dir) = &state.config.project_dir else {
|
let Some(project_dir) = &state.config.project_dir else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
if let Some((result, info)) = try_project_config(project_dir) {
|
if let Some((result, info)) = try_project_config(project_dir.as_ref()) {
|
||||||
let project_config = result?;
|
let project_config = result?;
|
||||||
state.config.custom_make = project_config.custom_make.clone();
|
state.config.custom_make = project_config.custom_make.clone();
|
||||||
state.config.custom_args = project_config.custom_args.clone();
|
state.config.custom_args = project_config.custom_args.clone();
|
||||||
state.config.target_obj_dir =
|
state.config.target_obj_dir = project_config
|
||||||
project_config.target_dir.as_deref().map(|p| project_dir.join(p));
|
.target_dir
|
||||||
state.config.base_obj_dir = project_config.base_dir.as_deref().map(|p| project_dir.join(p));
|
.as_deref()
|
||||||
|
.map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
state.config.base_obj_dir = project_config
|
||||||
|
.base_dir
|
||||||
|
.as_deref()
|
||||||
|
.map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
state.config.build_base = project_config.build_base.unwrap_or(true);
|
state.config.build_base = project_config.build_base.unwrap_or(true);
|
||||||
state.config.build_target = project_config.build_target.unwrap_or(false);
|
state.config.build_target = project_config.build_target.unwrap_or(false);
|
||||||
if let Some(watch_patterns) = &project_config.watch_patterns {
|
if let Some(watch_patterns) = &project_config.watch_patterns {
|
||||||
@ -109,21 +100,28 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
|||||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
||||||
}
|
}
|
||||||
state.watcher_change = true;
|
state.watcher_change = true;
|
||||||
state.objects = project_config.units.clone().unwrap_or_default();
|
state.objects = project_config
|
||||||
state.object_nodes = build_nodes(
|
.units
|
||||||
&mut state.objects,
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.iter()
|
||||||
|
.map(|o| {
|
||||||
|
ObjectConfig::new(
|
||||||
|
o,
|
||||||
project_dir,
|
project_dir,
|
||||||
state.config.target_obj_dir.as_deref(),
|
state.config.target_obj_dir.as_deref(),
|
||||||
state.config.base_obj_dir.as_deref(),
|
state.config.base_obj_dir.as_deref(),
|
||||||
);
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
state.object_nodes = build_nodes(&mut state.objects);
|
||||||
state.current_project_config = Some(project_config);
|
state.current_project_config = Some(project_config);
|
||||||
state.project_config_info = Some(info);
|
state.project_config_info = Some(info);
|
||||||
|
|
||||||
// Reload selected object
|
// Reload selected object
|
||||||
if let Some(selected_obj) = &state.config.selected_obj {
|
if let Some(selected_obj) = &state.config.selected_obj {
|
||||||
if let Some(obj) = state.objects.iter().find(|o| o.name() == selected_obj.name) {
|
if let Some(obj) = state.objects.iter().find(|o| o.name == selected_obj.name) {
|
||||||
let config = ObjectConfig::from(obj);
|
state.set_selected_obj(obj.clone());
|
||||||
state.set_selected_obj(config);
|
|
||||||
} else {
|
} else {
|
||||||
state.clear_selected_obj();
|
state.clear_selected_obj();
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ fn create_scratch_config(
|
|||||||
platform: scratch_config.platform.clone().unwrap_or_default(),
|
platform: scratch_config.platform.clone().unwrap_or_default(),
|
||||||
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
||||||
function_name,
|
function_name,
|
||||||
target_obj: target_path.to_path_buf(),
|
target_obj: target_path.clone(),
|
||||||
preset_id: scratch_config.preset_id,
|
preset_id: scratch_config.preset_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
#[cfg(all(windows, feature = "wsl"))]
|
#[cfg(all(windows, feature = "wsl"))]
|
||||||
use std::string::FromUtf16Error;
|
use std::string::FromUtf16Error;
|
||||||
use std::{
|
use std::{mem::take, path::MAIN_SEPARATOR};
|
||||||
mem::take,
|
|
||||||
path::{Path, PathBuf, MAIN_SEPARATOR},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(all(windows, feature = "wsl"))]
|
#[cfg(all(windows, feature = "wsl"))]
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
@ -13,13 +10,14 @@ use egui::{
|
|||||||
};
|
};
|
||||||
use globset::Glob;
|
use globset::Glob;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
|
config::{path::check_path_buf, DEFAULT_WATCH_PATTERNS},
|
||||||
diff::{
|
diff::{
|
||||||
ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
|
ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
|
||||||
ConfigPropertyValue, CONFIG_GROUPS,
|
ConfigPropertyValue, CONFIG_GROUPS,
|
||||||
},
|
},
|
||||||
jobs::{check_update::CheckUpdateResult, Job, JobQueue, JobResult},
|
jobs::{check_update::CheckUpdateResult, Job, JobQueue, JobResult},
|
||||||
};
|
};
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
||||||
@ -89,7 +87,7 @@ impl ConfigViewState {
|
|||||||
if let Ok(obj_path) = path.strip_prefix(base_dir) {
|
if let Ok(obj_path) = path.strip_prefix(base_dir) {
|
||||||
let target_path = target_dir.join(obj_path);
|
let target_path = target_dir.join(obj_path);
|
||||||
guard.set_selected_obj(ObjectConfig {
|
guard.set_selected_obj(ObjectConfig {
|
||||||
name: obj_path.display().to_string(),
|
name: obj_path.to_string(),
|
||||||
target_path: Some(target_path),
|
target_path: Some(target_path),
|
||||||
base_path: Some(path),
|
base_path: Some(path),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -97,7 +95,7 @@ impl ConfigViewState {
|
|||||||
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
|
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
|
||||||
let base_path = base_dir.join(obj_path);
|
let base_path = base_dir.join(obj_path);
|
||||||
guard.set_selected_obj(ObjectConfig {
|
guard.set_selected_obj(ObjectConfig {
|
||||||
name: obj_path.display().to_string(),
|
name: obj_path.to_string(),
|
||||||
target_path: Some(path),
|
target_path: Some(path),
|
||||||
base_path: Some(base_path),
|
base_path: Some(base_path),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -169,10 +167,7 @@ pub fn config_ui(
|
|||||||
) {
|
) {
|
||||||
let mut state_guard = state.write().unwrap();
|
let mut state_guard = state.write().unwrap();
|
||||||
let AppState {
|
let AppState {
|
||||||
config:
|
config: AppConfig { target_obj_dir, base_obj_dir, selected_obj, auto_update_check, .. },
|
||||||
AppConfig {
|
|
||||||
project_dir, target_obj_dir, base_obj_dir, selected_obj, auto_update_check, ..
|
|
||||||
},
|
|
||||||
objects,
|
objects,
|
||||||
object_nodes,
|
object_nodes,
|
||||||
..
|
..
|
||||||
@ -223,9 +218,9 @@ pub fn config_ui(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let selected_index = selected_obj.as_ref().and_then(|selected_obj| {
|
let selected_index = selected_obj
|
||||||
objects.iter().position(|obj| obj.name.as_ref() == Some(&selected_obj.name))
|
.as_ref()
|
||||||
});
|
.and_then(|selected_obj| objects.iter().position(|obj| obj.name == selected_obj.name));
|
||||||
let mut new_selected_index = selected_index;
|
let mut new_selected_index = selected_index;
|
||||||
if objects.is_empty() {
|
if objects.is_empty() {
|
||||||
if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
||||||
@ -324,22 +319,14 @@ pub fn config_ui(
|
|||||||
config_state.show_hidden,
|
config_state.show_hidden,
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
display_node(
|
display_node(ui, &mut new_selected_index, objects, &node, appearance, node_open);
|
||||||
ui,
|
|
||||||
&mut new_selected_index,
|
|
||||||
project_dir.as_deref(),
|
|
||||||
objects,
|
|
||||||
&node,
|
|
||||||
appearance,
|
|
||||||
node_open,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if new_selected_index != selected_index {
|
if new_selected_index != selected_index {
|
||||||
if let Some(idx) = new_selected_index {
|
if let Some(idx) = new_selected_index {
|
||||||
// Will set obj_changed, which will trigger a rebuild
|
// Will set obj_changed, which will trigger a rebuild
|
||||||
let config = ObjectConfig::from(&objects[idx]);
|
let config = objects[idx].clone();
|
||||||
state_guard.set_selected_obj(config);
|
state_guard.set_selected_obj(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,9 +340,8 @@ pub fn config_ui(
|
|||||||
fn display_unit(
|
fn display_unit(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
selected_obj: &mut Option<usize>,
|
selected_obj: &mut Option<usize>,
|
||||||
project_dir: Option<&Path>,
|
|
||||||
name: &str,
|
name: &str,
|
||||||
units: &[ProjectObject],
|
units: &[ObjectConfig],
|
||||||
index: usize,
|
index: usize,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
) {
|
) {
|
||||||
@ -363,7 +349,7 @@ fn display_unit(
|
|||||||
let selected = *selected_obj == Some(index);
|
let selected = *selected_obj == Some(index);
|
||||||
let color = if selected {
|
let color = if selected {
|
||||||
appearance.emphasized_text_color
|
appearance.emphasized_text_color
|
||||||
} else if let Some(complete) = object.complete() {
|
} else if let Some(complete) = object.complete {
|
||||||
if complete {
|
if complete {
|
||||||
appearance.insert_color
|
appearance.insert_color
|
||||||
} else {
|
} else {
|
||||||
@ -382,26 +368,22 @@ fn display_unit(
|
|||||||
.color(color),
|
.color(color),
|
||||||
)
|
)
|
||||||
.ui(ui);
|
.ui(ui);
|
||||||
if get_source_path(project_dir, object).is_some() {
|
if object.source_path.is_some() {
|
||||||
response.context_menu(|ui| object_context_ui(ui, object, project_dir));
|
response.context_menu(|ui| object_context_ui(ui, object));
|
||||||
}
|
}
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
*selected_obj = Some(index);
|
*selected_obj = Some(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_source_path(project_dir: Option<&Path>, object: &ProjectObject) -> Option<PathBuf> {
|
fn object_context_ui(ui: &mut egui::Ui, object: &ObjectConfig) {
|
||||||
project_dir.and_then(|dir| object.source_path().map(|path| dir.join(path)))
|
if let Some(source_path) = &object.source_path {
|
||||||
}
|
|
||||||
|
|
||||||
fn object_context_ui(ui: &mut egui::Ui, object: &ProjectObject, project_dir: Option<&Path>) {
|
|
||||||
if let Some(source_path) = get_source_path(project_dir, object) {
|
|
||||||
if ui
|
if ui
|
||||||
.button("Open source file")
|
.button("Open source file")
|
||||||
.on_hover_text("Open the source file in the default editor")
|
.on_hover_text("Open the source file in the default editor")
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
log::info!("Opening file {}", source_path.display());
|
log::info!("Opening file {}", source_path);
|
||||||
if let Err(e) = open::that_detached(&source_path) {
|
if let Err(e) = open::that_detached(&source_path) {
|
||||||
log::error!("Failed to open source file: {e}");
|
log::error!("Failed to open source file: {e}");
|
||||||
}
|
}
|
||||||
@ -422,15 +404,14 @@ enum NodeOpen {
|
|||||||
fn display_node(
|
fn display_node(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
selected_obj: &mut Option<usize>,
|
selected_obj: &mut Option<usize>,
|
||||||
project_dir: Option<&Path>,
|
units: &[ObjectConfig],
|
||||||
units: &[ProjectObject],
|
|
||||||
node: &ProjectObjectNode,
|
node: &ProjectObjectNode,
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
node_open: NodeOpen,
|
node_open: NodeOpen,
|
||||||
) {
|
) {
|
||||||
match node {
|
match node {
|
||||||
ProjectObjectNode::Unit(name, idx) => {
|
ProjectObjectNode::Unit(name, idx) => {
|
||||||
display_unit(ui, selected_obj, project_dir, name, units, *idx, appearance);
|
display_unit(ui, selected_obj, name, units, *idx, appearance);
|
||||||
}
|
}
|
||||||
ProjectObjectNode::Dir(name, children) => {
|
ProjectObjectNode::Dir(name, children) => {
|
||||||
let contains_obj = selected_obj.map(|idx| contains_node(node, idx));
|
let contains_obj = selected_obj.map(|idx| contains_node(node, idx));
|
||||||
@ -456,7 +437,7 @@ fn display_node(
|
|||||||
.open(open)
|
.open(open)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
for node in children {
|
for node in children {
|
||||||
display_node(ui, selected_obj, project_dir, units, node, appearance, node_open);
|
display_node(ui, selected_obj, units, node, appearance, node_open);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -473,7 +454,7 @@ fn contains_node(node: &ProjectObjectNode, selected_obj: usize) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn filter_node(
|
fn filter_node(
|
||||||
units: &[ProjectObject],
|
units: &[ObjectConfig],
|
||||||
node: &ProjectObjectNode,
|
node: &ProjectObjectNode,
|
||||||
search: &str,
|
search: &str,
|
||||||
filter_diffable: bool,
|
filter_diffable: bool,
|
||||||
@ -485,8 +466,8 @@ fn filter_node(
|
|||||||
let unit = &units[*idx];
|
let unit = &units[*idx];
|
||||||
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
||||||
&& (!filter_diffable || (unit.base_path.is_some() && unit.target_path.is_some()))
|
&& (!filter_diffable || (unit.base_path.is_some() && unit.target_path.is_some()))
|
||||||
&& (!filter_incomplete || matches!(unit.complete(), None | Some(false)))
|
&& (!filter_incomplete || matches!(unit.complete, None | Some(false)))
|
||||||
&& (show_hidden || !unit.hidden())
|
&& (show_hidden || !unit.hidden)
|
||||||
{
|
{
|
||||||
Some(node.clone())
|
Some(node.clone())
|
||||||
} else {
|
} else {
|
||||||
@ -524,13 +505,16 @@ fn subheading(ui: &mut egui::Ui, text: &str, appearance: &Appearance) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_path(path: &Option<PathBuf>, appearance: &Appearance) -> RichText {
|
fn format_path(path: &Option<Utf8PlatformPathBuf>, appearance: &Appearance) -> RichText {
|
||||||
let mut color = appearance.replace_color;
|
let mut color = appearance.replace_color;
|
||||||
let text = if let Some(dir) = path {
|
let text = if let Some(dir) = path {
|
||||||
if let Some(rel) = dirs::home_dir().and_then(|home| dir.strip_prefix(&home).ok()) {
|
if let Some(rel) = dirs::home_dir()
|
||||||
format!("~{}{}", MAIN_SEPARATOR, rel.display())
|
.and_then(|home| check_path_buf(home).ok())
|
||||||
|
.and_then(|home| dir.strip_prefix(&home).ok())
|
||||||
|
{
|
||||||
|
format!("~{}{}", MAIN_SEPARATOR, rel)
|
||||||
} else {
|
} else {
|
||||||
format!("{}", dir.display())
|
dir.to_string()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
color = appearance.delete_color;
|
color = appearance.delete_color;
|
||||||
@ -544,7 +528,7 @@ pub const CONFIG_DISABLED_TEXT: &str =
|
|||||||
|
|
||||||
fn pick_folder_ui(
|
fn pick_folder_ui(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
dir: &Option<PathBuf>,
|
dir: &Option<Utf8PlatformPathBuf>,
|
||||||
label: &str,
|
label: &str,
|
||||||
tooltip: impl FnOnce(&mut egui::Ui),
|
tooltip: impl FnOnce(&mut egui::Ui),
|
||||||
appearance: &Appearance,
|
appearance: &Appearance,
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
use std::{future::Future, path::PathBuf, pin::Pin, thread::JoinHandle};
|
use std::{future::Future, path::PathBuf, pin::Pin, thread::JoinHandle};
|
||||||
|
|
||||||
|
use objdiff_core::config::path::check_path_buf;
|
||||||
use pollster::FutureExt;
|
use pollster::FutureExt;
|
||||||
use rfd::FileHandle;
|
use rfd::FileHandle;
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub enum FileDialogResult {
|
pub enum FileDialogResult {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
ProjectDir(PathBuf),
|
ProjectDir(Utf8PlatformPathBuf),
|
||||||
TargetDir(PathBuf),
|
TargetDir(Utf8PlatformPathBuf),
|
||||||
BaseDir(PathBuf),
|
BaseDir(Utf8PlatformPathBuf),
|
||||||
Object(PathBuf),
|
Object(Utf8PlatformPathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -22,7 +24,7 @@ impl FileDialogState {
|
|||||||
pub fn queue<InitCb, ResultCb>(&mut self, init: InitCb, result_cb: ResultCb)
|
pub fn queue<InitCb, ResultCb>(&mut self, init: InitCb, result_cb: ResultCb)
|
||||||
where
|
where
|
||||||
InitCb: FnOnce() -> Pin<Box<dyn Future<Output = Option<FileHandle>> + Send>>,
|
InitCb: FnOnce() -> Pin<Box<dyn Future<Output = Option<FileHandle>> + Send>>,
|
||||||
ResultCb: FnOnce(PathBuf) -> FileDialogResult + Send + 'static,
|
ResultCb: FnOnce(Utf8PlatformPathBuf) -> FileDialogResult + Send + 'static,
|
||||||
{
|
{
|
||||||
if self.thread.is_some() {
|
if self.thread.is_some() {
|
||||||
return;
|
return;
|
||||||
@ -30,7 +32,8 @@ impl FileDialogState {
|
|||||||
let future = init();
|
let future = init();
|
||||||
self.thread = Some(std::thread::spawn(move || {
|
self.thread = Some(std::thread::spawn(move || {
|
||||||
if let Some(handle) = future.block_on() {
|
if let Some(handle) = future.block_on() {
|
||||||
result_cb(PathBuf::from(handle))
|
let path = PathBuf::from(handle);
|
||||||
|
check_path_buf(path).map(result_cb).unwrap_or(FileDialogResult::None)
|
||||||
} else {
|
} else {
|
||||||
FileDialogResult::None
|
FileDialogResult::None
|
||||||
}
|
}
|
||||||
|
@ -280,12 +280,10 @@ impl DiffViewState {
|
|||||||
let Ok(state) = state.read() else {
|
let Ok(state) = state.read() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let (Some(project_dir), Some(source_path)) = (
|
if let Some(source_path) =
|
||||||
&state.config.project_dir,
|
state.config.selected_obj.as_ref().and_then(|obj| obj.source_path.as_ref())
|
||||||
state.config.selected_obj.as_ref().and_then(|obj| obj.source_path.as_ref()),
|
{
|
||||||
) {
|
log::info!("Opening file {}", source_path);
|
||||||
let source_path = project_dir.join(source_path);
|
|
||||||
log::info!("Opening file {}", source_path.display());
|
|
||||||
open::that_detached(source_path).unwrap_or_else(|err| {
|
open::that_detached(source_path).unwrap_or_else(|err| {
|
||||||
log::error!("Failed to open source file: {err}");
|
log::error!("Failed to open source file: {err}");
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "objdiff-wasm",
|
"name": "objdiff-wasm",
|
||||||
"version": "2.6.0",
|
"version": "2.7.1",
|
||||||
"description": "A local diffing tool for decompilation projects.",
|
"description": "A local diffing tool for decompilation projects.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Luke Street",
|
"name": "Luke Street",
|
||||||
|
107
objdiff-wasm/src/display.ts
Normal file
107
objdiff-wasm/src/display.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
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});
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,20 @@
|
|||||||
import {ArgumentValue, DiffResult, InstructionDiff, RelocationTarget} from "../gen/diff_pb";
|
import {DiffResult} from "../gen/diff_pb";
|
||||||
import type {
|
import type {
|
||||||
ArmArchVersion,
|
ConfigProperty,
|
||||||
ArmR9Usage,
|
MappingConfig,
|
||||||
DiffObjConfig,
|
SymbolMappings,
|
||||||
MipsAbi,
|
|
||||||
MipsInstrCategory,
|
|
||||||
X86Formatter
|
|
||||||
} from '../pkg';
|
} from '../pkg';
|
||||||
import {AnyHandlerData, InMessage, OutMessage} from './worker';
|
import {AnyHandlerData, InMessage, OutMessage} from './worker';
|
||||||
|
|
||||||
// Export wasm types
|
// Export wasm types
|
||||||
export {ArmArchVersion, ArmR9Usage, MipsAbi, MipsInstrCategory, X86Formatter, DiffObjConfig};
|
export {ConfigProperty, MappingConfig, SymbolMappings};
|
||||||
|
|
||||||
// Export protobuf types
|
// Export protobuf types
|
||||||
export * from '../gen/diff_pb';
|
export * from '../gen/diff_pb';
|
||||||
|
|
||||||
|
// Export display types
|
||||||
|
export * from './display';
|
||||||
|
|
||||||
interface PromiseCallbacks<T> {
|
interface PromiseCallbacks<T> {
|
||||||
start: number;
|
start: number;
|
||||||
resolve: (value: T | PromiseLike<T>) => void;
|
resolve: (value: T | PromiseLike<T>) => void;
|
||||||
@ -111,12 +111,18 @@ async function defer<T>(message: AnyHandlerData, worker?: Worker): Promise<T> {
|
|||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runDiff(left: Uint8Array | undefined, right: Uint8Array | undefined, diff_config?: DiffObjConfig): Promise<DiffResult> {
|
export async function runDiff(
|
||||||
|
left: Uint8Array | null | undefined,
|
||||||
|
right: Uint8Array | null | undefined,
|
||||||
|
properties?: ConfigProperty[],
|
||||||
|
mappingConfig?: MappingConfig,
|
||||||
|
): Promise<DiffResult> {
|
||||||
const data = await defer<Uint8Array>({
|
const data = await defer<Uint8Array>({
|
||||||
type: 'run_diff_proto',
|
type: 'run_diff_proto',
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
diff_config
|
properties,
|
||||||
|
mappingConfig,
|
||||||
});
|
});
|
||||||
const parseStart = performance.now();
|
const parseStart = performance.now();
|
||||||
const result = DiffResult.fromBinary(data, {readUnknownField: false});
|
const result = DiffResult.fromBinary(data, {readUnknownField: false});
|
||||||
@ -124,109 +130,3 @@ export async function runDiff(left: Uint8Array | undefined, right: Uint8Array |
|
|||||||
console.debug(`Parsing message took ${end - parseStart}ms`);
|
console.debug(`Parsing message took ${end - parseStart}ms`);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
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});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,6 @@ import wasmInit, * as exports from '../pkg';
|
|||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
init: init,
|
init: init,
|
||||||
// run_diff_json: run_diff_json,
|
|
||||||
run_diff_proto: run_diff_proto,
|
run_diff_proto: run_diff_proto,
|
||||||
} as const;
|
} as const;
|
||||||
type ExtractData<T> = T extends (arg: infer U) => Promise<unknown> ? U : never;
|
type ExtractData<T> = T extends (arg: infer U) => Promise<unknown> ? U : never;
|
||||||
@ -29,24 +28,16 @@ async function initIfNeeded() {
|
|||||||
return wasmReady;
|
return wasmReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
// async function run_diff_json({left, right, config}: {
|
async function run_diff_proto({left, right, properties, mappingConfig}: {
|
||||||
// left: Uint8Array | undefined,
|
left: Uint8Array | null | undefined,
|
||||||
// right: Uint8Array | undefined,
|
right: Uint8Array | null | undefined,
|
||||||
// config?: exports.DiffObjConfig,
|
properties?: exports.ConfigProperty[],
|
||||||
// }): Promise<string> {
|
mappingConfig?: exports.MappingConfig,
|
||||||
// config = config || exports.default_diff_obj_config();
|
|
||||||
// return exports.run_diff_json(left, right, cfg);
|
|
||||||
// }
|
|
||||||
|
|
||||||
async function run_diff_proto({left, right, diff_config, mapping_config}: {
|
|
||||||
left: Uint8Array | undefined,
|
|
||||||
right: Uint8Array | undefined,
|
|
||||||
diff_config?: exports.DiffObjConfig,
|
|
||||||
mapping_config?: exports.MappingConfig,
|
|
||||||
}): Promise<Uint8Array> {
|
}): Promise<Uint8Array> {
|
||||||
diff_config = diff_config || {};
|
const diffConfig = exports.config_from_properties(properties || []);
|
||||||
mapping_config = mapping_config || {};
|
const leftObj = left ? exports.parse_object(left, diffConfig) : null;
|
||||||
return exports.run_diff_proto(left, right, diff_config, mapping_config);
|
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 AnyHandlerData = HandlerData[keyof HandlerData];
|
||||||
|
@ -28,6 +28,7 @@ export default defineConfig([
|
|||||||
// https://github.com/egoist/tsup/issues/278
|
// https://github.com/egoist/tsup/issues/278
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
await fs.copyFile('pkg/objdiff_core_bg.wasm', 'dist/objdiff_core_bg.wasm');
|
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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user