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};
35 use collections::{HashMap, HashSet};
47 pub struct Context<'a> {
51 crate_id: &'a CrateId,
53 hash: Option<&'a Svh>,
56 rejected_via_hash: bool,
62 metadata: MetadataBlob,
65 pub struct ArchiveMetadata {
66 priv archive: ArchiveRO,
67 // See comments in ArchiveMetadata::new for why this is static
68 priv data: &'static [u8],
71 // FIXME(#11857) this should be a "real" realpath
72 fn realpath(p: &Path) -> Path {
76 let path = os::make_absolute(p);
77 match fs::readlink(&path) {
83 impl<'a> Context<'a> {
84 pub fn load_library_crate(&mut self, root_ident: Option<&str>) -> Library {
85 match self.find_library_crate() {
88 self.sess.abort_if_errors();
89 let message = if self.rejected_via_hash {
90 format!("found possibly newer version of crate `{}`",
93 format!("can't find crate for `{}`", self.ident)
95 let message = match root_ident {
97 Some(c) => format!("{} which `{}` depends on", message, c),
99 self.sess.span_err(self.span, message);
101 if self.rejected_via_hash {
102 self.sess.span_note(self.span, "perhaps this crate needs \
105 self.sess.abort_if_errors();
111 fn find_library_crate(&mut self) -> Option<Library> {
112 let filesearch = self.sess.filesearch;
113 let (dyprefix, dysuffix) = self.dylibname();
115 // want: crate_name.dir_part() + prefix + crate_name.file_part + "-"
116 let dylib_prefix = format!("{}{}-", dyprefix, self.crate_id.name);
117 let rlib_prefix = format!("lib{}-", self.crate_id.name);
119 let mut candidates = HashMap::new();
121 // First, find all possible candidate rlibs and dylibs purely based on
122 // the name of the files themselves. We're trying to match against an
123 // exact crate_id and a possibly an exact hash.
125 // During this step, we can filter all found libraries based on the
126 // name and id found in the crate id (we ignore the path portion for
127 // filename matching), as well as the exact hash (if specified). If we
128 // end up having many candidates, we must look at the metadata to
129 // perform exact matches against hashes/crate ids. Note that opening up
130 // the metadata is where we do an exact match against the full contents
131 // of the crate id (path/name/id).
133 // The goal of this step is to look at as little metadata as possible.
134 filesearch.search(|path| {
135 let file = match path.filename_str() {
136 None => return FileDoesntMatch,
139 if file.starts_with(rlib_prefix) && file.ends_with(".rlib") {
140 info!("rlib candidate: {}", path.display());
141 match self.try_match(file, rlib_prefix, ".rlib") {
143 info!("rlib accepted, hash: {}", hash);
144 let slot = candidates.find_or_insert_with(hash, |_| {
145 (HashSet::new(), HashSet::new())
147 let (ref mut rlibs, _) = *slot;
148 rlibs.insert(realpath(path));
152 info!("rlib rejected");
156 } else if file.starts_with(dylib_prefix) && file.ends_with(dysuffix){
157 info!("dylib candidate: {}", path.display());
158 match self.try_match(file, dylib_prefix, dysuffix) {
160 info!("dylib accepted, hash: {}", hash);
161 let slot = candidates.find_or_insert_with(hash, |_| {
162 (HashSet::new(), HashSet::new())
164 let (_, ref mut dylibs) = *slot;
165 dylibs.insert(realpath(path));
169 info!("dylib rejected");
178 // We have now collected all known libraries into a set of candidates
179 // keyed of the filename hash listed. For each filename, we also have a
180 // list of rlibs/dylibs that apply. Here, we map each of these lists
181 // (per hash), to a Library candidate for returning.
183 // A Library candidate is created if the metadata for the set of
184 // libraries corresponds to the crate id and hash criteria that this
185 // serach is being performed for.
186 let mut libraries = ~[];
187 for (_hash, (rlibs, dylibs)) in candidates.move_iter() {
188 let mut metadata = None;
189 let rlib = self.extract_one(rlibs, "rlib", &mut metadata);
190 let dylib = self.extract_one(dylibs, "dylib", &mut metadata);
193 libraries.push(Library {
203 // Having now translated all relevant found hashes into libraries, see
204 // what we've got and figure out if we found multiple candidates for
206 match libraries.len() {
208 1 => Some(libraries[0]),
210 self.sess.span_err(self.span,
211 format!("multiple matching crates for `{}`",
212 self.crate_id.name));
213 self.sess.note("candidates:");
214 for lib in libraries.iter() {
217 self.sess.note(format!("path: {}", p.display()));
223 self.sess.note(format!("path: {}", p.display()));
227 let data = lib.metadata.as_slice();
228 let crate_id = decoder::get_crate_id(data);
229 note_crateid_attr(self.sess.diagnostic(), &crate_id);
236 // Attempts to match the requested version of a library against the file
237 // specified. The prefix/suffix are specified (disambiguates between
240 // The return value is `None` if `file` doesn't look like a rust-generated
241 // library, or if a specific version was requested and it doens't match the
242 // apparent file's version.
244 // If everything checks out, then `Some(hash)` is returned where `hash` is
245 // the listed hash in the filename itself.
246 fn try_match(&self, file: &str, prefix: &str, suffix: &str) -> Option<~str>{
247 let middle = file.slice(prefix.len(), file.len() - suffix.len());
248 debug!("matching -- {}, middle: {}", file, middle);
249 let mut parts = middle.splitn('-', 1);
250 let hash = match parts.next() { Some(h) => h, None => return None };
251 debug!("matching -- {}, hash: {} (want {})", file, hash, self.id_hash);
252 let vers = match parts.next() { Some(v) => v, None => return None };
253 debug!("matching -- {}, vers: {} (want {})", file, vers,
254 self.crate_id.version);
255 match self.crate_id.version {
256 Some(ref version) if version.as_slice() != vers => return None,
257 Some(..) => {} // check the hash
259 // hash is irrelevant, no version specified
260 None => return Some(hash.to_owned())
262 debug!("matching -- {}, vers ok", file);
263 // hashes in filenames are prefixes of the "true hash"
264 if self.id_hash == hash.as_slice() {
265 debug!("matching -- {}, hash ok", file);
266 Some(hash.to_owned())
272 // Attempts to extract *one* library from the set `m`. If the set has no
273 // elements, `None` is returned. If the set has more than one element, then
274 // the errors and notes are emitted about the set of libraries.
276 // With only one library in the set, this function will extract it, and then
277 // read the metadata from it if `*slot` is `None`. If the metadata couldn't
278 // be read, it is assumed that the file isn't a valid rust library (no
279 // errors are emitted).
281 // FIXME(#10786): for an optimization, we only read one of the library's
282 // metadata sections. In theory we should read both, but
283 // reading dylib metadata is quite slow.
284 fn extract_one(&mut self, m: HashSet<Path>, flavor: &str,
285 slot: &mut Option<MetadataBlob>) -> Option<Path> {
286 if m.len() == 0 { return None }
288 self.sess.span_err(self.span,
289 format!("multiple {} candidates for `{}` \
290 found", flavor, self.crate_id.name));
291 for (i, path) in m.iter().enumerate() {
292 self.sess.span_note(self.span,
293 format!(r"candidate \#{}: {}", i + 1,
299 let lib = m.move_iter().next().unwrap();
301 info!("{} reading meatadata from: {}", flavor, lib.display());
302 match get_metadata_section(self.os, &lib) {
304 if self.crate_matches(blob.as_slice()) {
307 info!("metadata mismatch");
312 info!("no metadata found");
320 fn crate_matches(&mut self, crate_data: &[u8]) -> bool {
321 match decoder::maybe_get_crate_id(crate_data) {
322 Some(ref id) if self.crate_id.matches(id) => {}
325 let hash = match decoder::maybe_get_crate_hash(crate_data) {
326 Some(hash) => hash, None => return false
332 self.rejected_via_hash = true;
341 // Returns the corresponding (prefix, suffix) that files need to have for
343 fn dylibname(&self) -> (&'static str, &'static str) {
345 OsWin32 => (win32::DLL_PREFIX, win32::DLL_SUFFIX),
346 OsMacos => (macos::DLL_PREFIX, macos::DLL_SUFFIX),
347 OsLinux => (linux::DLL_PREFIX, linux::DLL_SUFFIX),
348 OsAndroid => (android::DLL_PREFIX, android::DLL_SUFFIX),
349 OsFreebsd => (freebsd::DLL_PREFIX, freebsd::DLL_SUFFIX),
354 pub fn note_crateid_attr(diag: @SpanHandler, crateid: &CrateId) {
355 diag.handler().note(format!("crate_id: {}", crateid.to_str()));
358 impl ArchiveMetadata {
359 fn new(ar: ArchiveRO) -> Option<ArchiveMetadata> {
360 let data: &'static [u8] = {
361 let data = match ar.read(METADATA_FILENAME) {
364 debug!("didn't find '{}' in the archive", METADATA_FILENAME);
368 // This data is actually a pointer inside of the archive itself, but
369 // we essentially want to cache it because the lookup inside the
370 // archive is a fairly expensive operation (and it's queried for
371 // *very* frequently). For this reason, we transmute it to the
372 // static lifetime to put into the struct. Note that the buffer is
373 // never actually handed out with a static lifetime, but rather the
374 // buffer is loaned with the lifetime of this containing object.
375 // Hence, we're guaranteed that the buffer will never be used after
376 // this object is dead, so this is a safe operation to transmute and
377 // store the data as a static buffer.
378 unsafe { cast::transmute(data) }
380 Some(ArchiveMetadata {
386 pub fn as_slice<'a>(&'a self) -> &'a [u8] { self.data }
389 // Just a small wrapper to time how long reading metadata takes.
390 fn get_metadata_section(os: Os, filename: &Path) -> Option<MetadataBlob> {
391 let start = time::precise_time_ns();
392 let ret = get_metadata_section_imp(os, filename);
393 info!("reading {} => {}ms", filename.filename_display(),
394 (time::precise_time_ns() - start) / 1000000);
398 fn get_metadata_section_imp(os: Os, filename: &Path) -> Option<MetadataBlob> {
399 if filename.filename_str().unwrap().ends_with(".rlib") {
400 // Use ArchiveRO for speed here, it's backed by LLVM and uses mmap
401 // internally to read the file. We also avoid even using a memcpy by
402 // just keeping the archive along while the metadata is in use.
403 let archive = match ArchiveRO::open(filename) {
406 debug!("llvm didn't like `{}`", filename.display());
410 return ArchiveMetadata::new(archive).map(|ar| MetadataArchive(ar));
413 let mb = filename.with_c_str(|buf| {
414 llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf)
416 if mb as int == 0 { return None }
417 let of = match ObjectFile::new(mb) {
421 let si = mk_section_iter(of.llof);
422 while llvm::LLVMIsSectionIteratorAtEnd(of.llof, si.llsi) == False {
423 let name_buf = llvm::LLVMGetSectionName(si.llsi);
424 let name = str::raw::from_c_str(name_buf);
425 debug!("get_metadata_section: name {}", name);
426 if read_meta_section_name(os) == name {
427 let cbuf = llvm::LLVMGetSectionContents(si.llsi);
428 let csz = llvm::LLVMGetSectionSize(si.llsi) as uint;
429 let mut found = None;
430 let cvbuf: *u8 = cast::transmute(cbuf);
431 let vlen = encoder::metadata_encoding_version.len();
432 debug!("checking {} bytes of metadata-version stamp",
434 let minsz = cmp::min(vlen, csz);
435 let version_ok = vec::raw::buf_as_slice(cvbuf, minsz,
436 |buf0| buf0 == encoder::metadata_encoding_version);
437 if !version_ok { return None; }
439 let cvbuf1 = cvbuf.offset(vlen as int);
440 debug!("inflating {} bytes of compressed metadata",
442 vec::raw::buf_as_slice(cvbuf1, csz-vlen, |bytes| {
443 let inflated = flate::inflate_bytes(bytes);
444 found = Some(MetadataVec(inflated));
450 llvm::LLVMMoveToNextSection(si.llsi);
456 pub fn meta_section_name(os: Os) -> &'static str {
458 OsMacos => "__DATA,__note.rustc",
459 OsWin32 => ".note.rustc",
460 OsLinux => ".note.rustc",
461 OsAndroid => ".note.rustc",
462 OsFreebsd => ".note.rustc"
466 pub fn read_meta_section_name(os: Os) -> &'static str {
468 OsMacos => "__note.rustc",
469 OsWin32 => ".note.rustc",
470 OsLinux => ".note.rustc",
471 OsAndroid => ".note.rustc",
472 OsFreebsd => ".note.rustc"
476 // A diagnostic function for dumping crate metadata to an output stream
477 pub fn list_file_metadata(os: Os, path: &Path,
478 out: &mut io::Writer) -> io::IoResult<()> {
479 match get_metadata_section(os, path) {
480 Some(bytes) => decoder::list_crate_metadata(bytes.as_slice(), out),
482 write!(out, "could not find metadata in {}.\n", path.display())