Code change:
As far as I understand, the str types have a pretty strong assumption that they contain valid UTF-8. Any method/function that could break this (such as ) is marked as unsafe. However, it is possible to assign random bytes in the middle of a ~str, breaking the UTF-8 invariant: I think that such assignments should only be allowed in unsafe code. Update: should also be unsafe, IMO.
I had no idea str even allowed index-assign.
forbidding it entirely could also fix the issue, but might be overkill.
Hmm... This could be tricker than just forbidding assignment because this code compiles as well
I suppose we could also forbid (in safe code) to borrow mutable references to individual bytes in a string. Similarly, I believe that should be unsafe (but does not need to be.)
I think we should just forbid index-assign and borrowing mutable references. It can still be done through unsafe methods, but doesn't need to be in the language as a feature. The only language feature we have is raw pointer dereferencing so it's nice to keep it simple.
Nominating for the backwards compatible milestone.
doesn't need to be unsafe because it passes you a raw pointer, which is unusable without
Accepted for well-defined. (Also: document what "unsafe" means, which is a separate issue.)

Related discussion:
text: UnsafeFn => {} } } <ins> fn check_str_index(&mut self, e: @ast::Expr) { let base_type = match e.node { ast::ExprIndex(_, base, _) => ty::node_id_to_type(self.tcx, base.id), _ => return }; debug2!("effect: checking index with base type {}", ppaux::ty_to_str(self.tcx, base_type)); match ty::get(base_type).sty { ty::ty_estr(*) => { self.tcx.sess.span_err(e.span, "modification of string types is not allowed"); } _ => {} } } </ins> } impl Visitor<()> for EffectCheckVisitor { <del> fn visit_fn(&mut self, fn_kind:&fn_kind, fn_decl:&fn_decl, block:&Block, span:Span, node_id:NodeId, _:()) { let (is_item_fn, is_unsafe_fn) = match *fn_kind { fk_item_fn(_, _, purity, _) => (true, purity == unsafe_fn), fk_method(_, _, method) => (true, method.purity == unsafe_fn), _ => (false, false), }; let old_unsafe_context = self.context.unsafe_context; if is_unsafe_fn { self.context.unsafe_context = UnsafeFn } else if is_item_fn { self.context.unsafe_context = SafeContext } </del> <ins> fn visit_fn(&mut self, fn_kind: &visit::fn_kind, fn_decl: &ast::fn_decl, block: &ast::Block, span: Span, node_id: ast::NodeId, _:()) { let (is_item_fn, is_unsafe_fn) = match *fn_kind { visit::fk_item_fn(_, _, purity, _) => (true, purity == ast::unsafe_fn), visit::fk_method(_, _, method) => (true, method.purity == ast::unsafe_fn), _ => (false, false), }; let old_unsafe_context = self.unsafe_context; if is_unsafe_fn { self.unsafe_context = UnsafeFn } else if is_item_fn { self.unsafe_context = SafeContext } </ins> <del> visit::walk_fn(self, fn_kind, fn_decl, block, span, node_id, ()); </del> <ins> visit::walk_fn(self, fn_kind, fn_decl, block, span, node_id, ()); </ins> <del> self.context.unsafe_context = old_unsafe_context </del> <ins> self.unsafe_context = old_unsafe_context </ins> } <del> fn visit_block(&mut self, block:&Block, _:()) { let old_unsafe_context = self.context.unsafe_context; let is_unsafe = match block.rules { ast::UnsafeBlock(*) => true, ast::DefaultBlock => false }; if is_unsafe && self.context.unsafe_context == SafeContext { self.context.unsafe_context = UnsafeBlock(block.id) } </del> <ins> fn visit_block(&mut self, block: &ast::Block, _:()) { let old_unsafe_context = self.unsafe_context; let is_unsafe = match block.rules { ast::UnsafeBlock(*) => true, ast::DefaultBlock => false }; if is_unsafe && self.unsafe_context == SafeContext { self.unsafe_context = UnsafeBlock(block.id) } </ins> <del> visit::walk_block(self, block, ()); </del> <ins> visit::walk_block(self, block, ()); </ins> <del> self.context.unsafe_context = old_unsafe_context </del> <ins> self.unsafe_context = old_unsafe_context </ins> } <del> fn visit_expr(&mut self, expr:@Expr, _:()) { match expr.node { ExprMethodCall(callee_id, _, _, _, _, _) => { let base_type = ty::node_id_to_type(self.tcx, callee_id); debug2!("effect: method call case, base type is {}", ppaux::ty_to_str(self.tcx, base_type)); if type_is_unsafe_function(base_type) { self.require_unsafe(expr.span, "invocation of unsafe method") } </del> <ins> fn visit_expr(&mut self, expr: @ast::Expr, _:()) { match expr.node { ast::ExprMethodCall(callee_id, _, _, _, _, _) => { let base_type = ty::node_id_to_type(self.tcx, callee_id); debug2!("effect: method call case, base type is {}", ppaux::ty_to_str(self.tcx, base_type)); if type_is_unsafe_function(base_type) { self.require_unsafe(expr.span, "invocation of unsafe method") </ins> } <del> ExprCall(base, _, _) => { let base_type = ty::node_id_to_type(self.tcx, base.id); debug2!("effect: call case, base type is {}", ppaux::ty_to_str(self.tcx, base_type)); if type_is_unsafe_function(base_type) { self.require_unsafe(expr.span, "call to unsafe function") } </del> <ins> } ast::ExprCall(base, _, _) => { let base_type = ty::node_id_to_type(self.tcx, base.id); debug2!("effect: call case, base type is {}", ppaux::ty_to_str(self.tcx, base_type)); if type_is_unsafe_function(base_type) { self.require_unsafe(expr.span, "call to unsafe function") </ins> } <del> ExprUnary(_, UnDeref, base) => { let base_type = ty::node_id_to_type(self.tcx, base.id); debug2!("effect: unary case, base type is {}", ppaux::ty_to_str(self.tcx, base_type)); match ty::get(base_type).sty { ty_ptr(_) => { self.require_unsafe(expr.span, "dereference of unsafe pointer") } _ => {} </del> <ins> } ast::ExprUnary(_, ast::UnDeref, base) => { let base_type = ty::node_id_to_type(self.tcx, base.id); debug2!("effect: unary case, base type is {}", ppaux::ty_to_str(self.tcx, base_type)); match ty::get(base_type).sty { ty::ty_ptr(_) => { self.require_unsafe(expr.span, "dereference of unsafe pointer") </ins> } <ins> _ => {} </ins> } <del> ExprInlineAsm(*) => { self.require_unsafe(expr.span, "use of inline assembly") } ExprPath(*) => { match ty::resolve_expr(self.tcx, expr) { ast::DefStatic(_, true) => { self.require_unsafe(expr.span, "use of mutable static") } _ => {} </del> <ins> } ast::ExprAssign(base, _) | ast::ExprAssignOp(_, _, base, _) => { self.check_str_index(base); } ast::ExprAddrOf(ast::MutMutable, base) => { self.check_str_index(base); } ast::ExprInlineAsm(*) => { self.require_unsafe(expr.span, "use of inline assembly") } ast::ExprPath(*) => { match ty::resolve_expr(self.tcx, expr) { ast::DefStatic(_, true) => { self.require_unsafe(expr.span, "use of mutable static") </ins> } <ins> _ => {} </ins> } <del> _ => {} </del> } <ins> _ => {} } </ins> <del> visit::walk_expr(self, expr, ()); </del> <ins> visit::walk_expr(self, expr, ()); </ins> } } pub fn check_crate(tcx: ty::ctxt, method_map: method_map, crate: &ast::Crate) { <del> let context = @mut Context { method_map: method_map, unsafe_context: SafeContext, }; </del> let mut visitor = EffectCheckVisitor { tcx: tcx, <del> context: context, </del> <ins> method_map: method_map, unsafe_context: SafeContext, </ins> }; visit::walk_crate(&mut visitor, crate, ());