1use super::{
2 restrictions::CompilerSettingsRestrictions, CompilationError, Compiler, CompilerInput,
3 CompilerOutput, CompilerSettings, CompilerVersion, Language, ParsedSource,
4};
5use crate::{
6 resolver::{
7 parse::{SolData, SolParser},
8 Node,
9 },
10 SourceParser,
11};
12use foundry_compilers_artifacts::{
13 error::SourceLocation,
14 output_selection::OutputSelection,
15 remappings::Remapping,
16 sources::{Source, Sources},
17 BytecodeHash, Contract, Error, EvmVersion, Settings, Severity, SolcInput,
18};
19use foundry_compilers_core::error::{Result, SolcError, SolcIoError};
20use rayon::prelude::*;
21use semver::Version;
22use serde::{Deserialize, Serialize};
23use std::{
24 borrow::Cow,
25 collections::{BTreeMap, BTreeSet},
26 ops::{Deref, DerefMut},
27 path::{Path, PathBuf},
28};
29
30pub use foundry_compilers_artifacts::SolcLanguage;
31
32mod compiler;
33pub use compiler::{Solc, SOLC_EXTENSIONS};
34
35#[derive(Clone, Debug)]
36#[cfg_attr(feature = "svm-solc", derive(Default))]
37pub enum SolcCompiler {
38 #[default]
39 #[cfg(feature = "svm-solc")]
40 AutoDetect,
41
42 Specific(Solc),
43}
44
45impl Language for SolcLanguage {
46 const FILE_EXTENSIONS: &'static [&'static str] = SOLC_EXTENSIONS;
47}
48
49impl Compiler for SolcCompiler {
50 type Input = SolcVersionedInput;
51 type CompilationError = Error;
52 type Parser = SolParser;
53 type Settings = SolcSettings;
54 type Language = SolcLanguage;
55 type CompilerContract = Contract;
56
57 fn compile(
58 &self,
59 input: &Self::Input,
60 ) -> Result<CompilerOutput<Self::CompilationError, Self::CompilerContract>> {
61 let mut solc = match self {
62 Self::Specific(solc) => solc.clone(),
63
64 #[cfg(feature = "svm-solc")]
65 Self::AutoDetect => Solc::find_or_install(&input.version)?,
66 };
67 solc.base_path.clone_from(&input.cli_settings.base_path);
68 solc.allow_paths.clone_from(&input.cli_settings.allow_paths);
69 solc.include_paths.clone_from(&input.cli_settings.include_paths);
70 solc.extra_args.extend_from_slice(&input.cli_settings.extra_args);
71
72 let solc_output = solc.compile(&input.input)?;
73
74 let output = CompilerOutput {
75 errors: solc_output.errors,
76 contracts: solc_output.contracts,
77 sources: solc_output.sources,
78 metadata: BTreeMap::new(),
79 };
80
81 Ok(output)
82 }
83
84 fn available_versions(&self, _language: &Self::Language) -> Vec<CompilerVersion> {
85 match self {
86 Self::Specific(solc) => vec![CompilerVersion::Installed(Version::new(
87 solc.version.major,
88 solc.version.minor,
89 solc.version.patch,
90 ))],
91
92 #[cfg(feature = "svm-solc")]
93 Self::AutoDetect => {
94 let mut all_versions = Solc::installed_versions()
95 .into_iter()
96 .map(CompilerVersion::Installed)
97 .collect::<Vec<_>>();
98 let mut uniques = all_versions
99 .iter()
100 .map(|v| {
101 let v = v.as_ref();
102 (v.major, v.minor, v.patch)
103 })
104 .collect::<std::collections::HashSet<_>>();
105 all_versions.extend(
106 Solc::released_versions()
107 .into_iter()
108 .filter(|v| uniques.insert((v.major, v.minor, v.patch)))
109 .map(CompilerVersion::Remote),
110 );
111 all_versions.sort_unstable();
112 all_versions
113 }
114 }
115 }
116}
117
118#[derive(Clone, Debug, Serialize, Deserialize)]
119#[serde(rename_all = "camelCase")]
120pub struct SolcVersionedInput {
121 pub version: Version,
122 #[serde(flatten)]
123 pub input: SolcInput,
124 #[serde(flatten)]
125 pub cli_settings: CliSettings,
126}
127
128impl CompilerInput for SolcVersionedInput {
129 type Settings = SolcSettings;
130 type Language = SolcLanguage;
131
132 fn build(
137 sources: Sources,
138 settings: Self::Settings,
139 language: Self::Language,
140 version: Version,
141 ) -> Self {
142 let SolcSettings { settings, cli_settings } = settings;
143 let input = SolcInput::new(language, sources, settings).sanitized(&version);
144
145 Self { version, input, cli_settings }
146 }
147
148 fn language(&self) -> Self::Language {
149 self.input.language
150 }
151
152 fn version(&self) -> &Version {
153 &self.version
154 }
155
156 fn sources(&self) -> impl Iterator<Item = (&Path, &Source)> {
157 self.input.sources.iter().map(|(path, source)| (path.as_path(), source))
158 }
159
160 fn compiler_name(&self) -> Cow<'static, str> {
161 "Solc".into()
162 }
163
164 fn strip_prefix(&mut self, base: &Path) {
165 self.input.strip_prefix(base);
166 }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
170#[serde(rename_all = "camelCase")]
171pub struct CliSettings {
172 #[serde(default, skip_serializing_if = "Vec::is_empty")]
173 pub extra_args: Vec<String>,
174 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
175 pub allow_paths: BTreeSet<PathBuf>,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub base_path: Option<PathBuf>,
178 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
179 pub include_paths: BTreeSet<PathBuf>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
183pub struct SolcSettings {
184 #[serde(flatten)]
186 pub settings: Settings,
187 #[serde(flatten)]
189 pub cli_settings: CliSettings,
190}
191
192impl Deref for SolcSettings {
193 type Target = Settings;
194
195 fn deref(&self) -> &Self::Target {
196 &self.settings
197 }
198}
199
200impl DerefMut for SolcSettings {
201 fn deref_mut(&mut self) -> &mut Self::Target {
202 &mut self.settings
203 }
204}
205
206#[derive(Debug, Clone, Copy, Eq, Default, PartialEq)]
208pub struct Restriction<V> {
209 pub min: Option<V>,
210 pub max: Option<V>,
211}
212
213impl<V: Ord + Copy> Restriction<V> {
214 pub fn satisfies(&self, value: Option<V>) -> bool {
218 self.min.is_none_or(|min| value.is_some_and(|v| v >= min))
219 && self.max.is_none_or(|max| value.is_some_and(|v| v <= max))
220 }
221
222 pub fn merge(self, other: Self) -> Option<Self> {
224 let Self { mut min, mut max } = self;
225 let Self { min: other_min, max: other_max } = other;
226
227 min = min.map_or(other_min, |this_min| {
228 Some(other_min.map_or(this_min, |other_min| this_min.max(other_min)))
229 });
230 max = max.map_or(other_max, |this_max| {
231 Some(other_max.map_or(this_max, |other_max| this_max.min(other_max)))
232 });
233
234 if let (Some(min), Some(max)) = (min, max) {
235 if min > max {
236 return None;
237 }
238 }
239
240 Some(Self { min, max })
241 }
242
243 pub fn apply(&self, value: Option<V>) -> Option<V> {
244 match (value, self.min, self.max) {
245 (None, Some(min), _) => Some(min),
246 (None, None, Some(max)) => Some(max),
247 (Some(cur), Some(min), _) if cur < min => Some(min),
248 (Some(cur), _, Some(max)) if cur > max => Some(max),
249 _ => value,
250 }
251 }
252}
253
254#[derive(Debug, Clone, Copy, Default)]
256pub struct SolcRestrictions {
257 pub evm_version: Restriction<EvmVersion>,
258 pub via_ir: Option<bool>,
259 pub optimizer_runs: Restriction<usize>,
260 pub bytecode_hash: Option<BytecodeHash>,
261}
262
263impl CompilerSettingsRestrictions for SolcRestrictions {
264 fn merge(self, other: Self) -> Option<Self> {
265 if let (Some(via_ir), Some(other_via_ir)) = (self.via_ir, other.via_ir) {
266 if via_ir != other_via_ir {
267 return None;
268 }
269 }
270
271 if let (Some(bytecode_hash), Some(other_bytecode_hash)) =
272 (self.bytecode_hash, other.bytecode_hash)
273 {
274 if bytecode_hash != other_bytecode_hash {
275 return None;
276 }
277 }
278
279 Some(Self {
280 evm_version: self.evm_version.merge(other.evm_version)?,
281 via_ir: self.via_ir.or(other.via_ir),
282 optimizer_runs: self.optimizer_runs.merge(other.optimizer_runs)?,
283 bytecode_hash: self.bytecode_hash.or(other.bytecode_hash),
284 })
285 }
286}
287
288impl CompilerSettings for SolcSettings {
289 type Restrictions = SolcRestrictions;
290
291 fn update_output_selection(&mut self, mut f: impl FnMut(&mut OutputSelection)) {
292 f(&mut self.settings.output_selection);
293 }
294
295 fn can_use_cached(&self, other: &Self) -> bool {
296 let Self {
297 settings:
298 Settings {
299 stop_after,
300 remappings,
301 optimizer,
302 model_checker,
303 metadata,
304 output_selection,
305 evm_version,
306 via_ir,
307 debug,
308 libraries,
309 },
310 ..
311 } = self;
312
313 *stop_after == other.settings.stop_after
314 && *remappings == other.settings.remappings
315 && *optimizer == other.settings.optimizer
316 && *model_checker == other.settings.model_checker
317 && *metadata == other.settings.metadata
318 && *evm_version == other.settings.evm_version
319 && *via_ir == other.settings.via_ir
320 && *debug == other.settings.debug
321 && *libraries == other.settings.libraries
322 && output_selection.is_subset_of(&other.settings.output_selection)
323 }
324
325 fn with_remappings(mut self, remappings: &[Remapping]) -> Self {
326 self.settings.remappings = remappings.to_vec();
327
328 self
329 }
330
331 fn with_allow_paths(mut self, allowed_paths: &BTreeSet<PathBuf>) -> Self {
332 self.cli_settings.allow_paths.clone_from(allowed_paths);
333 self
334 }
335
336 fn with_base_path(mut self, base_path: &Path) -> Self {
337 self.cli_settings.base_path = Some(base_path.to_path_buf());
338 self
339 }
340
341 fn with_include_paths(mut self, include_paths: &BTreeSet<PathBuf>) -> Self {
342 self.cli_settings.include_paths.clone_from(include_paths);
343 self
344 }
345
346 fn satisfies_restrictions(&self, restrictions: &Self::Restrictions) -> bool {
347 let mut satisfies = true;
348
349 let SolcRestrictions { evm_version, via_ir, optimizer_runs, bytecode_hash } = restrictions;
350
351 satisfies &= evm_version.satisfies(self.evm_version);
352 satisfies &= via_ir.is_none_or(|via_ir| via_ir == self.via_ir.unwrap_or_default());
353 satisfies &= bytecode_hash.is_none_or(|bytecode_hash| {
354 self.metadata.as_ref().and_then(|m| m.bytecode_hash) == Some(bytecode_hash)
355 });
356 satisfies &= optimizer_runs.satisfies(self.optimizer.runs);
357
358 satisfies &= optimizer_runs
360 .min
361 .is_none_or(|min| min == 0 || self.optimizer.enabled.unwrap_or_default());
362
363 satisfies
364 }
365}
366
367impl SourceParser for SolParser {
368 type ParsedSource = SolData;
369
370 fn new(config: &crate::ProjectPathsConfig) -> Self {
371 Self {
372 compiler: solar::sema::Compiler::new(Self::session_with_opts(
373 solar::sema::interface::config::Opts {
374 include_paths: config.include_paths.iter().cloned().collect(),
375 base_path: Some(config.root.clone()),
376 import_remappings: config
377 .remappings
378 .iter()
379 .map(|r| solar::sema::interface::config::ImportRemapping {
380 context: r.context.clone().unwrap_or_default(),
381 prefix: r.name.clone(),
382 path: r.path.clone(),
383 })
384 .collect(),
385 ..Default::default()
386 },
387 )),
388 }
389 }
390
391 fn read(&mut self, path: &Path) -> Result<Node<Self::ParsedSource>> {
392 let mut sources = Sources::from_iter([(path.to_path_buf(), Source::read_(path)?)]);
393 let nodes = self.parse_sources(&mut sources)?;
394 debug_assert_eq!(nodes.len(), 1, "{nodes:#?}");
395 Ok(nodes.into_iter().next().unwrap().1)
396 }
397
398 fn parse_sources(
399 &mut self,
400 sources: &mut Sources,
401 ) -> Result<Vec<(PathBuf, Node<Self::ParsedSource>)>> {
402 self.compiler_mut().enter_mut(|compiler| {
403 let mut pcx = compiler.parse();
404 let files = sources
405 .par_iter()
406 .map(|(path, source)| {
407 pcx.sess
408 .source_map()
409 .new_source_file(path.clone(), source.content.as_str())
410 .map_err(|e| SolcError::Io(SolcIoError::new(e, path)))
411 })
412 .collect::<Result<Vec<_>>>()?;
413 pcx.add_files(files);
414 pcx.parse();
415
416 let parsed = sources.par_iter().map(|(path, source)| {
417 let sf = compiler.sess().source_map().get_file(path).unwrap();
418 let (_, s) = compiler.gcx().sources.get_file(&sf).unwrap();
419 let node = Node::new(
420 path.clone(),
421 source.clone(),
422 SolData::parse_from(compiler.gcx().sess, s),
423 );
424 (path.clone(), node)
425 });
426 let mut parsed = parsed.collect::<Vec<_>>();
427
428 if let Some(Err(diag)) = compiler.gcx().sess.emitted_errors() {
431 if let Some(idx) = parsed
432 .iter()
433 .position(|(_, node)| node.data.parse_result.is_ok())
434 .or_else(|| parsed.first().map(|_| 0))
435 {
436 let (_, node) = &mut parsed[idx];
437 node.data.parse_result = Err(diag.to_string());
438 }
439 }
440
441 for (path, node) in &parsed {
442 if let Err(e) = &node.data.parse_result {
443 debug!("failed parsing {}: {e}", path.display());
444 }
445 }
446
447 Ok(parsed)
448 })
449 }
450}
451
452impl ParsedSource for SolData {
453 type Language = SolcLanguage;
454
455 fn parse(content: &str, file: &std::path::Path) -> Result<Self> {
456 Ok(Self::parse(content, file))
457 }
458
459 fn version_req(&self) -> Option<&semver::VersionReq> {
460 self.version_req.as_ref()
461 }
462
463 fn contract_names(&self) -> &[String] {
464 &self.contract_names
465 }
466
467 fn language(&self) -> Self::Language {
468 if self.is_yul {
469 SolcLanguage::Yul
470 } else {
471 SolcLanguage::Solidity
472 }
473 }
474
475 fn resolve_imports<C>(
476 &self,
477 _paths: &crate::ProjectPathsConfig<C>,
478 _include_paths: &mut BTreeSet<PathBuf>,
479 ) -> Result<Vec<PathBuf>> {
480 Ok(self.imports.iter().map(|i| i.data().path().to_path_buf()).collect())
481 }
482
483 fn compilation_dependencies<'a>(
484 &self,
485 imported_nodes: impl Iterator<Item = (&'a Path, &'a Self)>,
486 ) -> impl Iterator<Item = &'a Path>
487 where
488 Self: 'a,
489 {
490 imported_nodes.filter_map(|(path, node)| (!node.libraries.is_empty()).then_some(path))
491 }
492}
493
494impl CompilationError for Error {
495 fn is_warning(&self) -> bool {
496 self.severity.is_warning()
497 }
498 fn is_error(&self) -> bool {
499 self.severity.is_error()
500 }
501
502 fn source_location(&self) -> Option<SourceLocation> {
503 self.source_location.clone()
504 }
505
506 fn severity(&self) -> Severity {
507 self.severity
508 }
509
510 fn error_code(&self) -> Option<u64> {
511 self.error_code
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use foundry_compilers_artifacts::{CompilerOutput, SolcLanguage};
518 use semver::Version;
519
520 use crate::{
521 buildinfo::RawBuildInfo,
522 compilers::{
523 solc::{SolcCompiler, SolcVersionedInput},
524 CompilerInput,
525 },
526 AggregatedCompilerOutput,
527 };
528
529 #[test]
530 fn can_parse_declaration_error() {
531 let s = r#"{
532 "errors": [
533 {
534 "component": "general",
535 "errorCode": "7576",
536 "formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"revert\"?\n --> /Users/src/utils/UpgradeProxy.sol:35:17:\n |\n35 | refert(\"Transparent ERC1967 proxies do not have upgradeable implementations\");\n | ^^^^^^\n\n",
537 "message": "Undeclared identifier. Did you mean \"revert\"?",
538 "severity": "error",
539 "sourceLocation": {
540 "end": 1623,
541 "file": "/Users/src/utils/UpgradeProxy.sol",
542 "start": 1617
543 },
544 "type": "DeclarationError"
545 }
546 ],
547 "sources": { }
548}"#;
549
550 let out: CompilerOutput = serde_json::from_str(s).unwrap();
551 assert_eq!(out.errors.len(), 1);
552
553 let out_converted = crate::compilers::CompilerOutput {
554 errors: out.errors,
555 contracts: Default::default(),
556 sources: Default::default(),
557 metadata: Default::default(),
558 };
559
560 let v = Version::new(0, 8, 12);
561 let input = SolcVersionedInput::build(
562 Default::default(),
563 Default::default(),
564 SolcLanguage::Solidity,
565 v.clone(),
566 );
567 let build_info = RawBuildInfo::new(&input, &out_converted, true).unwrap();
568 let mut aggregated = AggregatedCompilerOutput::<SolcCompiler>::default();
569 aggregated.extend(v, build_info, "default", out_converted);
570 assert!(!aggregated.is_unchanged());
571 }
572}