├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── examples ├── close.rs ├── close_method.rs ├── close_vec.rs ├── for_loop.rs ├── match.rs └── test.rs ├── src └── lib.rs └── tests ├── compile-fail ├── if-not-linear.rs ├── if-return.rs ├── match-not-linear.rs ├── non-linear-for-loop.rs ├── simple-if.rs ├── simple-loop.rs └── unused.rs ├── compile_test.rs ├── if-return.rs ├── loop-break.rs ├── loop-continue.rs └── match-return.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: nightly 3 | sudo: false 4 | 5 | notifications: 6 | email: 7 | - pmunksgaard@gmail.com 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "humpty_dumpty" 4 | version = "0.0.1" 5 | authors = ["Manish Goregaokar "] 6 | description = "A plugin to prevent certain types from being dropped, thus making them linear" 7 | repository = "https://github.com/Manishearth/humpty_dumpty" 8 | readme = "README.md" 9 | license = "MPL-2.0" 10 | keywords = ["linear", "drop", "plugin"] 11 | 12 | 13 | [lib] 14 | name = "humpty_dumpty" 15 | plugin = true 16 | 17 | [dev-dependencies] 18 | compiletest_rs = "*" 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Humpty Dumpty 2 | 3 | [![Build Status](https://travis-ci.org/Manishearth/humpty_dumpty.svg)](https://travis-ci.org/Manishearth/humpty_dumpty) 4 | 5 | The goal of this library is to be able to define types that cannot be implicitly 6 | dropped except in controlled situations. 7 | 8 | A sketch of the design can be found [here](https://gist.github.com/Manishearth/045ee457d6f81183ec6b). The design does not handle branches, 9 | though it can be extended to do so. It's also a bit different from what I finally implemented 10 | 11 | The idea is, that for a type that is marked `#[drop_protection]`, only functions annotated with `#[allowed_on_protected]` can use these, 12 | and each local *must* be dropped with a function marked `#[allowed_drop]` before its scope finishes. 13 | 14 | Current status: Is able to track such types and report on their usage. Maintains a list of what has been dropped properly to 15 | detect implicit drops. 16 | 17 | Some missing (but planned) functionality: 18 | 19 | - Cannot yet handle conditional drops, i.e. those in branches. 20 | - Cannot yet handle any bindings other than a let binding 21 | - Allowed functions cannot yet take &/&mut inputs 22 | - Cannot yet mark method calls as allowed 23 | 24 | 25 | To test, run `cargo run --example test`, or even better `rustc examples/test.rs -L target` (after building). The latter is better 26 | because it will rebuild every time, and we're only interested in build output. 27 | -------------------------------------------------------------------------------- /examples/close.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | #[allow_drop="Foo"] 12 | fn close(_: Foo) { } 13 | 14 | fn main() { 15 | let x = Foo; 16 | close(x); 17 | } 18 | -------------------------------------------------------------------------------- /examples/close_method.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | impl Foo { 12 | #[allow_drop="Foo"] 13 | fn close(self) { } 14 | } 15 | 16 | fn main() { 17 | let x = Foo; 18 | x.close(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/close_vec.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | unsafe trait Closeable { 12 | fn close(self); 13 | } 14 | 15 | unsafe impl Closeable for Vec { 16 | #[allow_drop="collections::vec::Vec"] 17 | fn close(self) { } 18 | } 19 | 20 | 21 | fn main() { 22 | let v: Vec = Vec::new(); 23 | v.close(); 24 | } 25 | -------------------------------------------------------------------------------- /examples/for_loop.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | #[allow_drop="Foo"] 12 | fn close(x: Foo) { } 13 | 14 | fn main() { 15 | let v = vec!(Foo, Foo, Foo); 16 | 17 | for x in v { 18 | close(x) 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /examples/match.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | #[drop_protect] 12 | struct Bar; 13 | 14 | impl Foo { 15 | #[allow_drop="Foo"] 16 | fn close(self) { } 17 | } 18 | 19 | impl Bar { 20 | #[allow_drop="Bar"] 21 | fn close(self) { } 22 | } 23 | 24 | fn main() { 25 | let x: Result = Ok(Foo); 26 | match x { 27 | Ok(y) => y.close(), 28 | Err(y) => y.close(), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/test.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | impl Foo { 12 | fn something(self) -> Self { 13 | self 14 | } 15 | } 16 | 17 | // Should not warn, since we're not dropping anything 18 | fn id(x: T) -> T { 19 | x 20 | } 21 | 22 | // Should not warn 23 | #[allow_drop="Foo"] 24 | fn close(_: Foo) { 25 | 26 | } 27 | 28 | fn main() { 29 | let mut x = Foo; 30 | x = x; 31 | let y = id(x); 32 | let z = y.something(); 33 | close(z); 34 | } 35 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin_registrar, quote, plugin, box_syntax, rustc_private, 2 | slice_patterns)] 3 | 4 | #![plugin(syntax)] 5 | #![plugin(rustc)] 6 | 7 | #![crate_type="dylib"] 8 | 9 | #[macro_use] 10 | extern crate syntax; 11 | #[macro_use] 12 | extern crate rustc; 13 | #[macro_use] 14 | extern crate log; 15 | 16 | use rustc::lint::{Context, LintArray, LintPass}; 17 | use rustc::plugin::Registry; 18 | 19 | use syntax::ast::*; 20 | use syntax::ast_util; 21 | use syntax::attr::{AttrMetaMethods}; 22 | use rustc::middle::ty::{self, ctxt}; 23 | use rustc::util::ppaux::Repr; 24 | use rustc::util::nodemap::{FnvHashMap, NodeMap}; 25 | use rustc::middle::def::*; 26 | use syntax::visit::{self, Visitor}; 27 | use syntax::codemap::Span; 28 | 29 | declare_lint!(DROPPED_LINEAR, Warn, "Warn about linear values being dropped"); 30 | 31 | struct Pass; 32 | 33 | impl LintPass for Pass { 34 | fn get_lints(&self) -> LintArray { 35 | lint_array!(DROPPED_LINEAR) 36 | } 37 | 38 | fn check_fn(&mut self, cx: &Context, _: visit::FnKind, decl: &FnDecl, block: &Block, _: Span, id: NodeId) { 39 | // Walk the arguments and add them to the map 40 | let attrs = cx.tcx.map.attrs(id); 41 | let mut visitor = LinearVisitor::new(cx, block.id, attrs); 42 | for arg in decl.inputs.iter() { 43 | visitor.walk_pat_and_add(&arg.pat); 44 | } 45 | 46 | visit::walk_block(&mut visitor, block); 47 | 48 | if !visitor.returning { 49 | for var in visitor.map.iter() { 50 | // TODO: prettify 51 | if !visitor.can_drop(var.0) { 52 | cx.span_lint(DROPPED_LINEAR, *var.1, "dropped var"); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | #[derive(Clone)] 60 | struct LinearVisitor<'a : 'b, 'tcx : 'a, 'b> { 61 | // Type context, with all the goodies 62 | map: NodeMap, // (blockid and span for declaration) 63 | cx: &'b Context<'a, 'tcx>, 64 | returning: bool, 65 | attrs: &'tcx [Attribute], 66 | breaking: bool, 67 | loopin: Option>, 68 | loopout: Option>, 69 | } 70 | 71 | impl <'a, 'tcx, 'b> LinearVisitor<'a, 'tcx, 'b> { 72 | fn new(cx: &'b Context<'a, 'tcx>, _: NodeId, attrs: &'tcx [Attribute]) -> Self { 73 | let map = FnvHashMap(); 74 | let visitor = LinearVisitor { cx: cx, 75 | map: map, 76 | returning: false, 77 | attrs: attrs, 78 | breaking: false, 79 | loopin: None, 80 | loopout: None 81 | }; 82 | visitor 83 | } 84 | 85 | fn is_protected(&self, ty: ty::Ty<'tcx>) -> bool { 86 | match ty.sty { 87 | ty::TypeVariants::TyEnum(did, _) | ty::TypeVariants::TyStruct(did, _) 88 | if ty::has_attr(self.cx.tcx, did, "drop_protect") => true, 89 | _ => false, 90 | } 91 | } 92 | 93 | fn can_drop(&self, id: &NodeId) -> bool { 94 | let tcx = self.cx.tcx; 95 | let node_ty = ty::node_id_to_type(tcx, *id); 96 | for attr in self.attrs { 97 | if let MetaNameValue(ref intstr, ref lit) = attr.node.value.node { 98 | if *intstr == "allow_drop" { 99 | if let LitStr(ref litstr, _) = lit.node { 100 | if *litstr == &node_ty.repr(tcx)[..] { 101 | return true; 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | false 109 | } 110 | 111 | fn walk_pat_and_add(&mut self, pat: &Pat) { 112 | ast_util::walk_pat(pat, |p| { 113 | if let PatIdent(_, _, _) = p.node { 114 | let ty = ty::pat_ty(self.cx.tcx, p); 115 | let mut protected = false; 116 | ty::walk_ty(ty, |t| { 117 | if self.is_protected(t) { 118 | protected = true; 119 | } 120 | }); 121 | if protected { 122 | self.map.insert(p.id, p.span); 123 | } 124 | } else if let PatWild(_) = p.node { 125 | let ty = ty::pat_ty(self.cx.tcx, p); 126 | let mut protected = false; 127 | ty::walk_ty(ty, |t| { 128 | if self.is_protected(t) { 129 | protected = true; 130 | } 131 | }); 132 | if protected && !self.can_drop(&pat.id) { 133 | self.cx.span_lint(DROPPED_LINEAR, p.span, "Protected type is dropped"); 134 | } 135 | } 136 | true 137 | }); 138 | } 139 | 140 | fn update_loopout(&mut self, e: &Expr, loopout: &Option>) { 141 | if self.loopout.is_some() { 142 | if &self.loopout != loopout { 143 | self.cx.span_lint(DROPPED_LINEAR, e.span, "Non-linear loopout"); 144 | } 145 | } else { 146 | self.loopout = loopout.clone(); 147 | } 148 | } 149 | } 150 | 151 | impl<'a, 'b, 'tcx, 'v> Visitor<'v> for LinearVisitor<'a, 'tcx, 'b> { 152 | fn visit_decl(&mut self, d: &'v Decl) { 153 | // If d is local and if d.ty is protected: 154 | // - First handle the initializer: We need to remove any used variables that are moved 155 | // - Also, if the initializer is a reference, then what? 156 | // - then add pat.id to self.map so we can track it going forward 157 | // We also need to handle if l.source is a LocalFor 158 | 159 | if let DeclLocal(ref l) = d.node { 160 | if l.source == LocalFor { 161 | unimplemented!(); 162 | } 163 | 164 | // Remove moved variables from map 165 | // Maybe it's a reference? Use maybe_walk_expr 166 | if let Some(ref ex) = l.init { 167 | self.visit_expr(&ex); 168 | } 169 | 170 | // Add the ids in the pat 171 | self.walk_pat_and_add(&l.pat); 172 | return; 173 | } 174 | visit::walk_decl(self, d); 175 | } 176 | 177 | fn visit_stmt(&mut self, s: &'v Stmt) { 178 | if !self.returning { 179 | if let StmtSemi(ref e, _) = s.node { 180 | let ty = ty::expr_ty(self.cx.tcx, e); 181 | if self.is_protected(ty) { 182 | self.cx.span_lint(DROPPED_LINEAR, s.span, "Return type is protected but unused"); 183 | } 184 | } 185 | visit::walk_stmt(self, s); 186 | } 187 | } 188 | 189 | fn visit_expr(&mut self, e: &'v Expr) { 190 | // Visit and remove all consumed values 191 | // Which Exprs do we need to handle? 192 | // At least ExprCall and ExprMethodCall 193 | if self.returning || self.breaking { 194 | return // Don't proceed 195 | } 196 | match e.node { 197 | ExprAssign(ref lhs, ref rhs) => { 198 | // Remove all protected vars in rhs 199 | self.visit_expr(&rhs); 200 | 201 | // Get the defid 202 | let defid = if let ExprPath(_, _) = lhs.node { 203 | expr_to_deflocal(self.cx.tcx, lhs).unwrap() 204 | } else { 205 | unimplemented!() 206 | }; 207 | 208 | // Check that we're not overwriting something 209 | if self.map.contains_key(&defid) { 210 | self.cx.span_lint(DROPPED_LINEAR, lhs.span, "cannot overwrite linear type"); 211 | } else { 212 | self.map.insert(defid, e.span); 213 | } 214 | } 215 | ExprPath(_, _) => { 216 | // If the path is a local id that's in our map and it is getting 217 | // moved, remove it from self.map. If we got this far, it is a 218 | // move 219 | if let Some(id) = expr_to_deflocal(self.cx.tcx, e) { 220 | if self.map.contains_key(&id) { 221 | self.map.remove(&id).unwrap(); 222 | } 223 | } 224 | visit::walk_expr(self, e); 225 | } 226 | ExprCall(_, _) | ExprMethodCall(_, _, _) => { 227 | visit::walk_expr(self, e); 228 | } 229 | ExprAddrOf(_, ref e1) => { 230 | if let ExprPath(_, _) = e1.node { 231 | // ignore 232 | } else { 233 | // recurse on e1 234 | self.visit_expr(&e1); 235 | } 236 | } 237 | 238 | ExprIf(_, ref if_block, ref else_expr) => { 239 | // Walk each of the arms, and check that outcoming hms are 240 | // identical 241 | let mut old: Option = None; 242 | 243 | let mut v = self.clone(); 244 | v.visit_block(&if_block); 245 | if !v.returning { 246 | self.update_loopout(e, &v.loopout); 247 | 248 | if else_expr.is_none() { 249 | if v.map != self.map && !v.breaking { 250 | self.cx.span_lint(DROPPED_LINEAR, e.span, "If branch is not linear"); 251 | } 252 | v.breaking = false; 253 | } 254 | old = Some(v); 255 | } 256 | 257 | if let &Some(ref else_expr) = else_expr { 258 | let mut v = self.clone(); 259 | v.visit_expr(&else_expr); 260 | if !v.returning { 261 | self.update_loopout(e, &v.loopout); 262 | if let &Some(ref tmp) = &old { 263 | if !tmp.breaking { 264 | if !v.breaking && tmp.map != v.map { 265 | // neither branch is breaking, but their maps are unequal 266 | self.cx.span_lint(DROPPED_LINEAR, e.span, "If branches are not linear"); 267 | } else if v.breaking && tmp.map != self.map { 268 | // `else` is breaking and `if` map is not neutral 269 | self.cx.span_lint(DROPPED_LINEAR, e.span, "If branches are not linear"); 270 | } 271 | // The resulting state is non-breaking 272 | v.breaking = false; 273 | } 274 | // If tmp (the `if` branch) is breaking the whole if 275 | // expr is breaking iff the `else` branch is 276 | // breaking. 277 | } 278 | old = Some(v); 279 | } 280 | } 281 | 282 | if let Some(old) = old { 283 | if !old.map.keys().all(|k| self.map.contains_key(k)) { 284 | self.cx.span_lint(DROPPED_LINEAR, e.span, "Match arms are not linear"); 285 | } else { 286 | self.map = old.map; 287 | self.breaking = old.breaking; 288 | } 289 | } else { 290 | // Everything is returning? 291 | self.returning = true; 292 | } 293 | } 294 | ExprMatch(ref e1, ref arms, _) => { 295 | // Consume stuff in e 296 | self.visit_expr(&e1); 297 | 298 | // If the match looks like this, we're in an expanded for loop: 299 | // match ::std::iter::IntoIterator::into_iter(&[1, 2, 3]) { 300 | // mut iter => 301 | // loop { 302 | // match ::std::iter::Iterator::next(&mut iter) { <- ForLoopDesugar 303 | // ::std::option::Option::Some(x) => { } 304 | // ::std::option::Option::None => break , 305 | // } 306 | // }, 307 | // } 308 | let mut is_for_loop = false; 309 | if let [Arm { ref body, .. }] = &arms[..] { 310 | if let ExprLoop(ref loop_block, _) = body.node { 311 | if let &Block { expr: Some(ref loop_expr), .. } = &**loop_block { 312 | if let ExprMatch(_, _, MatchSource::ForLoopDesugar) = loop_expr.node { 313 | is_for_loop = true; 314 | // Skip pattern in outermost arm, just visit the body 315 | // TODO: Guards 316 | let mut tmp = self.clone(); 317 | tmp.visit_block(loop_block); 318 | if !tmp.returning { 319 | if let Some(hm) = tmp.loopout { 320 | if tmp.map != hm { 321 | self.cx.span_lint(DROPPED_LINEAR, e.span, "Non-linear for loop"); 322 | } else { 323 | self.map = tmp.map; 324 | } 325 | } 326 | } else { 327 | self.returning = true; 328 | } 329 | } 330 | } 331 | } 332 | } 333 | 334 | if !is_for_loop { 335 | // Walk each of the arms, and check that outcoming hms are 336 | // identical 337 | // 338 | // TODO: Replace with Option once rust-lang/rust/issues/24227 339 | // is fixed 340 | let mut old: Option> = None; 341 | for arm in arms { 342 | let mut v = self.clone(); 343 | v.visit_arm(&arm); 344 | if !v.returning { 345 | self.update_loopout(e, &v.loopout); 346 | if let Some(tmp) = old { 347 | if !tmp.breaking { 348 | if !v.breaking && tmp.map != v.map { 349 | // Neither are breaking, but their scopes are different 350 | self.cx.span_lint(DROPPED_LINEAR, e.span, "Match arms are not linear"); 351 | } else if v.breaking && tmp.map != self.map { 352 | self.cx.span_lint(DROPPED_LINEAR, e.span, "Match arms are not linear"); 353 | } 354 | v.breaking = false; 355 | } 356 | } 357 | old = Some(v); 358 | } 359 | } 360 | 361 | if let Some(old) = old { 362 | if !old.map.keys().all(|k| self.map.contains_key(k)) { 363 | self.cx.span_lint(DROPPED_LINEAR, e.span, "Match arms are not linear"); 364 | } else { 365 | self.map = old.map; 366 | self.breaking = old.breaking; 367 | } 368 | } else { 369 | // Everything is returning 370 | self.returning = true; 371 | } 372 | } 373 | } 374 | ExprRet(ref e1) => { 375 | // If there is a return value, consume it 376 | if let &Some(ref ret) = e1 { 377 | self.visit_expr(ret); 378 | } 379 | 380 | // Check that the hm is empty 381 | for var in self.map.iter() { 382 | // TODO: prettify 383 | if !self.can_drop(var.0) { 384 | self.cx.span_lint(DROPPED_LINEAR, *var.1, "dropped var"); 385 | } 386 | } 387 | 388 | // Set the flag, indicating that we've returned 389 | self.returning = true; 390 | } 391 | ExprLoop(ref body, _) => { 392 | let mut tmp = self.clone(); 393 | tmp.loopin = Some(self.map.clone()); 394 | tmp.visit_block(body); 395 | if !tmp.returning { 396 | if let Some(outgoing) = tmp.loopout { 397 | self.map = outgoing; 398 | } else { 399 | self.map = tmp.map; 400 | } 401 | } else { 402 | self.returning = true; 403 | } 404 | } 405 | ExprWhile(_, _, _) => { 406 | unimplemented!(); 407 | } 408 | ExprBreak(label) => { 409 | if label.is_some() { 410 | unimplemented!(); 411 | } 412 | self.breaking = true; 413 | if let Some(ref outgoing) = self.loopout { 414 | if &self.map == outgoing { 415 | // All good 416 | } else { 417 | self.cx.span_lint(DROPPED_LINEAR, e.span, "Non-linear break"); 418 | } 419 | } else { 420 | self.loopout = Some(self.map.clone()); 421 | } 422 | } 423 | ExprAgain(ref label) => { 424 | if label.is_some() { 425 | unimplemented!(); 426 | } 427 | self.breaking = true; 428 | if let Some(ref incoming) = self.loopin { 429 | if &self.map == incoming { 430 | // All good 431 | } else { 432 | self.cx.span_lint(DROPPED_LINEAR, e.span, "Non-linear continue"); 433 | } 434 | } else { 435 | unreachable!(); 436 | } 437 | } 438 | _ => visit::walk_expr(self, e), 439 | } 440 | } 441 | 442 | fn visit_arm(&mut self, a: &'v Arm) { 443 | // Add patterns 444 | for pat in a.pats.iter() { 445 | self.walk_pat_and_add(&pat); 446 | } 447 | 448 | // TODO: What about guards 449 | if let Some(_) = a.guard { 450 | unimplemented!(); 451 | } 452 | 453 | // Consume stuff in body 454 | visit::walk_expr(self, &a.body); 455 | } 456 | 457 | fn visit_block(&mut self, b: &'v Block) { 458 | visit::walk_block(self, b); 459 | 460 | if !self.returning { 461 | if let Some(ref e) = b.expr { 462 | let ty = ty::expr_ty(self.cx.tcx, e); 463 | if self.is_protected(ty) { 464 | // This value is returned, and thus we can consume it 465 | visit::walk_expr(self, e); 466 | } 467 | } 468 | } 469 | } 470 | } 471 | 472 | fn expr_to_deflocal<'tcx>(tcx: &'tcx ctxt, expr: &Expr) -> Option { 473 | let def = tcx.def_map.borrow().get(&expr.id).map(|&v| v); 474 | if let Some(PathResolution { base_def: DefLocal(id), .. }) = def { 475 | Some(id) 476 | } else { 477 | None 478 | } 479 | } 480 | 481 | 482 | #[plugin_registrar] 483 | pub fn plugin_registrar(reg: &mut Registry) { 484 | reg.register_lint_pass(box Pass); 485 | } 486 | -------------------------------------------------------------------------------- /tests/compile-fail/if-not-linear.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | #![deny(dropped_linear)] 8 | 9 | #[drop_protect] 10 | struct Foo; 11 | 12 | impl Foo { 13 | #[allow_drop="Foo"] 14 | fn close(self) { } 15 | } 16 | 17 | fn test1() { 18 | let f = Foo; //~ ERROR dropped var 19 | if false { return } else { f.close(); } 20 | } 21 | 22 | fn test2() { 23 | let f = Foo; //~ERROR dropped var 24 | if true { f.close(); } else { return } 25 | } 26 | 27 | fn test3() { 28 | let f = Foo; 29 | if true { f.close(); } 30 | //~^ ERROR If branch is not linear 31 | } 32 | 33 | fn test4() { 34 | let x = Foo; 35 | loop { 36 | if true { 37 | //~^ERROR If branches are not linear 38 | let y = Foo; 39 | } else { 40 | x.close(); 41 | break 42 | } 43 | } 44 | } 45 | 46 | fn test5() { 47 | let x = Foo; 48 | if true { 49 | //~^ ERROR If branches are not linear 50 | } else { 51 | x.close(); 52 | } 53 | } 54 | 55 | // #23 56 | fn test6() { 57 | let x = Foo; 58 | loop { 59 | if true { 60 | //~^ ERROR Match arms are not linear 61 | x.close(); 62 | break 63 | } else { 64 | let y = Foo; 65 | } 66 | } 67 | } 68 | 69 | fn main() {} 70 | -------------------------------------------------------------------------------- /tests/compile-fail/if-return.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | #![deny(dropped_linear)] 8 | 9 | #[drop_protect] 10 | struct Foo; 11 | 12 | impl Foo { 13 | #[allow_drop="Foo"] 14 | fn close(self) { } 15 | } 16 | 17 | fn main() { 18 | let f = Foo; //~ ERROR dropped var 19 | } 20 | -------------------------------------------------------------------------------- /tests/compile-fail/match-not-linear.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | #![deny(dropped_linear)] 8 | 9 | fn main() {} 10 | 11 | #[drop_protect] 12 | struct Foo; 13 | 14 | impl Foo { 15 | fn new() -> Foo { Foo } 16 | fn one_or_the_other(self, b: bool) -> Result { 17 | if b { Ok(self) } else { Err(self) } 18 | } 19 | #[allow_drop="Foo"] 20 | fn drop(self) {} 21 | } 22 | 23 | fn test1() { 24 | let foo = Foo::new(); 25 | match foo.one_or_the_other(true) { 26 | //~^ ERROR Match arms are not linear 27 | //~^^ ERROR Match arms are not linear 28 | Ok(foo) => foo.drop(), 29 | Err(foo) => {} 30 | } 31 | } 32 | 33 | fn test2() { 34 | let foo = Foo::new(); 35 | match foo.one_or_the_other(false) { 36 | //~^ ERROR Match arms are not linear 37 | Ok(foo) => {}, 38 | Err(foo) => foo.drop() 39 | } 40 | } 41 | 42 | fn test3() { 43 | let x = Foo; 44 | loop { 45 | match true { 46 | //~^ ERROR Match arms are not linear 47 | true => { 48 | let y = Foo; 49 | } 50 | false => { 51 | x.drop(); 52 | break 53 | } 54 | } 55 | } 56 | } 57 | 58 | // #23 59 | fn test4() { 60 | let x = Foo; 61 | loop { 62 | match true { 63 | //~^ ERROR Match arms are not linear 64 | true => { 65 | x.drop(); 66 | break 67 | } 68 | false => { 69 | let y = Foo; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/compile-fail/non-linear-for-loop.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | #![deny(dropped_linear)] 8 | 9 | #[drop_protect] 10 | struct Foo; 11 | 12 | impl Foo { 13 | #[allow_drop="Foo"] 14 | fn close(self) { } 15 | } 16 | 17 | fn test1() { 18 | let f = vec!(Foo); 19 | for i in f { 20 | //~^ ERROR Non-linear for loop 21 | if true { 22 | break; 23 | } 24 | i.close(); 25 | } 26 | } 27 | 28 | fn main() {} 29 | -------------------------------------------------------------------------------- /tests/compile-fail/simple-if.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | #![deny(dropped_linear)] 8 | 9 | #[drop_protect] 10 | struct Foo; 11 | 12 | impl Foo { 13 | #[allow_drop="Foo"] 14 | fn close(self) { } 15 | } 16 | 17 | fn test1() { 18 | let f = Foo; 19 | if false { 20 | //~^ ERROR If branch is not linear 21 | f.close(); 22 | } 23 | } 24 | 25 | fn test2() { 26 | let f = Foo; //~ ERROR dropped var 27 | if false { 28 | //~^ ERROR If branches are not linear 29 | f.close(); 30 | } else { 31 | 32 | } 33 | } 34 | 35 | fn test3() { 36 | let f = Foo; 37 | if false { 38 | //~^ ERROR If branches are not linear 39 | } else { 40 | f.close(); 41 | } 42 | } 43 | 44 | fn main() {} 45 | -------------------------------------------------------------------------------- /tests/compile-fail/simple-loop.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #![deny(dropped_linear)] 9 | 10 | #[drop_protect] 11 | struct Foo; 12 | 13 | #[allow_drop="Foo"] 14 | fn close(_: Foo) { } 15 | 16 | fn main() { 17 | loop { 18 | let y = Foo; //~ ERROR dropped var 19 | } 20 | } 21 | 22 | fn test2() { 23 | let x = Foo; 24 | loop { 25 | match 2 { 26 | 1 => { 27 | close(x); 28 | break; 29 | } 30 | 2 => { 31 | break; //~ERROR Non-linear break 32 | } 33 | _ => { 34 | 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/compile-fail/unused.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #![deny(dropped_linear)] 9 | 10 | #[drop_protect] 11 | struct Foo; 12 | 13 | fn main() { 14 | let foo = Foo; //~ ERROR dropped var 15 | } 16 | -------------------------------------------------------------------------------- /tests/compile_test.rs: -------------------------------------------------------------------------------- 1 | extern crate compiletest_rs as compiletest; 2 | 3 | use std::path::PathBuf; 4 | 5 | fn run_mode(mode: &'static str) { 6 | 7 | let mut config = compiletest::default_config(); 8 | let cfg_mode = mode.parse().ok().expect("Invalid mode"); 9 | config.target_rustcflags = Some("-L target/debug/".to_string()); 10 | 11 | config.mode = cfg_mode; 12 | config.src_base = PathBuf::from(format!("tests/{}", mode)); 13 | 14 | compiletest::run_tests(&config); 15 | } 16 | 17 | #[test] 18 | fn compile_test() { 19 | run_mode("compile-fail"); 20 | } 21 | -------------------------------------------------------------------------------- /tests/if-return.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | impl Foo { 12 | #[allow_drop="Foo"] 13 | fn close(self) { } 14 | } 15 | 16 | #[test] 17 | fn if_return() { 18 | let f = Foo; 19 | 20 | if true { 21 | f.close(); 22 | return; 23 | } 24 | 25 | f.close(); 26 | } 27 | 28 | #[test] 29 | fn if_return_else() { 30 | let f = Foo; 31 | 32 | if true { 33 | f.close(); 34 | return; 35 | } else { 36 | // pass 37 | } 38 | 39 | f.close(); 40 | } 41 | 42 | #[test] 43 | fn if_return_else_return() { 44 | let f = Foo; 45 | 46 | if true { 47 | f.close(); 48 | return; 49 | } else { 50 | f.close(); 51 | return; 52 | } 53 | } 54 | 55 | #[test] 56 | fn if_else_return() { 57 | let f = Foo; 58 | 59 | if true { 60 | // pass 61 | } else { 62 | f.close(); 63 | return; 64 | } 65 | 66 | f.close(); 67 | } 68 | 69 | #[test] 70 | fn if_else() { 71 | let f = Foo; 72 | if true { 73 | // pass 74 | } else { 75 | // pass 76 | } 77 | 78 | f.close(); 79 | return; 80 | } 81 | 82 | 83 | #[test] 84 | fn if_return1() { 85 | let foo = Foo; 86 | 87 | if true { 88 | foo.close(); 89 | return; 90 | } else { 91 | } 92 | foo.close(); 93 | } 94 | 95 | 96 | #[test] 97 | fn if_return2() { 98 | let foo = Foo; 99 | 100 | if true { 101 | foo.close(); 102 | return; 103 | } 104 | foo.close(); 105 | } 106 | -------------------------------------------------------------------------------- /tests/loop-break.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | impl Foo { 12 | #[allow_drop="Foo"] 13 | fn close(self) { } 14 | } 15 | 16 | #[test] 17 | fn loop_break() { 18 | let foo = Foo; 19 | 20 | loop { 21 | foo.close(); 22 | break; 23 | } 24 | } 25 | 26 | #[test] 27 | fn loop_if_break() { 28 | let foo = Foo; 29 | 30 | loop { 31 | if true { 32 | foo.close(); 33 | break; 34 | } 35 | 36 | foo.close(); 37 | break; 38 | } 39 | } 40 | 41 | #[test] 42 | fn loop_match_break_else_break() { 43 | let foo = Foo; 44 | 45 | loop { 46 | match true { 47 | true => { 48 | foo.close(); 49 | return; 50 | } 51 | _ => { 52 | break; 53 | } 54 | } 55 | } 56 | foo.close(); 57 | } 58 | 59 | #[test] 60 | fn loop_if_break_else() { 61 | let foo = Foo; 62 | 63 | loop { 64 | if true { 65 | foo.close(); 66 | break; 67 | } else { 68 | foo.close(); 69 | } 70 | break; 71 | } 72 | } 73 | 74 | #[test] 75 | fn loop_if_break_else_break() { 76 | let foo = Foo; 77 | 78 | loop { 79 | if true { 80 | foo.close(); 81 | break; 82 | } else { 83 | foo.close(); 84 | break; 85 | } 86 | } 87 | } 88 | 89 | #[test] 90 | fn loop_if_break_else_break2() { 91 | let foo = Foo; 92 | 93 | loop { 94 | if true { 95 | foo.close(); 96 | break; 97 | } else { 98 | } 99 | foo.close(); 100 | break; 101 | } 102 | } 103 | 104 | #[test] 105 | fn loop_count_test() { 106 | let c = Foo; 107 | let mut n = 0; 108 | loop { 109 | if n < 10 { 110 | n += 1; 111 | } else { 112 | c.close(); 113 | break; 114 | } 115 | } 116 | } 117 | 118 | #[test] 119 | fn loop_count_test2() { 120 | let c = Foo; 121 | let mut n = 0; 122 | loop { 123 | if n >= 10 { 124 | c.close(); 125 | break; 126 | } else { 127 | n += 1; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /tests/loop-continue.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | impl Foo { 12 | #[allow_drop="Foo"] 13 | fn close(self) { } 14 | } 15 | 16 | #[test] 17 | fn loop_continue1() { 18 | let foo = Foo; 19 | 20 | loop { 21 | if false { 22 | continue; 23 | } else { 24 | foo.close(); 25 | break; 26 | } 27 | } 28 | } 29 | 30 | 31 | #[test] 32 | fn loop_continue2() { 33 | let foo = Foo; 34 | 35 | loop { 36 | if false { 37 | continue; 38 | } else { 39 | foo.close(); 40 | } 41 | break; 42 | } 43 | } 44 | 45 | #[test] 46 | fn loop_continue3() { 47 | let foo = Foo; 48 | 49 | loop { 50 | if false { 51 | continue; 52 | } else { 53 | } 54 | foo.close(); 55 | break; 56 | } 57 | } 58 | 59 | #[test] 60 | fn loop_continue4() { 61 | let foo = Foo; 62 | 63 | loop { 64 | if false { 65 | continue; 66 | } 67 | foo.close(); 68 | break; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/match-return.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![feature(custom_attribute)] 3 | #![plugin(humpty_dumpty)] 4 | #![allow(unused_attributes)] 5 | #![allow(unused_variables)] 6 | #![allow(dead_code)] 7 | 8 | #[drop_protect] 9 | struct Foo; 10 | 11 | impl Foo { 12 | #[allow_drop="Foo"] 13 | fn close(self) { } 14 | } 15 | 16 | #[test] 17 | fn match_return() { 18 | let f = Foo; 19 | 20 | match true { 21 | true => { 22 | f.close(); 23 | return; 24 | } 25 | _ => { 26 | } 27 | } 28 | 29 | f.close(); 30 | } 31 | 32 | #[test] 33 | fn match_all_return() { 34 | let f = Foo; 35 | 36 | match true { 37 | true => { 38 | f.close(); 39 | return; 40 | } 41 | _ => { 42 | f.close(); 43 | return; 44 | } 45 | } 46 | } 47 | 48 | 49 | #[test] 50 | fn match_one_return() { 51 | let f = Foo; 52 | 53 | match true { 54 | true => { 55 | f.close(); 56 | return; 57 | } 58 | _ => { 59 | f.close(); 60 | } 61 | } 62 | } 63 | 64 | #[test] 65 | fn match_second_return() { 66 | let f = Foo; 67 | 68 | match true { 69 | true => { 70 | f.close(); 71 | } 72 | _ => { 73 | f.close(); 74 | return; 75 | } 76 | } 77 | } 78 | 79 | #[test] 80 | fn match_two_return() { 81 | let f = Foo; 82 | 83 | match 1 { 84 | 0 => { 85 | f.close(); 86 | return; 87 | } 88 | 1 => { 89 | f.close(); 90 | return; 91 | } 92 | _ => { 93 | 94 | } 95 | } 96 | f.close(); 97 | } 98 | --------------------------------------------------------------------------------