]> git.lizzy.rs Git - rust.git/blob - src/librustc/metadata/loader.rs
617a8654eedab2b30df8c898d8bafdc02c781c1c
[rust.git] / src / librustc / metadata / loader.rs
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.
4 //
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.
10
11 //! Finds crate binaries and loads their metadata
12
13 use back::archive::{ArchiveRO, METADATA_FILENAME};
14 use back::svh::Svh;
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;
26
27 use std::c_str::ToCStr;
28 use std::cast;
29 use std::cmp;
30 use std::io;
31 use std::os::consts::{macos, freebsd, linux, android, win32};
32 use std::str;
33 use std::vec;
34
35 use collections::{HashMap, HashSet};
36 use flate;
37 use time;
38
39 pub enum Os {
40     OsMacos,
41     OsWin32,
42     OsLinux,
43     OsAndroid,
44     OsFreebsd
45 }
46
47 pub struct Context<'a> {
48     sess: Session,
49     span: Span,
50     ident: &'a str,
51     crate_id: &'a CrateId,
52     id_hash: &'a str,
53     hash: Option<&'a Svh>,
54     os: Os,
55     intr: @IdentInterner,
56     rejected_via_hash: bool,
57 }
58
59 pub struct Library {
60     dylib: Option<Path>,
61     rlib: Option<Path>,
62     metadata: MetadataBlob,
63 }
64
65 pub struct ArchiveMetadata {
66     priv archive: ArchiveRO,
67     // See comments in ArchiveMetadata::new for why this is static
68     priv data: &'static [u8],
69 }
70
71 // FIXME(#11857) this should be a "real" realpath
72 fn realpath(p: &Path) -> Path {
73     use std::os;
74     use std::io::fs;
75
76     let path = os::make_absolute(p);
77     match fs::readlink(&path) {
78         Ok(p) => p,
79         Err(..) => path
80     }
81 }
82
83 impl<'a> Context<'a> {
84     pub fn load_library_crate(&mut self, root_ident: Option<&str>) -> Library {
85         match self.find_library_crate() {
86             Some(t) => t,
87             None => {
88                 self.sess.abort_if_errors();
89                 let message = if self.rejected_via_hash {
90                     format!("found possibly newer version of crate `{}`",
91                             self.ident)
92                 } else {
93                     format!("can't find crate for `{}`", self.ident)
94                 };
95                 let message = match root_ident {
96                     None => message,
97                     Some(c) => format!("{} which `{}` depends on", message, c),
98                 };
99                 self.sess.span_err(self.span, message);
100
101                 if self.rejected_via_hash {
102                     self.sess.span_note(self.span, "perhaps this crate needs \
103                                                     to be recompiled?");
104                 }
105                 self.sess.abort_if_errors();
106                 unreachable!()
107             }
108         }
109     }
110
111     fn find_library_crate(&mut self) -> Option<Library> {
112         let filesearch = self.sess.filesearch;
113         let (dyprefix, dysuffix) = self.dylibname();
114
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);
118
119         let mut candidates = HashMap::new();
120
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.
124         //
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).
132         //
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,
137                 Some(file) => file,
138             };
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") {
142                     Some(hash) => {
143                         info!("rlib accepted, hash: {}", hash);
144                         let slot = candidates.find_or_insert_with(hash, |_| {
145                             (HashSet::new(), HashSet::new())
146                         });
147                         let (ref mut rlibs, _) = *slot;
148                         rlibs.insert(realpath(path));
149                         FileMatches
150                     }
151                     None => {
152                         info!("rlib rejected");
153                         FileDoesntMatch
154                     }
155                 }
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) {
159                     Some(hash) => {
160                         info!("dylib accepted, hash: {}", hash);
161                         let slot = candidates.find_or_insert_with(hash, |_| {
162                             (HashSet::new(), HashSet::new())
163                         });
164                         let (_, ref mut dylibs) = *slot;
165                         dylibs.insert(realpath(path));
166                         FileMatches
167                     }
168                     None => {
169                         info!("dylib rejected");
170                         FileDoesntMatch
171                     }
172                 }
173             } else {
174                 FileDoesntMatch
175             }
176         });
177
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.
182         //
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);
191             match metadata {
192                 Some(metadata) => {
193                     libraries.push(Library {
194                         dylib: dylib,
195                         rlib: rlib,
196                         metadata: metadata,
197                     })
198                 }
199                 None => {}
200             }
201         }
202
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
205         // libraries or not.
206         match libraries.len() {
207             0 => None,
208             1 => Some(libraries[0]),
209             _ => {
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() {
215                     match lib.dylib {
216                         Some(ref p) => {
217                             self.sess.note(format!("path: {}", p.display()));
218                         }
219                         None => {}
220                     }
221                     match lib.rlib {
222                         Some(ref p) => {
223                             self.sess.note(format!("path: {}", p.display()));
224                         }
225                         None => {}
226                     }
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);
230                 }
231                 None
232             }
233         }
234     }
235
236     // Attempts to match the requested version of a library against the file
237     // specified. The prefix/suffix are specified (disambiguates between
238     // rlib/dylib).
239     //
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.
243     //
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
258
259             // hash is irrelevant, no version specified
260             None => return Some(hash.to_owned())
261         }
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())
267         } else {
268             None
269         }
270     }
271
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.
275     //
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).
280     //
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 }
287         if m.len() > 1 {
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,
294                                             path.display()));
295             }
296             return None
297         }
298
299         let lib = m.move_iter().next().unwrap();
300         if slot.is_none() {
301             info!("{} reading meatadata from: {}", flavor, lib.display());
302             match get_metadata_section(self.os, &lib) {
303                 Some(blob) => {
304                     if self.crate_matches(blob.as_slice()) {
305                         *slot = Some(blob);
306                     } else {
307                         info!("metadata mismatch");
308                         return None;
309                     }
310                 }
311                 None => {
312                     info!("no metadata found");
313                     return None
314                 }
315             }
316         }
317         return Some(lib);
318     }
319
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) => {}
323             _ => return false
324         }
325         let hash = match decoder::maybe_get_crate_hash(crate_data) {
326             Some(hash) => hash, None => return false
327         };
328         match self.hash {
329             None => true,
330             Some(myhash) => {
331                 if *myhash != hash {
332                     self.rejected_via_hash = true;
333                     false
334                 } else {
335                     true
336                 }
337             }
338         }
339     }
340
341     // Returns the corresponding (prefix, suffix) that files need to have for
342     // dynamic libraries
343     fn dylibname(&self) -> (&'static str, &'static str) {
344         match self.os {
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),
350         }
351     }
352 }
353
354 pub fn note_crateid_attr(diag: @SpanHandler, crateid: &CrateId) {
355     diag.handler().note(format!("crate_id: {}", crateid.to_str()));
356 }
357
358 impl ArchiveMetadata {
359     fn new(ar: ArchiveRO) -> Option<ArchiveMetadata> {
360         let data: &'static [u8] = {
361             let data = match ar.read(METADATA_FILENAME) {
362                 Some(data) => data,
363                 None => {
364                     debug!("didn't find '{}' in the archive", METADATA_FILENAME);
365                     return None;
366                 }
367             };
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) }
379         };
380         Some(ArchiveMetadata {
381             archive: ar,
382             data: data,
383         })
384     }
385
386     pub fn as_slice<'a>(&'a self) -> &'a [u8] { self.data }
387 }
388
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);
395     return ret;
396 }
397
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) {
404             Some(ar) => ar,
405             None => {
406                 debug!("llvm didn't like `{}`", filename.display());
407                 return None;
408             }
409         };
410         return ArchiveMetadata::new(archive).map(|ar| MetadataArchive(ar));
411     }
412     unsafe {
413         let mb = filename.with_c_str(|buf| {
414             llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf)
415         });
416         if mb as int == 0 { return None }
417         let of = match ObjectFile::new(mb) {
418             Some(of) => of,
419             _ => return None
420         };
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",
433                        vlen);
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; }
438
439                 let cvbuf1 = cvbuf.offset(vlen as int);
440                 debug!("inflating {} bytes of compressed metadata",
441                        csz - vlen);
442                 vec::raw::buf_as_slice(cvbuf1, csz-vlen, |bytes| {
443                     let inflated = flate::inflate_bytes(bytes);
444                     found = Some(MetadataVec(inflated));
445                 });
446                 if found.is_some() {
447                     return found;
448                 }
449             }
450             llvm::LLVMMoveToNextSection(si.llsi);
451         }
452         return None;
453     }
454 }
455
456 pub fn meta_section_name(os: Os) -> &'static str {
457     match os {
458         OsMacos => "__DATA,__note.rustc",
459         OsWin32 => ".note.rustc",
460         OsLinux => ".note.rustc",
461         OsAndroid => ".note.rustc",
462         OsFreebsd => ".note.rustc"
463     }
464 }
465
466 pub fn read_meta_section_name(os: Os) -> &'static str {
467     match os {
468         OsMacos => "__note.rustc",
469         OsWin32 => ".note.rustc",
470         OsLinux => ".note.rustc",
471         OsAndroid => ".note.rustc",
472         OsFreebsd => ".note.rustc"
473     }
474 }
475
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),
481         None => {
482             write!(out, "could not find metadata in {}.\n", path.display())
483         }
484     }
485 }