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