use crate::disassembler::UnknownHandling;
use crate::error::Result;
use crate::manifest::ContractManifest;
use crate::nef::NefParser;
use crate::{Decompiler, OutputFormat};
mod report;
pub use report::{WebDecompileReport, WebDisasmReport, WebInfoReport};
#[derive(Debug, Clone, Copy, Default)]
pub struct WebDisasmOptions {
pub fail_on_unknown_opcodes: bool,
}
#[derive(Debug, Clone)]
pub struct WebDecompileOptions {
pub manifest_json: Option<String>,
pub strict_manifest: bool,
pub fail_on_unknown_opcodes: bool,
pub inline_single_use_temps: bool,
pub emit_trace_comments: bool,
pub output_format: OutputFormat,
}
impl Default for WebDecompileOptions {
fn default() -> Self {
Self {
manifest_json: None,
strict_manifest: false,
fail_on_unknown_opcodes: false,
inline_single_use_temps: true,
emit_trace_comments: false,
output_format: OutputFormat::All,
}
}
}
pub fn info_report(nef_bytes: &[u8], manifest_json: Option<&str>) -> Result<WebInfoReport> {
let nef = NefParser::new().parse(nef_bytes)?;
let manifest = parse_manifest(manifest_json, false)?;
Ok(report::build_info_report(&nef, manifest.as_ref()))
}
pub fn disasm_report(nef_bytes: &[u8], options: WebDisasmOptions) -> Result<WebDisasmReport> {
let handling = unknown_handling(options.fail_on_unknown_opcodes);
let nef = NefParser::new().parse(nef_bytes)?;
let output = Decompiler::with_unknown_handling(handling).disassemble_bytes(nef_bytes)?;
Ok(report::build_disasm_report(&nef, output))
}
pub fn decompile_report(
nef_bytes: &[u8],
options: WebDecompileOptions,
) -> Result<WebDecompileReport> {
let manifest = parse_manifest(options.manifest_json.as_deref(), options.strict_manifest)?;
let handling = unknown_handling(options.fail_on_unknown_opcodes);
let decompiler = Decompiler::with_unknown_handling(handling)
.with_inline_single_use_temps(options.inline_single_use_temps)
.with_trace_comments(options.emit_trace_comments);
let result =
decompiler.decompile_bytes_with_manifest(nef_bytes, manifest, options.output_format)?;
Ok(report::build_decompile_report(result))
}
fn parse_manifest(manifest_json: Option<&str>, strict: bool) -> Result<Option<ContractManifest>> {
manifest_json
.map(|json| {
if strict {
ContractManifest::from_json_str_strict(json)
} else {
ContractManifest::from_json_str(json)
}
})
.transpose()
}
fn unknown_handling(fail_on_unknown_opcodes: bool) -> UnknownHandling {
if fail_on_unknown_opcodes {
UnknownHandling::Error
} else {
UnknownHandling::Permit
}
}
#[cfg(target_arch = "wasm32")]
use crate::Error;
#[cfg(target_arch = "wasm32")]
use serde::de::DeserializeOwned;
#[cfg(target_arch = "wasm32")]
use serde::Deserialize;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
#[derive(Debug, Default, Deserialize)]
struct JsInfoOptions {
manifest_json: Option<String>,
strict_manifest: bool,
}
#[cfg(target_arch = "wasm32")]
#[derive(Debug, Default, Deserialize)]
struct JsDisasmOptions {
fail_on_unknown_opcodes: bool,
}
#[cfg(target_arch = "wasm32")]
#[derive(Debug, Deserialize)]
#[serde(default)]
struct JsDecompileOptions {
manifest_json: Option<String>,
strict_manifest: bool,
fail_on_unknown_opcodes: bool,
inline_single_use_temps: bool,
emit_trace_comments: bool,
output_format: Option<String>,
}
#[cfg(target_arch = "wasm32")]
impl Default for JsDecompileOptions {
fn default() -> Self {
let defaults = WebDecompileOptions::default();
Self {
manifest_json: None,
strict_manifest: false,
fail_on_unknown_opcodes: false,
inline_single_use_temps: defaults.inline_single_use_temps,
emit_trace_comments: defaults.emit_trace_comments,
output_format: None,
}
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(js_name = initPanicHook)]
pub fn init_panic_hook() {
console_error_panic_hook::set_once();
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(js_name = infoReport)]
pub fn info_report_wasm(
nef_bytes: &[u8],
options: JsValue,
) -> std::result::Result<JsValue, JsValue> {
let options: JsInfoOptions = parse_js_options(options)?;
let manifest = parse_manifest(options.manifest_json.as_deref(), options.strict_manifest)
.map_err(to_js_error)?;
let nef = NefParser::new().parse(nef_bytes).map_err(to_js_error)?;
serde_wasm_bindgen::to_value(&report::build_info_report(&nef, manifest.as_ref()))
.map_err(|err| JsValue::from_str(&err.to_string()))
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(js_name = disasmReport)]
pub fn disasm_report_wasm(
nef_bytes: &[u8],
options: JsValue,
) -> std::result::Result<JsValue, JsValue> {
let options: JsDisasmOptions = parse_js_options(options)?;
let report = disasm_report(
nef_bytes,
WebDisasmOptions {
fail_on_unknown_opcodes: options.fail_on_unknown_opcodes,
},
)
.map_err(to_js_error)?;
serde_wasm_bindgen::to_value(&report).map_err(|err| JsValue::from_str(&err.to_string()))
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(js_name = decompileReport)]
pub fn decompile_report_wasm(
nef_bytes: &[u8],
options: JsValue,
) -> std::result::Result<JsValue, JsValue> {
let options: JsDecompileOptions = parse_js_options(options)?;
let output_format = parse_output_format(options.output_format.as_deref())
.map_err(|err| JsValue::from_str(&err))?;
let report = decompile_report(
nef_bytes,
WebDecompileOptions {
manifest_json: options.manifest_json,
strict_manifest: options.strict_manifest,
fail_on_unknown_opcodes: options.fail_on_unknown_opcodes,
inline_single_use_temps: options.inline_single_use_temps,
emit_trace_comments: options.emit_trace_comments,
output_format,
},
)
.map_err(to_js_error)?;
serde_wasm_bindgen::to_value(&report).map_err(|err| JsValue::from_str(&err.to_string()))
}
#[cfg(target_arch = "wasm32")]
fn parse_js_options<T>(value: JsValue) -> std::result::Result<T, JsValue>
where
T: Default + DeserializeOwned,
{
if value.is_null() || value.is_undefined() {
Ok(T::default())
} else {
serde_wasm_bindgen::from_value(value)
.map_err(|err| JsValue::from_str(&format!("invalid options: {err}")))
}
}
#[cfg(target_arch = "wasm32")]
fn parse_output_format(value: Option<&str>) -> std::result::Result<OutputFormat, String> {
match value.unwrap_or("all") {
"all" => Ok(OutputFormat::All),
"pseudocode" => Ok(OutputFormat::Pseudocode),
"high_level" | "highLevel" => Ok(OutputFormat::HighLevel),
"csharp" | "c_sharp" => Ok(OutputFormat::CSharp),
other => Err(format!("invalid output_format: {other}")),
}
}
#[cfg(target_arch = "wasm32")]
fn to_js_error(err: Error) -> JsValue {
JsValue::from_str(&err.to_string())
}