1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Finds crate binaries and loads their metadata
13 use back::archive::{ArchiveRO, METADATA_FILENAME};
15 use driver::session::Session;
16 use lib::llvm::{False, llvm, ObjectFile, mk_section_iter};
17 use metadata::cstore::{MetadataBlob, MetadataVec, MetadataArchive};
18 use metadata::decoder;
19 use metadata::encoder;
20 use metadata::filesearch::{FileMatches, FileDoesntMatch};
21 use syntax::codemap::Span;
22 use syntax::diagnostic::SpanHandler;
23 use syntax::parse::token::IdentInterner;
24 use syntax::crateid::CrateId;
25 use syntax::attr::AttrMetaMethods;
27 use std::c_str::ToCStr;
31 use std::os::consts::{macos, freebsd, linux, android, win32};
37 use collections::{HashMap, HashSet};
49 pub struct HashMismatch {
53 pub struct Context<'a> {
54 pub sess: &'a Session,
57 pub crate_id: &'a CrateId,
59 pub hash: Option<&'a Svh>,
61 pub intr: Rc<IdentInterner>,
62 pub rejected_via_hash: Vec<HashMismatch>
66 pub dylib: Option<Path>,
67 pub rlib: Option<Path>,
68 pub metadata: MetadataBlob,
71 pub struct ArchiveMetadata {
73 // See comments in ArchiveMetadata::new for why this is static
77 pub struct CratePaths {
79 pub dylib: Option<Path>,
80 pub rlib: Option<Path>
84 fn paths(&self) -> Vec<Path> {
85 match (&self.dylib, &self.rlib) {
86 (&None, &None) => vec!(),
87 (&Some(ref p), &None) |
88 (&None, &Some(ref p)) => vec!(p.clone()),
89 (&Some(ref p1), &Some(ref p2)) => vec!(p1.clone(), p2.clone()),
94 // FIXME(#11857) this should be a "real" realpath
95 fn realpath(p: &Path) -> Path {
99 let path = os::make_absolute(p);
100 match fs::readlink(&path) {
106 impl<'a> Context<'a> {
107 pub fn load_library_crate(&mut self, root: &Option<CratePaths>) -> Library {
108 match self.find_library_crate() {
111 self.sess.abort_if_errors();
112 let message = if self.rejected_via_hash.len() > 0 {
113 format!("found possibly newer version of crate `{}`",
116 format!("can't find crate for `{}`", self.ident)
118 let message = match root {
120 &Some(ref r) => format!("{} which `{}` depends on",
123 self.sess.span_err(self.span, message);
125 if self.rejected_via_hash.len() > 0 {
126 self.sess.span_note(self.span, "perhaps this crate needs \
128 let mismatches = self.rejected_via_hash.iter();
129 for (i, &HashMismatch{ ref path }) in mismatches.enumerate() {
130 self.sess.fileline_note(self.span,
131 format!("crate `{}` path \\#{}: {}",
132 self.ident, i+1, path.display()));
137 for (i, path) in r.paths().iter().enumerate() {
138 self.sess.fileline_note(self.span,
139 format!("crate `{}` path \\#{}: {}",
140 r.ident, i+1, path.display()));
145 self.sess.abort_if_errors();
151 fn find_library_crate(&mut self) -> Option<Library> {
152 let filesearch = self.sess.filesearch();
153 let (dyprefix, dysuffix) = self.dylibname();
155 // want: crate_name.dir_part() + prefix + crate_name.file_part + "-"
156 let dylib_prefix = format!("{}{}-", dyprefix, self.crate_id.name);
157 let rlib_prefix = format!("lib{}-", self.crate_id.name);
159 let mut candidates = HashMap::new();
161 // First, find all possible candidate rlibs and dylibs purely based on
162 // the name of the files themselves. We're trying to match against an
163 // exact crate_id and a possibly an exact hash.
165 // During this step, we can filter all found libraries based on the
166 // name and id found in the crate id (we ignore the path portion for
167 // filename matching), as well as the exact hash (if specified). If we
168 // end up having many candidates, we must look at the metadata to
169 // perform exact matches against hashes/crate ids. Note that opening up
170 // the metadata is where we do an exact match against the full contents
171 // of the crate id (path/name/id).
173 // The goal of this step is to look at as little metadata as possible.
174 filesearch.search(|path| {
175 let file = match path.filename_str() {
176 None => return FileDoesntMatch,
179 if file.starts_with(rlib_prefix) && file.ends_with(".rlib") {
180 info!("rlib candidate: {}", path.display());
181 match self.try_match(file, rlib_prefix, ".rlib") {
183 info!("rlib accepted, hash: {}", hash);
184 let slot = candidates.find_or_insert_with(hash, |_| {
185 (HashSet::new(), HashSet::new())
187 let (ref mut rlibs, _) = *slot;
188 rlibs.insert(realpath(path));
192 info!("rlib rejected");
196 } else if file.starts_with(dylib_prefix) && file.ends_with(dysuffix){
197 info!("dylib candidate: {}", path.display());
198 match self.try_match(file, dylib_prefix, dysuffix) {
200 info!("dylib accepted, hash: {}", hash);
201 let slot = candidates.find_or_insert_with(hash, |_| {
202 (HashSet::new(), HashSet::new())
204 let (_, ref mut dylibs) = *slot;
205 dylibs.insert(realpath(path));
209 info!("dylib rejected");
218 // We have now collected all known libraries into a set of candidates
219 // keyed of the filename hash listed. For each filename, we also have a
220 // list of rlibs/dylibs that apply. Here, we map each of these lists
221 // (per hash), to a Library candidate for returning.
223 // A Library candidate is created if the metadata for the set of
224 // libraries corresponds to the crate id and hash criteria that this
225 // search is being performed for.
226 let mut libraries = Vec::new();
227 for (_hash, (rlibs, dylibs)) in candidates.move_iter() {
228 let mut metadata = None;
229 let rlib = self.extract_one(rlibs, "rlib", &mut metadata);
230 let dylib = self.extract_one(dylibs, "dylib", &mut metadata);
233 libraries.push(Library {
243 // Having now translated all relevant found hashes into libraries, see
244 // what we've got and figure out if we found multiple candidates for
246 match libraries.len() {
248 1 => Some(libraries.move_iter().next().unwrap()),
250 self.sess.span_err(self.span,
251 format!("multiple matching crates for `{}`",
252 self.crate_id.name));
253 self.sess.note("candidates:");
254 for lib in libraries.iter() {
257 self.sess.note(format!("path: {}", p.display()));
263 self.sess.note(format!("path: {}", p.display()));
267 let data = lib.metadata.as_slice();
268 let crate_id = decoder::get_crate_id(data);
269 note_crateid_attr(self.sess.diagnostic(), &crate_id);
276 // Attempts to match the requested version of a library against the file
277 // specified. The prefix/suffix are specified (disambiguates between
280 // The return value is `None` if `file` doesn't look like a rust-generated
281 // library, or if a specific version was requested and it doesn't match the
282 // apparent file's version.
284 // If everything checks out, then `Some(hash)` is returned where `hash` is
285 // the listed hash in the filename itself.
286 fn try_match(&self, file: &str, prefix: &str, suffix: &str) -> Option<~str>{
287 let middle = file.slice(prefix.len(), file.len() - suffix.len());
288 debug!("matching -- {}, middle: {}", file, middle);
289 let mut parts = middle.splitn('-', 1);
290 let hash = match parts.next() { Some(h) => h, None => return None };
291 debug!("matching -- {}, hash: {} (want {})", file, hash, self.id_hash);
292 let vers = match parts.next() { Some(v) => v, None => return None };
293 debug!("matching -- {}, vers: {} (want {})", file, vers,
294 self.crate_id.version);
295 match self.crate_id.version {
296 Some(ref version) if version.as_slice() != vers => return None,
297 Some(..) => {} // check the hash
299 // hash is irrelevant, no version specified
300 None => return Some(hash.to_owned())
302 debug!("matching -- {}, vers ok", file);
303 // hashes in filenames are prefixes of the "true hash"
304 if self.id_hash == hash.as_slice() {
305 debug!("matching -- {}, hash ok", file);
306 Some(hash.to_owned())
312 // Attempts to extract *one* library from the set `m`. If the set has no
313 // elements, `None` is returned. If the set has more than one element, then
314 // the errors and notes are emitted about the set of libraries.
316 // With only one library in the set, this function will extract it, and then
317 // read the metadata from it if `*slot` is `None`. If the metadata couldn't
318 // be read, it is assumed that the file isn't a valid rust library (no
319 // errors are emitted).
320 fn extract_one(&mut self, m: HashSet<Path>, flavor: &str,
321 slot: &mut Option<MetadataBlob>) -> Option<Path> {
322 let mut ret = None::<Path>;
326 // FIXME(#10786): for an optimization, we only read one of the
327 // library's metadata sections. In theory we should
328 // read both, but reading dylib metadata is quite
332 } else if m.len() == 1 {
333 return Some(m.move_iter().next().unwrap())
337 for lib in m.move_iter() {
338 info!("{} reading metadata from: {}", flavor, lib.display());
339 let metadata = match get_metadata_section(self.os, &lib) {
341 if self.crate_matches(blob.as_slice(), &lib) {
344 info!("metadata mismatch");
349 info!("no metadata found");
354 self.sess.span_err(self.span,
355 format!("multiple {} candidates for `{}` \
356 found", flavor, self.crate_id.name));
357 self.sess.span_note(self.span,
358 format!(r"candidate \#1: {}",
359 ret.get_ref().display()));
365 self.sess.span_note(self.span,
366 format!(r"candidate \#{}: {}", error,
370 *slot = Some(metadata);
373 return if error > 0 {None} else {ret}
376 fn crate_matches(&mut self, crate_data: &[u8], libpath: &Path) -> bool {
377 match decoder::maybe_get_crate_id(crate_data) {
378 Some(ref id) if self.crate_id.matches(id) => {}
381 let hash = match decoder::maybe_get_crate_hash(crate_data) {
382 Some(hash) => hash, None => return false
388 self.rejected_via_hash.push(HashMismatch{ path: libpath.clone() });
397 // Returns the corresponding (prefix, suffix) that files need to have for
399 fn dylibname(&self) -> (&'static str, &'static str) {
401 OsWin32 => (win32::DLL_PREFIX, win32::DLL_SUFFIX),
402 OsMacos => (macos::DLL_PREFIX, macos::DLL_SUFFIX),
403 OsLinux => (linux::DLL_PREFIX, linux::DLL_SUFFIX),
404 OsAndroid => (android::DLL_PREFIX, android::DLL_SUFFIX),
405 OsFreebsd => (freebsd::DLL_PREFIX, freebsd::DLL_SUFFIX),
410 pub fn note_crateid_attr(diag: &SpanHandler, crateid: &CrateId) {
411 diag.handler().note(format!("crate_id: {}", crateid.to_str()));
414 impl ArchiveMetadata {
415 fn new(ar: ArchiveRO) -> Option<ArchiveMetadata> {
416 let data: &'static [u8] = {
417 let data = match ar.read(METADATA_FILENAME) {
420 debug!("didn't find '{}' in the archive", METADATA_FILENAME);
424 // This data is actually a pointer inside of the archive itself, but
425 // we essentially want to cache it because the lookup inside the
426 // archive is a fairly expensive operation (and it's queried for
427 // *very* frequently). For this reason, we transmute it to the
428 // static lifetime to put into the struct. Note that the buffer is
429 // never actually handed out with a static lifetime, but rather the
430 // buffer is loaned with the lifetime of this containing object.
431 // Hence, we're guaranteed that the buffer will never be used after
432 // this object is dead, so this is a safe operation to transmute and
433 // store the data as a static buffer.
434 unsafe { cast::transmute(data) }
436 Some(ArchiveMetadata {
442 pub fn as_slice<'a>(&'a self) -> &'a [u8] { self.data }
445 // Just a small wrapper to time how long reading metadata takes.
446 fn get_metadata_section(os: Os, filename: &Path) -> Result<MetadataBlob, ~str> {
447 let start = time::precise_time_ns();
448 let ret = get_metadata_section_imp(os, filename);
449 info!("reading {} => {}ms", filename.filename_display(),
450 (time::precise_time_ns() - start) / 1000000);
454 fn get_metadata_section_imp(os: Os, filename: &Path) -> Result<MetadataBlob, ~str> {
455 if !filename.exists() {
456 return Err(format!("no such file: '{}'", filename.display()));
458 if filename.filename_str().unwrap().ends_with(".rlib") {
459 // Use ArchiveRO for speed here, it's backed by LLVM and uses mmap
460 // internally to read the file. We also avoid even using a memcpy by
461 // just keeping the archive along while the metadata is in use.
462 let archive = match ArchiveRO::open(filename) {
465 debug!("llvm didn't like `{}`", filename.display());
466 return Err(format!("failed to read rlib metadata: '{}'",
467 filename.display()));
470 return match ArchiveMetadata::new(archive).map(|ar| MetadataArchive(ar)) {
471 None => return Err(format!("failed to read rlib metadata: '{}'",
472 filename.display())),
473 Some(blob) => return Ok(blob)
477 let mb = filename.with_c_str(|buf| {
478 llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf)
481 return Err(format!("error reading library: '{}'",filename.display()))
483 let of = match ObjectFile::new(mb) {
485 _ => return Err(format!("provided path not an object file: '{}'", filename.display()))
487 let si = mk_section_iter(of.llof);
488 while llvm::LLVMIsSectionIteratorAtEnd(of.llof, si.llsi) == False {
489 let mut name_buf = ptr::null();
490 let name_len = llvm::LLVMRustGetSectionName(si.llsi, &mut name_buf);
491 let name = str::raw::from_buf_len(name_buf as *u8, name_len as uint);
492 debug!("get_metadata_section: name {}", name);
493 if read_meta_section_name(os) == name {
494 let cbuf = llvm::LLVMGetSectionContents(si.llsi);
495 let csz = llvm::LLVMGetSectionSize(si.llsi) as uint;
496 let mut found = Err(format!("metadata not found: '{}'", filename.display()));
497 let cvbuf: *u8 = cast::transmute(cbuf);
498 let vlen = encoder::metadata_encoding_version.len();
499 debug!("checking {} bytes of metadata-version stamp",
501 let minsz = cmp::min(vlen, csz);
502 let version_ok = slice::raw::buf_as_slice(cvbuf, minsz,
503 |buf0| buf0 == encoder::metadata_encoding_version);
504 if !version_ok { return Err(format!("incompatible metadata version found: '{}'",
505 filename.display())); }
507 let cvbuf1 = cvbuf.offset(vlen as int);
508 debug!("inflating {} bytes of compressed metadata",
510 slice::raw::buf_as_slice(cvbuf1, csz-vlen, |bytes| {
511 match flate::inflate_bytes(bytes) {
512 Some(inflated) => found = Ok(MetadataVec(inflated)),
513 None => found = Err(format!("failed to decompress metadata for: '{}'",
521 llvm::LLVMMoveToNextSection(si.llsi);
523 return Err(format!("metadata not found: '{}'", filename.display()));
527 pub fn meta_section_name(os: Os) -> &'static str {
529 OsMacos => "__DATA,__note.rustc",
530 OsWin32 => ".note.rustc",
531 OsLinux => ".note.rustc",
532 OsAndroid => ".note.rustc",
533 OsFreebsd => ".note.rustc"
537 pub fn read_meta_section_name(os: Os) -> &'static str {
539 OsMacos => "__note.rustc",
540 OsWin32 => ".note.rustc",
541 OsLinux => ".note.rustc",
542 OsAndroid => ".note.rustc",
543 OsFreebsd => ".note.rustc"
547 // A diagnostic function for dumping crate metadata to an output stream
548 pub fn list_file_metadata(os: Os, path: &Path,
549 out: &mut io::Writer) -> io::IoResult<()> {
550 match get_metadata_section(os, path) {
551 Ok(bytes) => decoder::list_crate_metadata(bytes.as_slice(), out),
553 write!(out, "{}\n", msg)