]> git.lizzy.rs Git - rust.git/blob - src/librustc/metadata/loader.rs
Honor hidden doc attribute of derivable trait methods
[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::ptr;
33 use std::rc::Rc;
34 use std::slice;
35 use std::str;
36
37 use collections::{HashMap, HashSet};
38 use flate;
39 use time;
40
41 pub enum Os {
42     OsMacos,
43     OsWin32,
44     OsLinux,
45     OsAndroid,
46     OsFreebsd
47 }
48
49 pub struct HashMismatch {
50     path: Path,
51 }
52
53 pub struct Context<'a> {
54     pub sess: &'a Session,
55     pub span: Span,
56     pub ident: &'a str,
57     pub crate_id: &'a CrateId,
58     pub id_hash: &'a str,
59     pub hash: Option<&'a Svh>,
60     pub os: Os,
61     pub intr: Rc<IdentInterner>,
62     pub rejected_via_hash: Vec<HashMismatch>
63 }
64
65 pub struct Library {
66     pub dylib: Option<Path>,
67     pub rlib: Option<Path>,
68     pub metadata: MetadataBlob,
69 }
70
71 pub struct ArchiveMetadata {
72     archive: ArchiveRO,
73     // See comments in ArchiveMetadata::new for why this is static
74     data: &'static [u8],
75 }
76
77 pub struct CratePaths {
78     pub ident: ~str,
79     pub dylib: Option<Path>,
80     pub rlib: Option<Path>
81 }
82
83 impl CratePaths {
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()),
90         }
91     }
92 }
93
94 // FIXME(#11857) this should be a "real" realpath
95 fn realpath(p: &Path) -> Path {
96     use std::os;
97     use std::io::fs;
98
99     let path = os::make_absolute(p);
100     match fs::readlink(&path) {
101         Ok(p) => p,
102         Err(..) => path
103     }
104 }
105
106 impl<'a> Context<'a> {
107     pub fn load_library_crate(&mut self, root: &Option<CratePaths>) -> Library {
108         match self.find_library_crate() {
109             Some(t) => t,
110             None => {
111                 self.sess.abort_if_errors();
112                 let message = if self.rejected_via_hash.len() > 0 {
113                     format!("found possibly newer version of crate `{}`",
114                             self.ident)
115                 } else {
116                     format!("can't find crate for `{}`", self.ident)
117                 };
118                 let message = match root {
119                     &None => message,
120                     &Some(ref r) => format!("{} which `{}` depends on",
121                                             message, r.ident)
122                 };
123                 self.sess.span_err(self.span, message);
124
125                 if self.rejected_via_hash.len() > 0 {
126                     self.sess.span_note(self.span, "perhaps this crate needs \
127                                                     to be recompiled?");
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()));
133                     }
134                     match root {
135                         &None => {}
136                         &Some(ref r) => {
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()));
141                             }
142                         }
143                     }
144                 }
145                 self.sess.abort_if_errors();
146                 unreachable!()
147             }
148         }
149     }
150
151     fn find_library_crate(&mut self) -> Option<Library> {
152         let filesearch = self.sess.filesearch();
153         let (dyprefix, dysuffix) = self.dylibname();
154
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);
158
159         let mut candidates = HashMap::new();
160
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.
164         //
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).
172         //
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,
177                 Some(file) => file,
178             };
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") {
182                     Some(hash) => {
183                         info!("rlib accepted, hash: {}", hash);
184                         let slot = candidates.find_or_insert_with(hash, |_| {
185                             (HashSet::new(), HashSet::new())
186                         });
187                         let (ref mut rlibs, _) = *slot;
188                         rlibs.insert(realpath(path));
189                         FileMatches
190                     }
191                     None => {
192                         info!("rlib rejected");
193                         FileDoesntMatch
194                     }
195                 }
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) {
199                     Some(hash) => {
200                         info!("dylib accepted, hash: {}", hash);
201                         let slot = candidates.find_or_insert_with(hash, |_| {
202                             (HashSet::new(), HashSet::new())
203                         });
204                         let (_, ref mut dylibs) = *slot;
205                         dylibs.insert(realpath(path));
206                         FileMatches
207                     }
208                     None => {
209                         info!("dylib rejected");
210                         FileDoesntMatch
211                     }
212                 }
213             } else {
214                 FileDoesntMatch
215             }
216         });
217
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.
222         //
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);
231             match metadata {
232                 Some(metadata) => {
233                     libraries.push(Library {
234                         dylib: dylib,
235                         rlib: rlib,
236                         metadata: metadata,
237                     })
238                 }
239                 None => {}
240             }
241         }
242
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
245         // libraries or not.
246         match libraries.len() {
247             0 => None,
248             1 => Some(libraries.move_iter().next().unwrap()),
249             _ => {
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() {
255                     match lib.dylib {
256                         Some(ref p) => {
257                             self.sess.note(format!("path: {}", p.display()));
258                         }
259                         None => {}
260                     }
261                     match lib.rlib {
262                         Some(ref p) => {
263                             self.sess.note(format!("path: {}", p.display()));
264                         }
265                         None => {}
266                     }
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);
270                 }
271                 None
272             }
273         }
274     }
275
276     // Attempts to match the requested version of a library against the file
277     // specified. The prefix/suffix are specified (disambiguates between
278     // rlib/dylib).
279     //
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.
283     //
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
298
299             // hash is irrelevant, no version specified
300             None => return Some(hash.to_owned())
301         }
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())
307         } else {
308             None
309         }
310     }
311
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.
315     //
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>;
323         let mut error = 0;
324
325         if slot.is_some() {
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
329             //                slow.
330             if m.len() == 0 {
331                 return None
332             } else if m.len() == 1 {
333                 return Some(m.move_iter().next().unwrap())
334             }
335         }
336
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) {
340                 Ok(blob) => {
341                     if self.crate_matches(blob.as_slice(), &lib) {
342                         blob
343                     } else {
344                         info!("metadata mismatch");
345                         continue
346                     }
347                 }
348                 Err(_) => {
349                     info!("no metadata found");
350                     continue
351                 }
352             };
353             if ret.is_some() {
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()));
360                 error = 1;
361                 ret = None;
362             }
363             if error > 0 {
364                 error += 1;
365                 self.sess.span_note(self.span,
366                                     format!(r"candidate \#{}: {}", error,
367                                             lib.display()));
368                 continue
369             }
370             *slot = Some(metadata);
371             ret = Some(lib);
372         }
373         return if error > 0 {None} else {ret}
374     }
375
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) => {}
379             _ => return false
380         }
381         let hash = match decoder::maybe_get_crate_hash(crate_data) {
382             Some(hash) => hash, None => return false
383         };
384         match self.hash {
385             None => true,
386             Some(myhash) => {
387                 if *myhash != hash {
388                     self.rejected_via_hash.push(HashMismatch{ path: libpath.clone() });
389                     false
390                 } else {
391                     true
392                 }
393             }
394         }
395     }
396
397     // Returns the corresponding (prefix, suffix) that files need to have for
398     // dynamic libraries
399     fn dylibname(&self) -> (&'static str, &'static str) {
400         match self.os {
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),
406         }
407     }
408 }
409
410 pub fn note_crateid_attr(diag: &SpanHandler, crateid: &CrateId) {
411     diag.handler().note(format!("crate_id: {}", crateid.to_str()));
412 }
413
414 impl ArchiveMetadata {
415     fn new(ar: ArchiveRO) -> Option<ArchiveMetadata> {
416         let data: &'static [u8] = {
417             let data = match ar.read(METADATA_FILENAME) {
418                 Some(data) => data,
419                 None => {
420                     debug!("didn't find '{}' in the archive", METADATA_FILENAME);
421                     return None;
422                 }
423             };
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) }
435         };
436         Some(ArchiveMetadata {
437             archive: ar,
438             data: data,
439         })
440     }
441
442     pub fn as_slice<'a>(&'a self) -> &'a [u8] { self.data }
443 }
444
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);
451     return ret;
452 }
453
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()));
457     }
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) {
463             Some(ar) => ar,
464             None => {
465                 debug!("llvm didn't like `{}`", filename.display());
466                 return Err(format!("failed to read rlib metadata: '{}'",
467                                    filename.display()));
468             }
469         };
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)
474         }
475     }
476     unsafe {
477         let mb = filename.with_c_str(|buf| {
478             llvm::LLVMRustCreateMemoryBufferWithContentsOfFile(buf)
479         });
480         if mb as int == 0 {
481             return Err(format!("error reading library: '{}'",filename.display()))
482         }
483         let of = match ObjectFile::new(mb) {
484             Some(of) => of,
485             _ => return Err(format!("provided path not an object file: '{}'", filename.display()))
486         };
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",
500                        vlen);
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())); }
506
507                 let cvbuf1 = cvbuf.offset(vlen as int);
508                 debug!("inflating {} bytes of compressed metadata",
509                        csz - vlen);
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: '{}'",
514                                                     filename.display()))
515                     }
516                 });
517                 if found.is_ok() {
518                     return found;
519                 }
520             }
521             llvm::LLVMMoveToNextSection(si.llsi);
522         }
523         return Err(format!("metadata not found: '{}'", filename.display()));
524     }
525 }
526
527 pub fn meta_section_name(os: Os) -> &'static str {
528     match os {
529         OsMacos => "__DATA,__note.rustc",
530         OsWin32 => ".note.rustc",
531         OsLinux => ".note.rustc",
532         OsAndroid => ".note.rustc",
533         OsFreebsd => ".note.rustc"
534     }
535 }
536
537 pub fn read_meta_section_name(os: Os) -> &'static str {
538     match os {
539         OsMacos => "__note.rustc",
540         OsWin32 => ".note.rustc",
541         OsLinux => ".note.rustc",
542         OsAndroid => ".note.rustc",
543         OsFreebsd => ".note.rustc"
544     }
545 }
546
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),
552         Err(msg) => {
553             write!(out, "{}\n", msg)
554         }
555     }
556 }