]> git.lizzy.rs Git - rust.git/blob - src/librustc/metadata/loader.rs
auto merge of #14866 : bjz/rust/bitwise, r=alexcrichton
[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::{FileSearch, FileMatches, FileDoesntMatch};
21 use syntax::codemap::Span;
22 use syntax::diagnostic::SpanHandler;
23 use syntax::crateid::CrateId;
24 use syntax::attr::AttrMetaMethods;
25 use util::fs;
26
27 use std::c_str::ToCStr;
28 use std::cmp;
29 use std::io;
30 use std::mem;
31 use std::ptr;
32 use std::slice;
33 use std::str;
34
35 use std::collections::{HashMap, HashSet};
36 use flate;
37 use time;
38
39 pub static MACOS_DLL_PREFIX: &'static str = "lib";
40 pub static MACOS_DLL_SUFFIX: &'static str = ".dylib";
41
42 pub static WIN32_DLL_PREFIX: &'static str = "";
43 pub static WIN32_DLL_SUFFIX: &'static str = ".dll";
44
45 pub static LINUX_DLL_PREFIX: &'static str = "lib";
46 pub static LINUX_DLL_SUFFIX: &'static str = ".so";
47
48 pub static FREEBSD_DLL_PREFIX: &'static str = "lib";
49 pub static FREEBSD_DLL_SUFFIX: &'static str = ".so";
50
51 pub static ANDROID_DLL_PREFIX: &'static str = "lib";
52 pub static ANDROID_DLL_SUFFIX: &'static str = ".so";
53
54 pub enum Os {
55     OsMacos,
56     OsWin32,
57     OsLinux,
58     OsAndroid,
59     OsFreebsd
60 }
61
62 pub struct CrateMismatch {
63     path: Path,
64     got: String,
65 }
66
67 pub struct Context<'a> {
68     pub sess: &'a Session,
69     pub span: Span,
70     pub ident: &'a str,
71     pub crate_id: &'a CrateId,
72     pub id_hash: &'a str,
73     pub hash: Option<&'a Svh>,
74     pub triple: &'a str,
75     pub os: Os,
76     pub filesearch: FileSearch<'a>,
77     pub root: &'a Option<CratePaths>,
78     pub rejected_via_hash: Vec<CrateMismatch>,
79     pub rejected_via_triple: Vec<CrateMismatch>,
80 }
81
82 pub struct Library {
83     pub dylib: Option<Path>,
84     pub rlib: Option<Path>,
85     pub metadata: MetadataBlob,
86 }
87
88 pub struct ArchiveMetadata {
89     _archive: ArchiveRO,
90     // See comments in ArchiveMetadata::new for why this is static
91     data: &'static [u8],
92 }
93
94 pub struct CratePaths {
95     pub ident: String,
96     pub dylib: Option<Path>,
97     pub rlib: Option<Path>
98 }
99
100 impl CratePaths {
101     fn paths(&self) -> Vec<Path> {
102         match (&self.dylib, &self.rlib) {
103             (&None,    &None)              => vec!(),
104             (&Some(ref p), &None) |
105             (&None, &Some(ref p))          => vec!(p.clone()),
106             (&Some(ref p1), &Some(ref p2)) => vec!(p1.clone(), p2.clone()),
107         }
108     }
109 }
110
111 impl<'a> Context<'a> {
112     pub fn maybe_load_library_crate(&mut self) -> Option<Library> {
113         self.find_library_crate()
114     }
115
116     pub fn load_library_crate(&mut self) -> Library {
117         match self.find_library_crate() {
118             Some(t) => t,
119             None => {
120                 self.report_load_errs();
121                 unreachable!()
122             }
123         }
124     }
125
126     pub fn report_load_errs(&mut self) {
127         let message = if self.rejected_via_hash.len() > 0 {
128             format!("found possibly newer version of crate `{}`",
129                     self.ident)
130         } else if self.rejected_via_triple.len() > 0 {
131             format!("found incorrect triple for crate `{}`", self.ident)
132         } else {
133             format!("can't find crate for `{}`", self.ident)
134         };
135         let message = match self.root {
136             &None => message,
137             &Some(ref r) => format!("{} which `{}` depends on",
138                                     message, r.ident)
139         };
140         self.sess.span_err(self.span, message.as_slice());
141
142         let mismatches = self.rejected_via_triple.iter();
143         if self.rejected_via_triple.len() > 0 {
144             self.sess.span_note(self.span,
145                                 format!("expected triple of {}",
146                                         self.triple).as_slice());
147             for (i, &CrateMismatch{ ref path, ref got }) in mismatches.enumerate() {
148                 self.sess.fileline_note(self.span,
149                     format!("crate `{}` path {}{}, triple {}: {}",
150                             self.ident, "#", i+1, got, path.display()).as_slice());
151             }
152         }
153         if self.rejected_via_hash.len() > 0 {
154             self.sess.span_note(self.span, "perhaps this crate needs \
155                                             to be recompiled?");
156             let mismatches = self.rejected_via_hash.iter();
157             for (i, &CrateMismatch{ ref path, .. }) in mismatches.enumerate() {
158                 self.sess.fileline_note(self.span,
159                     format!("crate `{}` path {}{}: {}",
160                             self.ident, "#", i+1, path.display()).as_slice());
161             }
162             match self.root {
163                 &None => {}
164                 #[cfg(stage0)]
165                 &Some(ref r) => {
166                     for (i, path) in r.paths().iter().enumerate() {
167                         self.sess.fileline_note(self.span,
168                             format!("crate `{}` path \\#{}: {}",
169                                     r.ident, i+1, path.display()).as_slice());
170                     }
171                 }
172                 #[cfg(not(stage0))]
173                 &Some(ref r) => {
174                     for (i, path) in r.paths().iter().enumerate() {
175                         self.sess.fileline_note(self.span,
176                             format!("crate `{}` path #{}: {}",
177                                     r.ident, i+1, path.display()).as_slice());
178                     }
179                 }
180             }
181         }
182         self.sess.abort_if_errors();
183     }
184
185     fn find_library_crate(&mut self) -> Option<Library> {
186         let (dyprefix, dysuffix) = self.dylibname();
187
188         // want: crate_name.dir_part() + prefix + crate_name.file_part + "-"
189         let dylib_prefix = format!("{}{}-", dyprefix, self.crate_id.name);
190         let rlib_prefix = format!("lib{}-", self.crate_id.name);
191
192         let mut candidates = HashMap::new();
193
194         // First, find all possible candidate rlibs and dylibs purely based on
195         // the name of the files themselves. We're trying to match against an
196         // exact crate_id and a possibly an exact hash.
197         //
198         // During this step, we can filter all found libraries based on the
199         // name and id found in the crate id (we ignore the path portion for
200         // filename matching), as well as the exact hash (if specified). If we
201         // end up having many candidates, we must look at the metadata to
202         // perform exact matches against hashes/crate ids. Note that opening up
203         // the metadata is where we do an exact match against the full contents
204         // of the crate id (path/name/id).
205         //
206         // The goal of this step is to look at as little metadata as possible.
207         self.filesearch.search(|path| {
208             let file = match path.filename_str() {
209                 None => return FileDoesntMatch,
210                 Some(file) => file,
211             };
212             if file.starts_with(rlib_prefix.as_slice()) &&
213                     file.ends_with(".rlib") {
214                 info!("rlib candidate: {}", path.display());
215                 match self.try_match(file, rlib_prefix.as_slice(), ".rlib") {
216                     Some(hash) => {
217                         info!("rlib accepted, hash: {}", hash);
218                         let slot = candidates.find_or_insert_with(hash, |_| {
219                             (HashSet::new(), HashSet::new())
220                         });
221                         let (ref mut rlibs, _) = *slot;
222                         rlibs.insert(fs::realpath(path).unwrap());
223                         FileMatches
224                     }
225                     None => {
226                         info!("rlib rejected");
227                         FileDoesntMatch
228                     }
229                 }
230             } else if file.starts_with(dylib_prefix.as_slice()) &&
231                     file.ends_with(dysuffix){
232                 info!("dylib candidate: {}", path.display());
233                 match self.try_match(file,
234                                      dylib_prefix.as_slice(),
235                                      dysuffix) {
236                     Some(hash) => {
237                         info!("dylib accepted, hash: {}", hash);
238                         let slot = candidates.find_or_insert_with(hash, |_| {
239                             (HashSet::new(), HashSet::new())
240                         });
241                         let (_, ref mut dylibs) = *slot;
242                         dylibs.insert(fs::realpath(path).unwrap());
243                         FileMatches
244                     }
245                     None => {
246                         info!("dylib rejected");
247                         FileDoesntMatch
248                     }
249                 }
250             } else {
251                 FileDoesntMatch
252             }
253         });
254
255         // We have now collected all known libraries into a set of candidates
256         // keyed of the filename hash listed. For each filename, we also have a
257         // list of rlibs/dylibs that apply. Here, we map each of these lists
258         // (per hash), to a Library candidate for returning.
259         //
260         // A Library candidate is created if the metadata for the set of
261         // libraries corresponds to the crate id and hash criteria that this
262         // search is being performed for.
263         let mut libraries = Vec::new();
264         for (_hash, (rlibs, dylibs)) in candidates.move_iter() {
265             let mut metadata = None;
266             let rlib = self.extract_one(rlibs, "rlib", &mut metadata);
267             let dylib = self.extract_one(dylibs, "dylib", &mut metadata);
268             match metadata {
269                 Some(metadata) => {
270                     libraries.push(Library {
271                         dylib: dylib,
272                         rlib: rlib,
273                         metadata: metadata,
274                     })
275                 }
276                 None => {}
277             }
278         }
279
280         // Having now translated all relevant found hashes into libraries, see
281         // what we've got and figure out if we found multiple candidates for
282         // libraries or not.
283         match libraries.len() {
284             0 => None,
285             1 => Some(libraries.move_iter().next().unwrap()),
286             _ => {
287                 self.sess.span_err(self.span,
288                     format!("multiple matching crates for `{}`",
289                             self.crate_id.name).as_slice());
290                 self.sess.note("candidates:");
291                 for lib in libraries.iter() {
292                     match lib.dylib {
293                         Some(ref p) => {
294                             self.sess.note(format!("path: {}",
295                                                    p.display()).as_slice());
296                         }
297                         None => {}
298                     }
299                     match lib.rlib {
300                         Some(ref p) => {
301                             self.sess.note(format!("path: {}",
302                                                    p.display()).as_slice());
303                         }
304                         None => {}
305                     }
306                     let data = lib.metadata.as_slice();
307                     let crate_id = decoder::get_crate_id(data);
308                     note_crateid_attr(self.sess.diagnostic(), &crate_id);
309                 }
310                 None
311             }
312         }
313     }
314
315     // Attempts to match the requested version of a library against the file
316     // specified. The prefix/suffix are specified (disambiguates between
317     // rlib/dylib).
318     //
319     // The return value is `None` if `file` doesn't look like a rust-generated
320     // library, or if a specific version was requested and it doesn't match the
321     // apparent file's version.
322     //
323     // If everything checks out, then `Some(hash)` is returned where `hash` is
324     // the listed hash in the filename itself.
325     fn try_match(&self, file: &str, prefix: &str, suffix: &str) -> Option<String>{
326         let middle = file.slice(prefix.len(), file.len() - suffix.len());
327         debug!("matching -- {}, middle: {}", file, middle);
328         let mut parts = middle.splitn('-', 1);
329         let hash = match parts.next() { Some(h) => h, None => return None };
330         debug!("matching -- {}, hash: {} (want {})", file, hash, self.id_hash);
331         let vers = match parts.next() { Some(v) => v, None => return None };
332         debug!("matching -- {}, vers: {} (want {})", file, vers,
333                self.crate_id.version);
334         match self.crate_id.version {
335             Some(ref version) if version.as_slice() != vers => return None,
336             Some(..) => {} // check the hash
337
338             // hash is irrelevant, no version specified
339             None => return Some(hash.to_string())
340         }
341         debug!("matching -- {}, vers ok", file);
342         // hashes in filenames are prefixes of the "true hash"
343         if self.id_hash == hash.as_slice() {
344             debug!("matching -- {}, hash ok", file);
345             Some(hash.to_string())
346         } else {
347             None
348         }
349     }
350
351     // Attempts to extract *one* library from the set `m`. If the set has no
352     // elements, `None` is returned. If the set has more than one element, then
353     // the errors and notes are emitted about the set of libraries.
354     //
355     // With only one library in the set, this function will extract it, and then
356     // read the metadata from it if `*slot` is `None`. If the metadata couldn't
357     // be read, it is assumed that the file isn't a valid rust library (no
358     // errors are emitted).
359     fn extract_one(&mut self, m: HashSet<Path>, flavor: &str,
360                    slot: &mut Option<MetadataBlob>) -> Option<Path> {
361         let mut ret = None::<Path>;
362         let mut error = 0;
363
364         if slot.is_some() {
365             // FIXME(#10786): for an optimization, we only read one of the
366             //                library's metadata sections. In theory we should
367             //                read both, but reading dylib metadata is quite
368             //                slow.
369             if m.len() == 0 {
370                 return None
371             } else if m.len() == 1 {
372                 return Some(m.move_iter().next().unwrap())
373             }
374         }
375
376         for lib in m.move_iter() {
377             info!("{} reading metadata from: {}", flavor, lib.display());
378             let metadata = match get_metadata_section(self.os, &lib) {
379                 Ok(blob) => {
380                     if self.crate_matches(blob.as_slice(), &lib) {
381                         blob
382                     } else {
383                         info!("metadata mismatch");
384                         continue
385                     }
386                 }
387                 Err(_) => {
388                     info!("no metadata found");
389                     continue
390                 }
391             };
392             if ret.is_some() {
393                 self.sess.span_err(self.span,
394                                    format!("multiple {} candidates for `{}` \
395                                             found",
396                                            flavor,
397                                            self.crate_id.name).as_slice());
398                 self.sess.span_note(self.span,
399                                     format!(r"candidate \#1: {}",
400                                             ret.get_ref()
401                                                .display()).as_slice());
402                 error = 1;
403                 ret = None;
404             }
405             if error > 0 {
406                 error += 1;
407                 self.sess.span_note(self.span,
408                                     format!(r"candidate \#{}: {}", error,
409                                             lib.display()).as_slice());
410                 continue
411             }
412             *slot = Some(metadata);
413             ret = Some(lib);
414         }
415         return if error > 0 {None} else {ret}
416     }
417
418     fn crate_matches(&mut self, crate_data: &[u8], libpath: &Path) -> bool {
419         match decoder::maybe_get_crate_id(crate_data) {
420             Some(ref id) if self.crate_id.matches(id) => {}
421             _ => { info!("Rejecting via crate_id"); return false }
422         }
423         let hash = match decoder::maybe_get_crate_hash(crate_data) {
424             Some(hash) => hash, None => {
425                 info!("Rejecting via lack of crate hash");
426                 return false;
427             }
428         };
429
430         let triple = decoder::get_crate_triple(crate_data);
431         if triple.as_slice() != self.triple {
432             info!("Rejecting via crate triple: expected {} got {}", self.triple, triple);
433             self.rejected_via_triple.push(CrateMismatch {
434                 path: libpath.clone(),
435                 got: triple.to_string()
436             });
437             return false;
438         }
439
440         match self.hash {
441             None => true,
442             Some(myhash) => {
443                 if *myhash != hash {
444                     info!("Rejecting via hash: expected {} got {}", *myhash, hash);
445                     self.rejected_via_hash.push(CrateMismatch {
446                         path: libpath.clone(),
447                         got: myhash.as_str().to_string()
448                     });
449                     false
450                 } else {
451                     true
452                 }
453             }
454         }
455     }
456
457
458     // Returns the corresponding (prefix, suffix) that files need to have for
459     // dynamic libraries
460     fn dylibname(&self) -> (&'static str, &'static str) {
461         match self.os {
462             OsWin32 => (WIN32_DLL_PREFIX, WIN32_DLL_SUFFIX),
463             OsMacos => (MACOS_DLL_PREFIX, MACOS_DLL_SUFFIX),
464             OsLinux => (LINUX_DLL_PREFIX, LINUX_DLL_SUFFIX),
465             OsAndroid => (ANDROID_DLL_PREFIX, ANDROID_DLL_SUFFIX),
466             OsFreebsd => (FREEBSD_DLL_PREFIX, FREEBSD_DLL_SUFFIX),
467         }
468     }
469
470 }
471
472 pub fn note_crateid_attr(diag: &SpanHandler, crateid: &CrateId) {
473     diag.handler().note(format!("crate_id: {}", crateid.to_str()).as_slice());
474 }
475
476 impl ArchiveMetadata {
477     fn new(ar: ArchiveRO) -> Option<ArchiveMetadata> {
478         let data: &'static [u8] = {
479             let data = match ar.read(METADATA_FILENAME) {
480                 Some(data) => data,
481                 None => {
482                     debug!("didn't find '{}' in the archive", METADATA_FILENAME);
483                     return None;
484                 }
485             };
486             // This data is actually a pointer inside of the archive itself, but
487             // we essentially want to cache it because the lookup inside the
488             // archive is a fairly expensive operation (and it's queried for
489             // *very* frequently). For this reason, we transmute it to the
490             // static lifetime to put into the struct. Note that the buffer is
491             // never actually handed out with a static lifetime, but rather the
492             // buffer is loaned with the lifetime of this containing object.
493             // Hence, we're guaranteed that the buffer will never be used after
494             // this object is dead, so this is a safe operation to transmute and
495             // store the data as a static buffer.
496             unsafe { mem::transmute(data) }
497         };
498         Some(ArchiveMetadata {
499             _archive: ar,
500             data: data,
501         })
502     }
503
504     pub fn as_slice<'a>(&'a self) -> &'a [u8] { self.data }
505 }
506
507 // Just a small wrapper to time how long reading metadata takes.
508 fn get_metadata_section(os: Os, filename: &Path) -> Result<MetadataBlob, String> {
509     let start = time::precise_time_ns();
510     let ret = get_metadata_section_imp(os, filename);
511     info!("reading {} => {}ms", filename.filename_display(),
512            (time::precise_time_ns() - start) / 1000000);
513     return ret;
514 }
515
516 fn get_metadata_section_imp(os: Os, filename: &Path) -> Result<MetadataBlob, String> {
517     if !filename.exists() {
518         return Err(format!("no such file: '{}'", filename.display()));
519     }
520     if filename.filename_str().unwrap().ends_with(".rlib") {
521         // Use ArchiveRO for speed here, it's backed by LLVM and uses mmap
522         // internally to read the file. We also avoid even using a memcpy by
523         // just keeping the archive along while the metadata is in use.
524         let archive = match ArchiveRO::open(filename) {
525             Some(ar) => ar,
526             None => {
527                 debug!("llvm didn't like `{}`", filename.display());
528                 return Err(format!("failed to read rlib metadata: '{}'",
529                                    filename.display()));
530             }
531         };
532         return match ArchiveMetadata::new(archive).map(|ar| MetadataArchive(ar)) {
533             None => {
534                 return Err((format!("failed to read rlib metadata: '{}'",
535                                     filename.display())))
536             }
537             Some(blob) => return Ok(blob)
538         }
539     }
540     unsafe {
541         let mb = filename.with_c_str(|buf| {
542             llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf)
543         });
544         if mb as int == 0 {
545             return Err(format!("error reading library: '{}'",
546                                filename.display()))
547         }
548         let of = match ObjectFile::new(mb) {
549             Some(of) => of,
550             _ => {
551                 return Err((format!("provided path not an object file: '{}'",
552                                     filename.display())))
553             }
554         };
555         let si = mk_section_iter(of.llof);
556         while llvm::LLVMIsSectionIteratorAtEnd(of.llof, si.llsi) == False {
557             let mut name_buf = ptr::null();
558             let name_len = llvm::LLVMRustGetSectionName(si.llsi, &mut name_buf);
559             let name = str::raw::from_buf_len(name_buf as *u8, name_len as uint);
560             debug!("get_metadata_section: name {}", name);
561             if read_meta_section_name(os).as_slice() == name.as_slice() {
562                 let cbuf = llvm::LLVMGetSectionContents(si.llsi);
563                 let csz = llvm::LLVMGetSectionSize(si.llsi) as uint;
564                 let mut found =
565                     Err(format!("metadata not found: '{}'", filename.display()));
566                 let cvbuf: *u8 = mem::transmute(cbuf);
567                 let vlen = encoder::metadata_encoding_version.len();
568                 debug!("checking {} bytes of metadata-version stamp",
569                        vlen);
570                 let minsz = cmp::min(vlen, csz);
571                 let version_ok = slice::raw::buf_as_slice(cvbuf, minsz,
572                     |buf0| buf0 == encoder::metadata_encoding_version);
573                 if !version_ok {
574                     return Err((format!("incompatible metadata version found: '{}'",
575                                         filename.display())));
576                 }
577
578                 let cvbuf1 = cvbuf.offset(vlen as int);
579                 debug!("inflating {} bytes of compressed metadata",
580                        csz - vlen);
581                 slice::raw::buf_as_slice(cvbuf1, csz-vlen, |bytes| {
582                     match flate::inflate_bytes(bytes) {
583                         Some(inflated) => found = Ok(MetadataVec(inflated)),
584                         None => {
585                             found =
586                                 Err(format!("failed to decompress \
587                                              metadata for: '{}'",
588                                             filename.display()))
589                         }
590                     }
591                 });
592                 if found.is_ok() {
593                     return found;
594                 }
595             }
596             llvm::LLVMMoveToNextSection(si.llsi);
597         }
598         return Err(format!("metadata not found: '{}'", filename.display()));
599     }
600 }
601
602 pub fn meta_section_name(os: Os) -> &'static str {
603     match os {
604         OsMacos => "__DATA,__note.rustc",
605         OsWin32 => ".note.rustc",
606         OsLinux => ".note.rustc",
607         OsAndroid => ".note.rustc",
608         OsFreebsd => ".note.rustc"
609     }
610 }
611
612 pub fn read_meta_section_name(os: Os) -> &'static str {
613     match os {
614         OsMacos => "__note.rustc",
615         OsWin32 => ".note.rustc",
616         OsLinux => ".note.rustc",
617         OsAndroid => ".note.rustc",
618         OsFreebsd => ".note.rustc"
619     }
620 }
621
622 // A diagnostic function for dumping crate metadata to an output stream
623 pub fn list_file_metadata(os: Os, path: &Path,
624                           out: &mut io::Writer) -> io::IoResult<()> {
625     match get_metadata_section(os, path) {
626         Ok(bytes) => decoder::list_crate_metadata(bytes.as_slice(), out),
627         Err(msg) => {
628             write!(out, "{}\n", msg)
629         }
630     }
631 }