-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nested return expressions can cause drop to run on incorrect values. #15763
Comments
Hi, I have encountered this bug in the wild. Here it is causing a segfault: use std::io::IoResult;
use std::io::{standard_error, InvalidInput};
fn build_vec_fail() -> IoResult<Vec<uint>> {
Err(standard_error(InvalidInput))
}
fn build_vec() -> IoResult<Vec<uint>> {
Ok(vec![])
}
struct MyStruct {
field1: Vec<uint>,
field2: Vec<uint>
}
fn real_main() -> IoResult<MyStruct> {
// This is fine if we assign it to a variable then return the
// variable. As written this segfaults.
Ok(MyStruct {
field1: try!(build_vec()),
field2: try!(build_vec_fail()),
})
}
fn main() { real_main(); } A simple workaround is to assign the return value of Edit: Oops, I posted the wrong source code! Fixed. |
This bug causes code like extern crate serialize;
use serialize::json;
#[deriving(Decodable)]
struct Foo {
field1: String,
field2: String
}
fn main() {
let data = r##"
{
"field1": "something",
"non_existing_field": "something_else"
}
"##;
let _ = json::decode::<Foo>(data.as_slice());
} to segfault. It expands to the following, which contains a #![feature(phase)]
#![no_std]
#![feature(globs)]
#[phase(plugin, link)]
extern crate std;
extern crate native;
extern crate serialize;
use std::prelude::*;
use serialize::json;
struct Foo {
field1: String,
field2: String,
}
#[automatically_derived]
impl <__D: ::serialize::Decoder<__E>, __E> ::serialize::Decodable<__D, __E>
for Foo {
fn decode(__arg_0: &mut __D) -> ::std::result::Result<Foo, __E> {
__arg_0.read_struct("Foo", 2u,
|_d|
::std::result::Ok(Foo{field1:
match _d.read_struct_field("field1",
0u,
|_d|
::serialize::Decodable::decode(_d))
{
Ok(__try_var) =>
__try_var,
Err(__try_var)
=>
return Err(__try_var)
},
field2:
match _d.read_struct_field("field2",
1u,
|_d|
::serialize::Decodable::decode(_d))
{
Ok(__try_var) =>
__try_var,
Err(__try_var)
=>
return Err(__try_var)
},}))
}
}
fn main() {
let data =
r##"
{
"field1": "something",
"non_existing_field": "something_else"
}
"##;
let _ = json::decode::<Foo>(data.as_slice());
} |
Nominating, decoders are pretty important! |
Note that this is not P-backcompat-lang. |
Assigning P-high, 1.0 milestone. |
Same story with tuples: use std::io::{IoResult, InvalidInput, standard_error};
fn deserialize() -> IoResult<(Box<uint>, ())> {
Ok((try!(Ok(box 0)),
try!(Err(standard_error(InvalidInput)))))
}
fn main() {
println!("{}", deserialize());
} also segfaults. Edit: Here is a maybe more enlightening test case: struct MyStruct(Box<int>);
impl Drop for MyStruct {
fn drop(&mut self) {
let &MyStruct(ref n) = self;
println!("dropping {}", n);
}
}
fn foo() -> Result<(MyStruct, MyStruct), MyStruct> {
Ok((MyStruct(box 0), try!(Err(MyStruct(box 1)))))
}
fn main() {
foo();
} This outputs |
The `repr_c_implicit_padding` lint checks structures marked a u8 followed by a u64, which would require 7 bytes of padding to get the u64 aligned correctly. The reason for this is that in the BSD socket code, there is some shifty casting between structs which results in data being stored in padding bytes. Since the LLVM Load instruction does not copy padding, this can result in data loss. (This is also a problem in C, but since structs are rarely copied by value, instead using pointers and memcpy(), it is not as serious a problem.) We therefore need explicit padding for the socket structures, and this lint will make sure we get it. This is necessary because the recent pull rust-lang#16081 which is a fix for rust-lang#15763, changes the return value semantics to involve a Load. Without padding fixes, this breaks the Tcp code.
…ut-ptr instead The BSD socket code does some cast tricks with the `libc::sockaddr*` structs, which causes useful data to be stored in struct padding. Since Load/Store instructions do not copy struct padding, this makes these structures dangerous to pass or return by value. In particular, rust-lang#15763 changes return semantics so that a Load instruction is used, breaking the TCP code. Once this PR is merged, that one should merge without error.
@luqmana can you rebase and retry this one? It should pass the tests with the recent TCP changes. |
@apoelstra yup! works now! |
rust-lang#16081 fixed an issue where a nested return statement would cause incorrect behaviour due to the inner return writing over the return stack slot that had already been written too. However, the check was very broad and picked many cases that wouldn't ever be affected by this issue. As a result, the number of allocas increased dramatically and therefore stack-size increased. LLVM is not able to remove all of the extraneous allocas. Any code that had multiple return values in a compound expression at the end of a function (including loops) would be hit by the issue. The check now uses a control-flow graph to only consider the case when the inner return is executed conditionally. By itself, this narrowed definition causes rust-lang#15763 to return, so the control-flow graph is also used to avoid passing the return slot as a destination when the result won't be used. This change allows the stack-size of the main rustc task to be reduced to 8MB from 32MB.
Came across this while working on inlining enum constructors but I could reproduce on master as well:
When compiled and run this will print out:
This is wrong since the
Foo
we're actually dropping at that point (before returning infoo
) is the one with it's field set to 23. The problem is that we translate return expressions to write directly into the return pointer (either a hidden outpointer or a temporary alloca which we'll return from). So with the outer return we start writing out the struct but then we encounter another return in one of the fields and overwrite the previous result so now when we pass a pointer (which is just some offset into the return pointer) to the drop glue for what should be the initialFoo
we're actually calling drop on the second one.The text was updated successfully, but these errors were encountered: