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