Skip to content

Commit afa5f57

Browse files
committed
Re-work loading crates with nicer errors
This commit rewrites crate loading internally in attempt to look at less metadata and provide nicer errors. The loading is now split up into a few stages: 1. Collect a mapping of (hash => ~[Path]) for a set of candidate libraries for a given search. The hash is the hash in the filename and the Path is the location of the library in question. All candidates are filtered based on their prefix/suffix (dylib/rlib appropriate) and then the hash/version are split up and are compared (if necessary). This means that if you're looking for an exact hash of library you don't have to open up the metadata of all libraries named the same, but also in your path. 2. Once this mapping is constructed, each (hash, ~[Path]) pair is filtered down to just a Path. This is necessary because the same rlib could show up twice in the path in multiple locations. Right now the filenames are based on just the crate id, so this could be indicative of multiple version of a crate during one crate_id lifetime in the path. If multiple duplicate crates are found, an error is generated. 3. Now that we have a mapping of (hash => Path), we error on multiple versions saying that multiple versions were found. Only if there's one (hash => Path) pair do we actually return that Path and its metadata. With this restructuring, it restructures code so errors which were assertions previously are now first-class errors. Additionally, this should read much less metadata with lots of crates of the same name or same version in a path. Closes #11908
1 parent 6532d2f commit afa5f57

File tree

5 files changed

+240
-94
lines changed

5 files changed

+240
-94
lines changed

src/librustc/metadata/loader.rs

+167-94
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use syntax::attr::AttrMetaMethods;
2626

2727
use std::c_str::ToCStr;
2828
use std::cast;
29+
use std::hashmap::{HashMap, HashSet};
2930
use std::cmp;
3031
use std::io;
3132
use std::os::consts::{macos, freebsd, linux, android, win32};
@@ -69,6 +70,7 @@ impl Context {
6970
match self.find_library_crate() {
7071
Some(t) => t,
7172
None => {
73+
self.sess.abort_if_errors();
7274
let message = match root_ident {
7375
None => format!("can't find crate for `{}`", self.ident),
7476
Some(c) => format!("can't find crate for `{}` which `{}` depends on",
@@ -82,78 +84,107 @@ impl Context {
8284

8385
fn find_library_crate(&self) -> Option<Library> {
8486
let filesearch = self.sess.filesearch;
85-
let crate_name = self.name.clone();
8687
let (dyprefix, dysuffix) = self.dylibname();
8788

8889
// want: crate_name.dir_part() + prefix + crate_name.file_part + "-"
89-
let dylib_prefix = format!("{}{}-", dyprefix, crate_name);
90-
let rlib_prefix = format!("lib{}-", crate_name);
90+
let dylib_prefix = format!("{}{}-", dyprefix, self.name);
91+
let rlib_prefix = format!("lib{}-", self.name);
9192

92-
let mut matches = ~[];
93-
filesearch.search(|path| {
94-
match path.filename_str() {
95-
None => FileDoesntMatch,
96-
Some(file) => {
97-
let (candidate, existing) = if file.starts_with(rlib_prefix) &&
98-
file.ends_with(".rlib") {
99-
debug!("{} is an rlib candidate", path.display());
100-
(true, self.add_existing_rlib(matches, path, file))
101-
} else if file.starts_with(dylib_prefix) &&
102-
file.ends_with(dysuffix) {
103-
debug!("{} is a dylib candidate", path.display());
104-
(true, self.add_existing_dylib(matches, path, file))
105-
} else {
106-
(false, false)
107-
};
93+
let mut candidates = HashMap::new();
10894

109-
if candidate && existing {
95+
// First, find all possible candidate rlibs and dylibs purely based on
96+
// the name of the files themselves. We're trying to match against an
97+
// exact crate_id and a possibly an exact hash.
98+
//
99+
// During this step, we can filter all found libraries based on the
100+
// name and id found in the crate id (we ignore the path portion for
101+
// filename matching), as well as the exact hash (if specified). If we
102+
// end up having many candidates, we must look at the metadata to
103+
// perform exact matches against hashes/crate ids. Note that opening up
104+
// the metadata is where we do an exact match against the full contents
105+
// of the crate id (path/name/id).
106+
//
107+
// The goal of this step is to look at as little metadata as possible.
108+
filesearch.search(|path| {
109+
let file = match path.filename_str() {
110+
None => return FileDoesntMatch,
111+
Some(file) => file,
112+
};
113+
if file.starts_with(rlib_prefix) && file.ends_with(".rlib") {
114+
info!("rlib candidate: {}", path.display());
115+
match self.try_match(file, rlib_prefix, ".rlib") {
116+
Some(hash) => {
117+
info!("rlib accepted, hash: {}", hash);
118+
let slot = candidates.find_or_insert_with(hash, |_| {
119+
(HashSet::new(), HashSet::new())
120+
});
121+
let (ref mut rlibs, _) = *slot;
122+
rlibs.insert(path.clone());
110123
FileMatches
111-
} else if candidate {
112-
match get_metadata_section(self.os, path) {
113-
Some(cvec) =>
114-
if crate_matches(cvec.as_slice(),
115-
self.name.clone(),
116-
self.version.clone(),
117-
self.hash.clone()) {
118-
debug!("found {} with matching crate_id",
119-
path.display());
120-
let (rlib, dylib) = if file.ends_with(".rlib") {
121-
(Some(path.clone()), None)
122-
} else {
123-
(None, Some(path.clone()))
124-
};
125-
matches.push(Library {
126-
rlib: rlib,
127-
dylib: dylib,
128-
metadata: cvec,
129-
});
130-
FileMatches
131-
} else {
132-
debug!("skipping {}, crate_id doesn't match",
133-
path.display());
134-
FileDoesntMatch
135-
},
136-
_ => {
137-
debug!("could not load metadata for {}",
138-
path.display());
139-
FileDoesntMatch
140-
}
141-
}
142-
} else {
124+
}
125+
None => {
126+
info!("rlib rejected");
143127
FileDoesntMatch
144128
}
145129
}
130+
} else if file.starts_with(dylib_prefix) && file.ends_with(dysuffix){
131+
info!("dylib candidate: {}", path.display());
132+
match self.try_match(file, dylib_prefix, dysuffix) {
133+
Some(hash) => {
134+
info!("dylib accepted, hash: {}", hash);
135+
let slot = candidates.find_or_insert_with(hash, |_| {
136+
(HashSet::new(), HashSet::new())
137+
});
138+
let (_, ref mut dylibs) = *slot;
139+
dylibs.insert(path.clone());
140+
FileMatches
141+
}
142+
None => {
143+
info!("dylib rejected");
144+
FileDoesntMatch
145+
}
146+
}
147+
} else {
148+
FileDoesntMatch
146149
}
147150
});
148151

149-
match matches.len() {
152+
// We have now collected all known libraries into a set of candidates
153+
// keyed of the filename hash listed. For each filename, we also have a
154+
// list of rlibs/dylibs that apply. Here, we map each of these lists
155+
// (per hash), to a Library candidate for returning.
156+
//
157+
// A Library candidate is created if the metadata for the set of
158+
// libraries corresponds to the crate id and hash criteria that this
159+
// serach is being performed for.
160+
let mut libraries = ~[];
161+
for (_hash, (rlibs, dylibs)) in candidates.move_iter() {
162+
let mut metadata = None;
163+
let rlib = self.extract_one(rlibs, "rlib", &mut metadata);
164+
let dylib = self.extract_one(dylibs, "dylib", &mut metadata);
165+
match metadata {
166+
Some(metadata) => {
167+
libraries.push(Library {
168+
dylib: dylib,
169+
rlib: rlib,
170+
metadata: metadata,
171+
})
172+
}
173+
None => {}
174+
}
175+
}
176+
177+
// Having now translated all relevant found hashes into libraries, see
178+
// what we've got and figure out if we found multiple candidates for
179+
// libraries or not.
180+
match libraries.len() {
150181
0 => None,
151-
1 => Some(matches[0]),
182+
1 => Some(libraries[0]),
152183
_ => {
153184
self.sess.span_err(self.span,
154-
format!("multiple matching crates for `{}`", crate_name));
185+
format!("multiple matching crates for `{}`", self.name));
155186
self.sess.note("candidates:");
156-
for lib in matches.iter() {
187+
for lib in libraries.iter() {
157188
match lib.dylib {
158189
Some(ref p) => {
159190
self.sess.note(format!("path: {}", p.display()));
@@ -175,50 +206,90 @@ impl Context {
175206
}
176207
}
177208
}
178-
self.sess.abort_if_errors();
179209
None
180210
}
181211
}
182212
}
183213

184-
fn add_existing_rlib(&self, libs: &mut [Library],
185-
path: &Path, file: &str) -> bool {
186-
let (prefix, suffix) = self.dylibname();
187-
let file = file.slice_from(3); // chop off 'lib'
188-
let file = file.slice_to(file.len() - 5); // chop off '.rlib'
189-
let file = format!("{}{}{}", prefix, file, suffix);
190-
191-
for lib in libs.mut_iter() {
192-
match lib.dylib {
193-
Some(ref p) if p.filename_str() == Some(file.as_slice()) => {
194-
assert!(lib.rlib.is_none()); // FIXME: legit compiler error
195-
lib.rlib = Some(path.clone());
196-
return true;
197-
}
198-
Some(..) | None => {}
199-
}
214+
// Attempts to match the requested version of a library against the file
215+
// specified. The prefix/suffix are specified (disambiguates between
216+
// rlib/dylib).
217+
//
218+
// The return value is `None` if `file` doesn't look like a rust-generated
219+
// library, or if a specific version was requested and it doens't match the
220+
// apparent file's version.
221+
//
222+
// If everything checks out, then `Some(hash)` is returned where `hash` is
223+
// the listed hash in the filename itself.
224+
fn try_match(&self, file: &str, prefix: &str, suffix: &str) -> Option<~str>{
225+
let middle = file.slice(prefix.len(), file.len() - suffix.len());
226+
debug!("matching -- {}, middle: {}", file, middle);
227+
let mut parts = middle.splitn('-', 1);
228+
let hash = match parts.next() { Some(h) => h, None => return None };
229+
debug!("matching -- {}, hash: {}", file, hash);
230+
let vers = match parts.next() { Some(v) => v, None => return None };
231+
debug!("matching -- {}, vers: {}", file, vers);
232+
if !self.version.is_empty() && self.version.as_slice() != vers {
233+
return None
234+
}
235+
debug!("matching -- {}, vers ok (requested {})", file,
236+
self.version);
237+
// hashes in filenames are prefixes of the "true hash"
238+
if self.hash.is_empty() || self.hash.starts_with(hash) {
239+
debug!("matching -- {}, hash ok (requested {})", file, self.hash);
240+
Some(hash.to_owned())
241+
} else {
242+
None
200243
}
201-
return false;
202244
}
203245

204-
fn add_existing_dylib(&self, libs: &mut [Library],
205-
path: &Path, file: &str) -> bool {
206-
let (prefix, suffix) = self.dylibname();
207-
let file = file.slice_from(prefix.len());
208-
let file = file.slice_to(file.len() - suffix.len());
209-
let file = format!("lib{}.rlib", file);
246+
// Attempts to extract *one* library from the set `m`. If the set has no
247+
// elements, `None` is returned. If the set has more than one element, then
248+
// the errors and notes are emitted about the set of libraries.
249+
//
250+
// With only one library in the set, this function will extract it, and then
251+
// read the metadata from it if `*slot` is `None`. If the metadata couldn't
252+
// be read, it is assumed that the file isn't a valid rust library (no
253+
// errors are emitted).
254+
//
255+
// FIXME(#10786): for an optimization, we only read one of the library's
256+
// metadata sections. In theory we should read both, but
257+
// reading dylib metadata is quite slow.
258+
fn extract_one(&self, m: HashSet<Path>, flavor: &str,
259+
slot: &mut Option<MetadataBlob>) -> Option<Path> {
260+
if m.len() == 0 { return None }
261+
if m.len() > 1 {
262+
self.sess.span_err(self.span,
263+
format!("multiple {} candidates for `{}` \
264+
found", flavor, self.name));
265+
for (i, path) in m.iter().enumerate() {
266+
self.sess.span_note(self.span,
267+
format!(r"candidate \#{}: {}", i + 1,
268+
path.display()));
269+
}
270+
return None
271+
}
210272

211-
for lib in libs.mut_iter() {
212-
match lib.rlib {
213-
Some(ref p) if p.filename_str() == Some(file.as_slice()) => {
214-
assert!(lib.dylib.is_none()); // FIXME: legit compiler error
215-
lib.dylib = Some(path.clone());
216-
return true;
273+
let lib = m.move_iter().next().unwrap();
274+
if slot.is_none() {
275+
info!("{} reading meatadata from: {}", flavor, lib.display());
276+
match get_metadata_section(self.os, &lib) {
277+
Some(blob) => {
278+
if crate_matches(blob.as_slice(), self.name,
279+
self.version, self.hash) {
280+
*slot = Some(blob);
281+
} else {
282+
info!("metadata mismatch");
283+
return None;
284+
}
285+
}
286+
None => {
287+
info!("no metadata found");
288+
return None
217289
}
218-
Some(..) | None => {}
219290
}
220291
}
221-
return false;
292+
return Some(lib);
222293
}
223294

224295
// Returns the corresponding (prefix, suffix) that files need to have for
@@ -239,16 +310,16 @@ pub fn note_crateid_attr(diag: @SpanHandler, crateid: &CrateId) {
239310
}
240311

241312
fn crate_matches(crate_data: &[u8],
242-
name: ~str,
243-
version: ~str,
244-
hash: ~str) -> bool {
313+
name: &str,
314+
version: &str,
315+
hash: &str) -> bool {
245316
let attrs = decoder::get_crate_attributes(crate_data);
246317
match attr::find_crateid(attrs) {
247318
None => false,
248319
Some(crateid) => {
249320
if !hash.is_empty() {
250321
let chash = decoder::get_crate_hash(crate_data);
251-
if chash != hash { return false; }
322+
if chash.as_slice() != hash { return false; }
252323
}
253324
name == crateid.name &&
254325
(version.is_empty() ||
@@ -383,7 +454,9 @@ pub fn read_meta_section_name(os: Os) -> &'static str {
383454
pub fn list_file_metadata(os: Os, path: &Path,
384455
out: &mut io::Writer) -> io::IoResult<()> {
385456
match get_metadata_section(os, path) {
386-
Some(bytes) => decoder::list_crate_metadata(bytes.as_slice(), out),
387-
None => write!(out, "could not find metadata in {}.\n", path.display())
457+
Some(bytes) => decoder::list_crate_metadata(bytes.as_slice(), out),
458+
None => {
459+
write!(out, "could not find metadata in {}.\n", path.display())
460+
}
388461
}
389462
}

src/test/auxiliary/issue-11908-1.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2014 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+
// no-prefer-dynamic
12+
13+
#[crate_id = "collections#0.10-pre"];
14+
#[crate_type = "dylib"];

src/test/auxiliary/issue-11908-2.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2014 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+
// no-prefer-dynamic
12+
13+
#[crate_id = "collections#0.10-pre"];
14+
#[crate_type = "rlib"];
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2014 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+
// aux-build:issue-11908-1.rs
12+
// ignore-android this test is incompatible with the android test runner
13+
// error-pattern: multiple dylib candidates for `collections` found
14+
15+
// This test ensures that if you have the same rlib or dylib at two locations
16+
// in the same path that you don't hit an assertion in the compiler.
17+
//
18+
// Note that this relies on `libcollections` to be in the path somewhere else,
19+
// and then our aux-built libraries will collide with libcollections (they have
20+
// the same version listed)
21+
22+
extern crate collections;
23+
24+
fn main() {}

0 commit comments

Comments
 (0)