Separate compilation and linker phases (#376)

Separate between compilation and linker phases to allow deploy time
linking and back-porting era compiler changes to fix #91. Unlinked
contract binaries (caused by missing libraries or missing factory
dependencies in turn) are emitted as raw ELF object.

Few drive by fixes:
- #98
- A compiler panic on missing libraries definitions.
- Fixes some incosistent type forwarding in JSON output (empty string
vs. null object).
- Remove the unused fallback for size optimization setting.
- Remove the broken `--lvm-ir`  mode.
- CI workflow fixes.

---------

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Signed-off-by: xermicus <bigcyrill@hotmail.com>
Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
xermicus
2025-09-27 20:52:22 +02:00
committed by GitHub
parent 13faedf08a
commit 94ec34c4d5
169 changed files with 6288 additions and 5206 deletions
@@ -1,72 +1,64 @@
//! The `solc --combined-json` contract.
use std::collections::BTreeMap;
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
/// The contract.
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Contract {
/// The `solc` hashes output.
#[serde(skip_serializing_if = "Option::is_none")]
pub hashes: Option<BTreeMap<String, String>>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub hashes: BTreeMap<String, String>,
/// The `solc` ABI output.
#[serde(skip_serializing_if = "Option::is_none")]
pub abi: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
pub abi: serde_json::Value,
/// The `solc` metadata output.
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<String>,
/// The `solc` developer documentation output.
#[serde(skip_serializing_if = "Option::is_none")]
pub devdoc: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
pub devdoc: serde_json::Value,
/// The `solc` user documentation output.
#[serde(skip_serializing_if = "Option::is_none")]
pub userdoc: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
pub userdoc: serde_json::Value,
/// The `solc` storage layout output.
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_layout: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
pub storage_layout: serde_json::Value,
/// The `solc` AST output.
#[serde(skip_serializing_if = "Option::is_none")]
pub ast: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
pub ast: serde_json::Value,
/// The `solc` assembly output.
#[serde(skip_serializing_if = "Option::is_none")]
pub asm: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
pub asm: serde_json::Value,
/// LLVM-generated assembly.
#[cfg(feature = "resolc")]
#[serde(default, skip_serializing_if = "Option::is_none", skip_deserializing)]
pub assembly: Option<String>,
/// The `solc` hexadecimal binary output.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none", skip_deserializing)]
pub bin: Option<String>,
/// The `solc` hexadecimal binary runtime part output.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none", skip_deserializing)]
pub bin_runtime: Option<String>,
/// The factory dependencies.
#[serde(skip_serializing_if = "Option::is_none")]
pub factory_deps: Option<BTreeMap<String, String>>,
/// The missing libraries.
#[serde(skip_serializing_if = "Option::is_none")]
pub missing_libraries: Option<HashSet<String>>,
}
impl Contract {
/// Returns the signature hash of the specified contract entry.
/// # Panics
/// If the hashes have not been requested in the `solc` call.
pub fn entry(&self, entry: &str) -> u32 {
self.hashes
.as_ref()
.expect("Always exists")
.iter()
.find_map(|(contract_entry, hash)| {
if contract_entry.starts_with(entry) {
Some(
u32::from_str_radix(hash.as_str(), revive_common::BASE_HEXADECIMAL)
.expect("Test hash is always valid"),
)
} else {
None
}
})
.unwrap_or_else(|| panic!("Entry `{entry}` not found"))
}
/// The unlinked factory dependencies.
#[cfg(feature = "resolc")]
#[serde(default, skip_deserializing)]
pub factory_deps_unlinked: std::collections::BTreeSet<String>,
/// The factory dependencies.
#[cfg(feature = "resolc")]
#[serde(default, skip_deserializing)]
pub factory_deps: BTreeMap<String, String>,
/// The missing libraries.
#[cfg(feature = "resolc")]
#[serde(default, skip_deserializing)]
pub missing_libraries: std::collections::BTreeSet<String>,
/// The binary object format.
#[cfg(feature = "resolc")]
#[serde(default, skip_deserializing)]
pub object_format: Option<revive_common::ObjectFormat>,
}
@@ -1,79 +1,51 @@
//! The `solc --combined-json` output.
pub mod contract;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use serde::Deserialize;
use serde::Serialize;
use self::contract::Contract;
pub mod contract;
pub mod selector;
/// The `solc --combined-json` output.
#[derive(Debug, Serialize, Deserialize)]
pub struct CombinedJson {
/// The contract entries.
pub contracts: BTreeMap<String, Contract>,
/// The list of source files.
#[serde(rename = "sourceList")]
#[serde(skip_serializing_if = "Option::is_none")]
pub source_list: Option<Vec<String>>,
#[serde(default, rename = "sourceList", skip_serializing_if = "Vec::is_empty")]
pub source_list: Vec<String>,
/// The source code extra data, including the AST.
#[serde(skip_serializing_if = "Option::is_none")]
pub sources: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
pub sources: serde_json::Value,
/// The `solc` compiler version.
pub version: String,
/// The `resolc` compiler version.
#[serde(skip_serializing_if = "Option::is_none")]
pub revive_version: Option<String>,
#[cfg(feature = "resolc")]
pub resolc_version: Option<String>,
}
#[cfg(feature = "resolc")]
impl CombinedJson {
/// Returns the signature hash of the specified contract and entry.
pub fn entry(&self, path: &str, entry: &str) -> u32 {
self.contracts
.iter()
.find_map(|(name, contract)| {
if name.starts_with(path) {
Some(contract)
} else {
None
}
})
.expect("Always exists")
.entry(entry)
}
/// Returns the full contract path which can be found in `combined-json` output.
pub fn get_full_path(&self, name: &str) -> Option<String> {
self.contracts.iter().find_map(|(path, _value)| {
if let Some(last_slash_position) = path.rfind('/') {
if let Some(colon_position) = path.rfind(':') {
if &path[last_slash_position + 1..colon_position] == name {
return Some(path.to_owned());
}
}
}
None
})
}
/// Removes EVM artifacts to prevent their accidental usage.
pub fn remove_evm(&mut self) {
for (_, contract) in self.contracts.iter_mut() {
contract.bin = None;
contract.bin_runtime = None;
/// A shortcut constructor.
pub fn new(solc_version: semver::Version, resolc_version: Option<String>) -> Self {
Self {
contracts: BTreeMap::new(),
source_list: Vec::new(),
sources: serde_json::Value::Null,
version: solc_version.to_string(),
resolc_version,
}
}
/// Writes the JSON to the specified directory.
pub fn write_to_directory(
self,
output_directory: &Path,
output_directory: &std::path::Path,
overwrite: bool,
) -> anyhow::Result<()> {
let mut file_path = output_directory.to_owned();
@@ -85,10 +57,11 @@ impl CombinedJson {
);
}
File::create(&file_path)
.map_err(|error| anyhow::anyhow!("File {:?} creating error: {}", file_path, error))?
.write_all(serde_json::to_vec(&self).expect("Always valid").as_slice())
.map_err(|error| anyhow::anyhow!("File {:?} writing error: {}", file_path, error))?;
std::fs::write(
file_path.as_path(),
serde_json::to_vec(&self).expect("Always valid").as_slice(),
)
.map_err(|error| anyhow::anyhow!("File {file_path:?} writing: {error}"))?;
Ok(())
}
@@ -0,0 +1,115 @@
//! The `solc --combined-json` expected output selection flag.
use std::str::FromStr;
use serde::{Deserialize, Serialize};
/// The solc `--combind-json` invalid selector message.
pub const MESSAGE_SELECTOR_INVALID: &str = "Invalid option to --combined-json";
/// The `solc --combined-json` expected output selection flag.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Selector {
/// The ABI JSON.
#[serde(rename = "abi")]
ABI,
/// The function signature hashes JSON.
#[serde(rename = "hashes")]
Hashes,
/// The metadata.
#[serde(rename = "metadata")]
Metadata,
/// The developer documentation.
#[serde(rename = "devdoc")]
Devdoc,
/// The user documentation.
#[serde(rename = "userdoc")]
Userdoc,
/// The storage layout.
#[serde(rename = "storage-layout")]
StorageLayout,
/// The transient storage layout.
#[serde(rename = "transient-storage-layout")]
TransientStorageLayout,
/// The AST JSON.
#[serde(rename = "ast")]
AST,
/// The EVM assembly.
#[serde(rename = "asm")]
ASM,
/// The assembly.
#[serde(rename = "assembly", skip_serializing)]
Assembly,
/// The deploy bytecode.
#[serde(rename = "bin", skip_serializing)]
Bytecode,
/// The runtime bytecode.
#[serde(rename = "bin-runtime", skip_serializing)]
BytecodeRuntime,
}
impl Selector {
/// Converts the comma-separated CLI argument into an array of flags.
pub fn from_cli(format: &str) -> Vec<anyhow::Result<Self>> {
format
.split(',')
.map(|flag| Self::from_str(flag.trim()))
.collect()
}
/// Whether the selector is available in `solc`.
pub fn is_source_solc(&self) -> bool {
!matches!(
self,
Self::Assembly | Self::Bytecode | Self::BytecodeRuntime
)
}
}
impl FromStr for Selector {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"abi" => Ok(Self::ABI),
"hashes" => Ok(Self::Hashes),
"metadata" => Ok(Self::Metadata),
"devdoc" => Ok(Self::Devdoc),
"userdoc" => Ok(Self::Userdoc),
"storage-layout" => Ok(Self::StorageLayout),
"transient-storage-layout" => Ok(Self::TransientStorageLayout),
"ast" => Ok(Self::AST),
"asm" => Ok(Self::ASM),
"bin" => Ok(Self::Bytecode),
"bin-runtime" => Ok(Self::BytecodeRuntime),
"assembly" => Ok(Self::Assembly),
selector => anyhow::bail!("{MESSAGE_SELECTOR_INVALID}: {selector}"),
}
}
}
impl std::fmt::Display for Selector {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::ABI => write!(f, "abi"),
Self::Hashes => write!(f, "hashes"),
Self::Metadata => write!(f, "metadata"),
Self::Devdoc => write!(f, "devdoc"),
Self::Userdoc => write!(f, "userdoc"),
Self::StorageLayout => write!(f, "storage-layout"),
Self::TransientStorageLayout => write!(f, "transient-storage-layout"),
Self::AST => write!(f, "ast"),
Self::ASM => write!(f, "asm"),
Self::Bytecode => write!(f, "bin"),
Self::BytecodeRuntime => write!(f, "bin-runtime"),
Self::Assembly => write!(f, "assembly"),
}
}
}