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 authored and GitHub committed 2025-09-27 20:52:22 +02:00
1 parent 13faedf08a
commit 94ec34c4d5
169 files changed
+6288 -5206

No files matched your search

@@ -1,33 +1,40 @@
//! The `solc --standard-json` output.
pub mod contract;
pub mod error;
pub mod source;
use std::collections::BTreeMap;
use serde::Deserialize;
use serde::Serialize;
#[cfg(feature = "resolc")]
use crate::warning::Warning;
use crate::standard_json::input::settings::warning::Warning;
use crate::standard_json::output::error::error_handler::ErrorHandler;
#[cfg(feature = "resolc")]
use crate::SolcStandardJsonInputSettingsSelection;
#[cfg(feature = "resolc")]
use crate::SolcStandardJsonInputSource;
#[cfg(feature = "parallel")]
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use self::contract::Contract;
use self::error::Error as SolcStandardJsonOutputError;
use self::source::Source;
pub mod contract;
pub mod error;
pub mod source;
/// The `solc --standard-json` output.
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Output {
/// The file-contract hashmap.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contracts: Option<BTreeMap<String, BTreeMap<String, Contract>>>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub contracts: BTreeMap<String, BTreeMap<String, Contract>>,
/// The source code mapping data.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sources: Option<BTreeMap<String, Source>>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub sources: BTreeMap<String, Source>,
/// The compilation errors and warnings.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub errors: Option<Vec<SolcStandardJsonOutputError>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub errors: Vec<SolcStandardJsonOutputError>,
/// The `solc` compiler version.
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
@@ -39,33 +46,156 @@ pub struct Output {
pub revive_version: Option<String>,
}
#[cfg(feature = "resolc")]
impl Output {
/// Traverses the AST and returns the list of additional errors and warnings.
#[cfg(feature = "resolc")]
pub fn preprocess_ast(&mut self, suppressed_warnings: &[Warning]) -> anyhow::Result<()> {
let sources = match self.sources.as_ref() {
Some(sources) => sources,
None => return Ok(()),
};
/// Initializes a standard JSON output.
///
/// Is used for projects compiled without `solc`.
pub fn new(
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
messages: &mut Vec<SolcStandardJsonOutputError>,
) -> Self {
let sources = sources
.keys()
.enumerate()
.map(|(index, path)| (path.to_owned(), Source::new(index)))
.collect::<BTreeMap<String, Source>>();
let mut messages = Vec::new();
for (path, source) in sources.iter() {
if let Some(ast) = source.ast.as_ref() {
let mut polkavm_messages = Source::get_messages(ast, suppressed_warnings);
for message in polkavm_messages.iter_mut() {
message.push_contract_path(path.as_str());
}
messages.extend(polkavm_messages);
Self {
contracts: BTreeMap::new(),
sources,
errors: std::mem::take(messages),
version: None,
long_version: None,
revive_version: None,
}
}
/// Initializes a standard JSON output with messages.
///
/// Is used to emit errors in standard JSON mode.
pub fn new_with_messages(messages: Vec<SolcStandardJsonOutputError>) -> Self {
Self {
contracts: BTreeMap::new(),
sources: BTreeMap::new(),
errors: messages,
version: None,
long_version: None,
revive_version: None,
}
}
/// Prunes the output JSON and prints it to stdout.
pub fn write_and_exit(
mut self,
selection_to_prune: SolcStandardJsonInputSettingsSelection,
) -> ! {
let sources = self.sources.values_mut().collect::<Vec<&mut Source>>();
for source in sources.into_iter() {
if selection_to_prune
.contains(&crate::SolcStandardJsonInputSettingsSelectionFileFlag::AST)
{
source.ast = None;
}
}
self.errors = match self.errors.take() {
Some(mut errors) => {
errors.extend(messages);
Some(errors)
let contracts = self
.contracts
.values_mut()
.flat_map(|contracts| contracts.values_mut())
.collect::<Vec<&mut Contract>>();
for contract in contracts.into_iter() {
if selection_to_prune
.contains(&crate::SolcStandardJsonInputSettingsSelectionFileFlag::Metadata)
{
contract.metadata = serde_json::Value::Null;
}
None => Some(messages),
};
if selection_to_prune
.contains(&crate::SolcStandardJsonInputSettingsSelectionFileFlag::Yul)
{
contract.ir_optimized = String::new();
}
if let Some(ref mut evm) = contract.evm {
if selection_to_prune.contains(
&crate::SolcStandardJsonInputSettingsSelectionFileFlag::MethodIdentifiers,
) {
evm.method_identifiers.clear();
}
}
}
self.contracts.retain(|_, contracts| {
contracts.retain(|_, contract| !contract.is_empty());
!contracts.is_empty()
});
serde_json::to_writer(std::io::stdout(), &self).expect("Stdout writing error");
std::process::exit(revive_common::EXIT_CODE_SUCCESS);
}
/// Traverses the AST and returns the list of additional errors and warnings.
pub fn preprocess_ast(
&mut self,
sources: &BTreeMap<String, SolcStandardJsonInputSource>,
suppressed_warnings: &[Warning],
) -> anyhow::Result<()> {
let id_paths: BTreeMap<usize, &String> = self
.sources
.iter()
.map(|(path, source)| (source.id, path))
.collect();
#[cfg(feature = "parallel")]
let iter = self.sources.par_iter();
#[cfg(not(feature = "parallel"))]
let iter = self.sources.iter();
let messages: Vec<SolcStandardJsonOutputError> = iter
.flat_map(|(_path, source)| {
source
.ast
.as_ref()
.map(|ast| Source::get_messages(ast, &id_paths, sources, suppressed_warnings))
.unwrap_or_default()
})
.collect();
self.errors.extend(messages);
Ok(())
}
/// Pushes an arbitrary error with path.
///
/// Please do not push project-general errors without paths here.
pub fn push_error(&mut self, path: Option<String>, error: anyhow::Error) {
use crate::standard_json::output::error::source_location::SourceLocation;
self.errors.push(SolcStandardJsonOutputError::new_error(
error,
path.map(SourceLocation::new),
None,
));
}
}
impl ErrorHandler for Output {
fn errors(&self) -> Vec<&SolcStandardJsonOutputError> {
self.errors
.iter()
.filter(|error| error.is_error())
.collect()
}
fn take_warnings(&mut self) -> Vec<SolcStandardJsonOutputError> {
let warnings = self
.errors
.iter()
.filter(|message| message.is_warning())
.cloned()
.collect();
self.errors.retain(|message| !message.is_warning());
warnings
}
}