mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-19 12:11:04 +00:00
resolc crate (#328)
- Factor the YUL crate out of `revive-solidity`. - `revive-solidity` is in reality not a Solidity implementation but the revive solidity compiler driver (`resolc`). By renaming we not only get this straight but also a binary with the same name as the crate which should be less confusing. --------- Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
//! The Solidity compiler.
|
||||
|
||||
#[cfg(not(target_os = "emscripten"))]
|
||||
pub mod solc_compiler;
|
||||
#[cfg(target_os = "emscripten")]
|
||||
pub mod soljson_compiler;
|
||||
pub mod version;
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use revive_solc_json_interface::combined_json::CombinedJson;
|
||||
use revive_solc_json_interface::SolcStandardJsonInput;
|
||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||
|
||||
use self::version::Version;
|
||||
|
||||
/// The first version of `solc` with the support of standard JSON interface.
|
||||
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 0);
|
||||
|
||||
/// The last supported version of `solc`.
|
||||
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 30);
|
||||
|
||||
/// `--include-path` was introduced in solc `0.8.8` <https://github.com/ethereum/solidity/releases/tag/v0.8.8>
|
||||
pub const FIRST_INCLUDE_PATH_VERSION: semver::Version = semver::Version::new(0, 8, 8);
|
||||
|
||||
/// The Solidity compiler.
|
||||
pub trait Compiler {
|
||||
/// Compiles the Solidity `--standard-json` input into Yul IR.
|
||||
fn standard_json(
|
||||
&mut self,
|
||||
input: SolcStandardJsonInput,
|
||||
base_path: Option<String>,
|
||||
include_paths: Vec<String>,
|
||||
allow_paths: Option<String>,
|
||||
) -> anyhow::Result<SolcStandardJsonOutput>;
|
||||
|
||||
/// The `solc --combined-json abi,hashes...` mirror.
|
||||
fn combined_json(
|
||||
&self,
|
||||
paths: &[PathBuf],
|
||||
combined_json_argument: &str,
|
||||
) -> anyhow::Result<CombinedJson>;
|
||||
|
||||
/// The `solc` Yul validator.
|
||||
fn validate_yul(&self, path: &Path) -> anyhow::Result<()>;
|
||||
|
||||
/// The `solc --version` mini-parser.
|
||||
fn version(&mut self) -> anyhow::Result<Version>;
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
//! The Solidity compiler solc interface.
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use revive_solc_json_interface::combined_json::CombinedJson;
|
||||
use revive_solc_json_interface::SolcStandardJsonInput;
|
||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||
|
||||
use crate::solc::version::Version;
|
||||
|
||||
use super::Compiler;
|
||||
|
||||
/// The Solidity compiler.
|
||||
pub struct SolcCompiler {
|
||||
/// The binary executable name.
|
||||
pub executable: String,
|
||||
}
|
||||
|
||||
impl SolcCompiler {
|
||||
/// The default executable name.
|
||||
pub const DEFAULT_EXECUTABLE_NAME: &'static str = "solc";
|
||||
|
||||
/// A shortcut constructor.
|
||||
/// Different tools may use different `executable` names. For example, the integration tester
|
||||
/// uses `solc-<version>` format.
|
||||
pub fn new(executable: String) -> anyhow::Result<Self> {
|
||||
if let Err(error) = which::which(executable.as_str()) {
|
||||
anyhow::bail!(
|
||||
"The `{executable}` executable not found in ${{PATH}}: {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
Ok(Self { executable })
|
||||
}
|
||||
}
|
||||
|
||||
impl Compiler for SolcCompiler {
|
||||
/// Compiles the Solidity `--standard-json` input into Yul IR.
|
||||
fn standard_json(
|
||||
&mut self,
|
||||
mut input: SolcStandardJsonInput,
|
||||
base_path: Option<String>,
|
||||
include_paths: Vec<String>,
|
||||
allow_paths: Option<String>,
|
||||
) -> anyhow::Result<SolcStandardJsonOutput> {
|
||||
let version = self.version()?.validate(&include_paths)?.default;
|
||||
|
||||
let mut command = std::process::Command::new(self.executable.as_str());
|
||||
command.stdin(std::process::Stdio::piped());
|
||||
command.stdout(std::process::Stdio::piped());
|
||||
command.arg("--standard-json");
|
||||
|
||||
for include_path in include_paths.into_iter() {
|
||||
command.arg("--include-path");
|
||||
command.arg(include_path);
|
||||
}
|
||||
if let Some(base_path) = base_path {
|
||||
command.arg("--base-path");
|
||||
command.arg(base_path);
|
||||
}
|
||||
if let Some(allow_paths) = allow_paths {
|
||||
command.arg("--allow-paths");
|
||||
command.arg(allow_paths);
|
||||
}
|
||||
|
||||
input.normalize(&version);
|
||||
|
||||
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
|
||||
|
||||
let input_json = serde_json::to_vec(&input).expect("Always valid");
|
||||
|
||||
let process = command.spawn().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess spawning error: {:?}", self.executable, error)
|
||||
})?;
|
||||
process
|
||||
.stdin
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("{} stdin getting error", self.executable))?
|
||||
.write_all(input_json.as_slice())
|
||||
.map_err(|error| {
|
||||
anyhow::anyhow!("{} stdin writing error: {:?}", self.executable, error)
|
||||
})?;
|
||||
|
||||
let output = process.wait_with_output().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess output error: {:?}", self.executable, error)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"{} error: {}",
|
||||
self.executable,
|
||||
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
let mut output: SolcStandardJsonOutput =
|
||||
revive_common::deserialize_from_slice(output.stdout.as_slice()).map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"{} subprocess output parsing error: {}\n{}",
|
||||
self.executable,
|
||||
error,
|
||||
revive_common::deserialize_from_slice::<serde_json::Value>(
|
||||
output.stdout.as_slice()
|
||||
)
|
||||
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
|
||||
.unwrap_or_else(
|
||||
|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
output.preprocess_ast(suppressed_warnings.as_slice())?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// The `solc --combined-json abi,hashes...` mirror.
|
||||
fn combined_json(
|
||||
&self,
|
||||
paths: &[PathBuf],
|
||||
combined_json_argument: &str,
|
||||
) -> anyhow::Result<CombinedJson> {
|
||||
let mut command = std::process::Command::new(self.executable.as_str());
|
||||
command.args(paths);
|
||||
|
||||
let mut combined_json_flags = Vec::new();
|
||||
let mut combined_json_fake_flag_pushed = false;
|
||||
let mut filtered_flags = Vec::with_capacity(3);
|
||||
for flag in combined_json_argument.split(',') {
|
||||
match flag {
|
||||
flag @ "asm" | flag @ "bin" | flag @ "bin-runtime" => filtered_flags.push(flag),
|
||||
flag => combined_json_flags.push(flag),
|
||||
}
|
||||
}
|
||||
if combined_json_flags.is_empty() {
|
||||
combined_json_flags.push("ast");
|
||||
combined_json_fake_flag_pushed = true;
|
||||
}
|
||||
command.arg("--combined-json");
|
||||
command.arg(combined_json_flags.join(","));
|
||||
|
||||
let output = command.output().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
writeln!(
|
||||
std::io::stdout(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(output.stdout.as_slice())
|
||||
)?;
|
||||
writeln!(
|
||||
std::io::stdout(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(output.stderr.as_slice())
|
||||
)?;
|
||||
anyhow::bail!(
|
||||
"{} error: {}",
|
||||
self.executable,
|
||||
String::from_utf8_lossy(output.stdout.as_slice()).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
let mut combined_json: CombinedJson =
|
||||
revive_common::deserialize_from_slice(output.stdout.as_slice()).map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"{} subprocess output parsing error: {}\n{}",
|
||||
self.executable,
|
||||
error,
|
||||
revive_common::deserialize_from_slice::<serde_json::Value>(
|
||||
output.stdout.as_slice()
|
||||
)
|
||||
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
|
||||
.unwrap_or_else(
|
||||
|_| String::from_utf8_lossy(output.stdout.as_slice()).to_string()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
for filtered_flag in filtered_flags.into_iter() {
|
||||
for (_path, contract) in combined_json.contracts.iter_mut() {
|
||||
match filtered_flag {
|
||||
"asm" => contract.asm = Some(serde_json::Value::Null),
|
||||
"bin" => contract.bin = Some("".to_owned()),
|
||||
"bin-runtime" => contract.bin_runtime = Some("".to_owned()),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
if combined_json_fake_flag_pushed {
|
||||
combined_json.source_list = None;
|
||||
combined_json.sources = None;
|
||||
}
|
||||
combined_json.remove_evm();
|
||||
|
||||
Ok(combined_json)
|
||||
}
|
||||
|
||||
/// The `solc` Yul validator.
|
||||
fn validate_yul(&self, path: &Path) -> anyhow::Result<()> {
|
||||
let mut command = std::process::Command::new(self.executable.as_str());
|
||||
command.arg("--strict-assembly");
|
||||
command.arg(path);
|
||||
|
||||
let output = command.output().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"{} error: {}",
|
||||
self.executable,
|
||||
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The `solc --version` mini-parser.
|
||||
fn version(&mut self) -> anyhow::Result<Version> {
|
||||
let mut command = std::process::Command::new(self.executable.as_str());
|
||||
command.arg("--version");
|
||||
let output = command.output().map_err(|error| {
|
||||
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
"{} error: {}",
|
||||
self.executable,
|
||||
String::from_utf8_lossy(output.stderr.as_slice()).to_string()
|
||||
);
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(output.stdout.as_slice());
|
||||
let long = stdout
|
||||
.lines()
|
||||
.nth(1)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("{} version parsing: not enough lines", self.executable)
|
||||
})?
|
||||
.split(' ')
|
||||
.nth(1)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"{} version parsing: not enough words in the 2nd line",
|
||||
self.executable
|
||||
)
|
||||
})?
|
||||
.to_owned();
|
||||
let default: semver::Version = long
|
||||
.split('+')
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("{} version parsing: metadata dropping", self.executable)
|
||||
})?
|
||||
.parse()
|
||||
.map_err(|error| anyhow::anyhow!("{} version parsing: {}", self.executable, error))?;
|
||||
|
||||
let l2_revision: Option<semver::Version> = stdout
|
||||
.lines()
|
||||
.nth(2)
|
||||
.and_then(|line| line.split(' ').nth(1))
|
||||
.and_then(|line| line.split('-').nth(1))
|
||||
.and_then(|version| version.parse().ok());
|
||||
|
||||
Ok(Version::new(long, default, l2_revision))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//! The Solidity compiler solJson interface.
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use revive_solc_json_interface::combined_json::CombinedJson;
|
||||
use revive_solc_json_interface::SolcStandardJsonInput;
|
||||
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||
|
||||
use crate::solc::version::Version;
|
||||
use anyhow::Context;
|
||||
use std::ffi::{c_char, c_void, CStr, CString};
|
||||
|
||||
use super::Compiler;
|
||||
|
||||
extern "C" {
|
||||
fn soljson_version() -> *const c_char;
|
||||
fn soljson_compile(inputPtr: *const c_char, inputLen: usize) -> *const c_char;
|
||||
}
|
||||
|
||||
/// The Solidity compiler.
|
||||
pub struct SoljsonCompiler;
|
||||
|
||||
impl Compiler for SoljsonCompiler {
|
||||
/// Compiles the Solidity `--standard-json` input into Yul IR.
|
||||
fn standard_json(
|
||||
&mut self,
|
||||
mut input: SolcStandardJsonInput,
|
||||
base_path: Option<String>,
|
||||
include_paths: Vec<String>,
|
||||
allow_paths: Option<String>,
|
||||
) -> anyhow::Result<SolcStandardJsonOutput> {
|
||||
if !include_paths.is_empty() {
|
||||
anyhow::bail!("configuring include paths is not supported with solJson")
|
||||
}
|
||||
if base_path.is_some() {
|
||||
anyhow::bail!("configuring the base path is not supported with solJson")
|
||||
}
|
||||
if allow_paths.is_some() {
|
||||
anyhow::bail!("configuring allow paths is not supported with solJson")
|
||||
}
|
||||
|
||||
let version = self.version()?.validate(&include_paths)?.default;
|
||||
input.normalize(&version);
|
||||
|
||||
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
|
||||
|
||||
let input_json = serde_json::to_string(&input).expect("Always valid");
|
||||
let out = Self::compile_standard_json(input_json)?;
|
||||
let mut output: SolcStandardJsonOutput =
|
||||
revive_common::deserialize_from_slice(out.as_bytes()).map_err(|error| {
|
||||
anyhow::anyhow!(
|
||||
"Soljson output parsing error: {}\n{}",
|
||||
error,
|
||||
revive_common::deserialize_from_slice::<serde_json::Value>(out.as_bytes())
|
||||
.map(|json| serde_json::to_string_pretty(&json).expect("Always valid"))
|
||||
.unwrap_or_else(|_| String::from_utf8_lossy(out.as_bytes()).to_string()),
|
||||
)
|
||||
})?;
|
||||
output.preprocess_ast(suppressed_warnings.as_slice())?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn combined_json(
|
||||
&self,
|
||||
_paths: &[PathBuf],
|
||||
_combined_json_argument: &str,
|
||||
) -> anyhow::Result<CombinedJson> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn validate_yul(&self, _path: &Path) -> anyhow::Result<()> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn version(&mut self) -> anyhow::Result<Version> {
|
||||
let version = Self::get_soljson_version()?;
|
||||
let long = version.clone();
|
||||
let default: semver::Version = version
|
||||
.split('+')
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Soljson version parsing: metadata dropping"))?
|
||||
.parse()
|
||||
.map_err(|error| anyhow::anyhow!("Soljson version parsing: {}", error))?;
|
||||
let l2_revision: Option<semver::Version> = version
|
||||
.split('-')
|
||||
.nth(1)
|
||||
.and_then(|version| version.parse().ok());
|
||||
Ok(Version::new(long, default, l2_revision))
|
||||
}
|
||||
}
|
||||
|
||||
impl SoljsonCompiler {
|
||||
fn get_soljson_version() -> anyhow::Result<String> {
|
||||
unsafe {
|
||||
let version_ptr = soljson_version();
|
||||
let version = CStr::from_ptr(version_ptr)
|
||||
.to_str()
|
||||
.with_context(|| "Failed to convert C string to Rust string")
|
||||
.map(str::to_owned);
|
||||
libc::free(version_ptr as *mut c_void);
|
||||
Ok(version?)
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_standard_json(input: String) -> anyhow::Result<String> {
|
||||
let c_input = CString::new(input).unwrap();
|
||||
let c_input_len = c_input.as_bytes().len();
|
||||
|
||||
unsafe {
|
||||
let output_ptr = soljson_compile(c_input.as_ptr(), c_input_len);
|
||||
let output_json = CStr::from_ptr(output_ptr)
|
||||
.to_str()
|
||||
.with_context(|| "Failed to convert C string to Rust string")
|
||||
.map(str::to_owned);
|
||||
libc::free(output_ptr as *mut c_void);
|
||||
Ok(output_json?)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//! The Solidity compiler version.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
/// The Solidity compiler version.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Version {
|
||||
/// The long version string.
|
||||
pub long: String,
|
||||
/// The short `semver`.
|
||||
pub default: semver::Version,
|
||||
/// The L2 revision additional versioning.
|
||||
pub l2_revision: Option<semver::Version>,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
/// A shortcut constructor.
|
||||
pub fn new(
|
||||
long: String,
|
||||
default: semver::Version,
|
||||
l2_revision: Option<semver::Version>,
|
||||
) -> Self {
|
||||
Self {
|
||||
long,
|
||||
default,
|
||||
l2_revision,
|
||||
}
|
||||
}
|
||||
|
||||
/// A shortcut constructor for a simple version.
|
||||
pub fn new_simple(version: semver::Version) -> Self {
|
||||
Self {
|
||||
long: version.to_string(),
|
||||
default: version,
|
||||
l2_revision: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(self, include_paths: &[String]) -> anyhow::Result<Self> {
|
||||
if self.default < super::FIRST_SUPPORTED_VERSION {
|
||||
anyhow::bail!(
|
||||
"`solc` versions <{} are not supported, found {}",
|
||||
super::FIRST_SUPPORTED_VERSION,
|
||||
self.default
|
||||
);
|
||||
}
|
||||
if self.default > super::LAST_SUPPORTED_VERSION {
|
||||
anyhow::bail!(
|
||||
"`solc` versions >{} are not supported, found {}",
|
||||
super::LAST_SUPPORTED_VERSION,
|
||||
self.default
|
||||
);
|
||||
}
|
||||
if !include_paths.is_empty() && self.default < super::FIRST_INCLUDE_PATH_VERSION {
|
||||
anyhow::bail!("--include-path is not supported in solc {}", self.default);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user