rust/compiler
Frank Steffahn 8f8689fb31 Improve unused_unsafe lint
Main motivation: Fixes some issues with the current behavior. This PR is
more-or-less completely re-implementing the unused_unsafe lint; it’s also only
done in the MIR-version of the lint, the set of tests for the `-Zthir-unsafeck`
version no longer succeeds (and is thus disabled, see `lint-unused-unsafe.rs`).

On current nightly,
```rs
unsafe fn unsf() {}

fn inner_ignored() {
    unsafe {
        #[allow(unused_unsafe)]
        unsafe {
            unsf()
        }
    }
}
```

doesn’t create any warnings. This situation is not unrealistic to come by, the
inner `unsafe` block could e.g. come from a macro. Actually, this PR even
includes removal of one unused `unsafe` in the standard library that was missed
in a similar situation. (The inner `unsafe` coming from an external macro hides
    the warning, too.)

The reason behind this problem is how the check currently works:
* While generating MIR, it already skips nested unsafe blocks (i.e. unsafe
  nested in other unsafe) so that the inner one is always the one considered
  unused
* To differentiate the cases of no unsafe operations inside the `unsafe` vs.
  a surrounding `unsafe` block, there’s some ad-hoc magic walking up the HIR to
  look for surrounding used `unsafe` blocks.

There’s a lot of problems with this approach besides the one presented above.
E.g. the MIR-building uses checks for `unsafe_op_in_unsafe_fn` lint to decide
early whether or not `unsafe` blocks in an `unsafe fn` are redundant and ought
to be removed.
```rs
unsafe fn granular_disallow_op_in_unsafe_fn() {
    unsafe {
        #[deny(unsafe_op_in_unsafe_fn)]
        {
            unsf();
        }
    }
}
```
```
error: call to unsafe function is unsafe and requires unsafe block (error E0133)
  --> src/main.rs:13:13
   |
13 |             unsf();
   |             ^^^^^^ call to unsafe function
   |
note: the lint level is defined here
  --> src/main.rs:11:16
   |
11 |         #[deny(unsafe_op_in_unsafe_fn)]
   |                ^^^^^^^^^^^^^^^^^^^^^^
   = note: consult the function's documentation for information on how to avoid undefined behavior

warning: unnecessary `unsafe` block
  --> src/main.rs:10:5
   |
9  | unsafe fn granular_disallow_op_in_unsafe_fn() {
   | --------------------------------------------- because it's nested under this `unsafe` fn
10 |     unsafe {
   |     ^^^^^^ unnecessary `unsafe` block
   |
   = note: `#[warn(unused_unsafe)]` on by default

```
Here, the intermediate `unsafe` was ignored, even though it contains a unsafe
operation that is not allowed to happen in an `unsafe fn` without an additional `unsafe` block.

Also closures were problematic and the workaround/algorithms used on current
nightly didn’t work properly. (I skipped trying to fully understand what it was
supposed to do, because this PR uses a completely different approach.)
```rs
fn nested() {
    unsafe {
        unsafe { unsf() }
    }
}
```
```
warning: unnecessary `unsafe` block
  --> src/main.rs:10:9
   |
9  |     unsafe {
   |     ------ because it's nested under this `unsafe` block
10 |         unsafe { unsf() }
   |         ^^^^^^ unnecessary `unsafe` block
   |
   = note: `#[warn(unused_unsafe)]` on by default
```

vs

```rs
fn nested() {
    let _ = || unsafe {
        let _ = || unsafe { unsf() };
    };
}
```
```
warning: unnecessary `unsafe` block
 --> src/main.rs:9:16
  |
9 |     let _ = || unsafe {
  |                ^^^^^^ unnecessary `unsafe` block
  |
  = note: `#[warn(unused_unsafe)]` on by default

warning: unnecessary `unsafe` block
  --> src/main.rs:10:20
   |
10 |         let _ = || unsafe { unsf() };
   |                    ^^^^^^ unnecessary `unsafe` block
```

*note that this warning kind-of suggests that **both** unsafe blocks are redundant*

--------------------------------------------------------------------------------

I also dislike the fact that it always suggests keeping the outermost `unsafe`.
E.g. for
```rs
fn granularity() {
    unsafe {
        unsafe { unsf() }
        unsafe { unsf() }
        unsafe { unsf() }
    }
}
```
I prefer if `rustc` suggests removing the more-course outer-level `unsafe`
instead of the fine-grained inner `unsafe` blocks, which it currently does on nightly:
```
warning: unnecessary `unsafe` block
  --> src/main.rs:10:9
   |
9  |     unsafe {
   |     ------ because it's nested under this `unsafe` block
10 |         unsafe { unsf() }
   |         ^^^^^^ unnecessary `unsafe` block
   |
   = note: `#[warn(unused_unsafe)]` on by default

warning: unnecessary `unsafe` block
  --> src/main.rs:11:9
   |
9  |     unsafe {
   |     ------ because it's nested under this `unsafe` block
10 |         unsafe { unsf() }
11 |         unsafe { unsf() }
   |         ^^^^^^ unnecessary `unsafe` block

warning: unnecessary `unsafe` block
  --> src/main.rs:12:9
   |
9  |     unsafe {
   |     ------ because it's nested under this `unsafe` block
...
12 |         unsafe { unsf() }
   |         ^^^^^^ unnecessary `unsafe` block
```

--------------------------------------------------------------------------------

Needless to say, this PR addresses all these points. For context, as far as my
understanding goes, the main advantage of skipping inner unsafe blocks was that
a test case like
```rs
fn top_level_used() {
    unsafe {
        unsf();
        unsafe { unsf() }
        unsafe { unsf() }
        unsafe { unsf() }
    }
}
```
should generate some warning because there’s redundant nested `unsafe`, however
every single `unsafe` block _does_ contain some statement that uses it. Of course
this PR doesn’t aim change the warnings on this kind of code example, because
the current behavior, warning on all the inner `unsafe` blocks, makes sense in this case.

As mentioned, during MIR building all the unsafe blocks *are* kept now, and usage
is attributed to them. The way to still generate a warning like
```
warning: unnecessary `unsafe` block
  --> src/main.rs:11:9
   |
9  |     unsafe {
   |     ------ because it's nested under this `unsafe` block
10 |         unsf();
11 |         unsafe { unsf() }
   |         ^^^^^^ unnecessary `unsafe` block
   |
   = note: `#[warn(unused_unsafe)]` on by default

warning: unnecessary `unsafe` block
  --> src/main.rs:12:9
   |
9  |     unsafe {
   |     ------ because it's nested under this `unsafe` block
...
12 |         unsafe { unsf() }
   |         ^^^^^^ unnecessary `unsafe` block

warning: unnecessary `unsafe` block
  --> src/main.rs:13:9
   |
9  |     unsafe {
   |     ------ because it's nested under this `unsafe` block
...
13 |         unsafe { unsf() }
   |         ^^^^^^ unnecessary `unsafe` block
```

in this case is by emitting a `unused_unsafe` warning for all of the `unsafe`
blocks that are _within a **used** unsafe block_.

The previous code had a little HIR traversal already anyways to collect a set of
all the unsafe blocks (in order to afterwards determine which ones are unused
afterwards). This PR uses such a traversal to do additional things including logic
like _always_ warn for an `unsafe` block that’s inside of another **used**
unsafe block. The traversal is expanded to include nested closures in the same go,
this simplifies a lot of things.

The whole logic around `unsafe_op_in_unsafe_fn` is a little complicated, there’s
some test cases of corner-cases in this PR. (The implementation involves
differentiating between whether a used unsafe block was used exclusively by
operations where `allow(unsafe_op_in_unsafe_fn)` was active.) The main goal was
to make sure that code should compile successfully if all the `unused_unsafe`-warnings
are addressed _simultaneously_ (by removing the respective `unsafe` blocks)
no matter how complicated the patterns of `unsafe_op_in_unsafe_fn` being
disallowed and allowed throughout the function are.

--------------------------------------------------------------------------------

One noteworthy design decision I took here: An `unsafe` block
with `allow(unused_unsafe)` **is considered used** for the purposes of
linting about redundant contained unsafe blocks. So while
```rs

fn granularity() {
    unsafe { //~ ERROR: unnecessary `unsafe` block
        unsafe { unsf() }
        unsafe { unsf() }
        unsafe { unsf() }
    }
}
```
warns for the outer `unsafe` block,
```rs

fn top_level_ignored() {
    #[allow(unused_unsafe)]
    unsafe {
        #[deny(unused_unsafe)]
        {
            unsafe { unsf() } //~ ERROR: unnecessary `unsafe` block
            unsafe { unsf() } //~ ERROR: unnecessary `unsafe` block
            unsafe { unsf() } //~ ERROR: unnecessary `unsafe` block
        }
    }
}
```
warns on the inner ones.
2022-02-20 21:00:12 +01:00
..
rustc
rustc_apfloat
rustc_arena
rustc_ast Rollup merge of #93634 - matthiaskrgr:clippy_complexity_jan_2022, r=oli-obk 2022-02-18 16:23:33 +01:00
rustc_ast_lowering Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_ast_passes Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_ast_pretty Pretty print ItemKind::Use in rustfmt style 2022-02-07 21:51:05 -08:00
rustc_attr Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_borrowck Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_builtin_macros Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_codegen_cranelift Overhaul Const. 2022-02-15 16:19:59 +11:00
rustc_codegen_gcc Unconditionally update symbols 2022-02-10 18:27:18 +01:00
rustc_codegen_llvm Improve unused_unsafe lint 2022-02-20 21:00:12 +01:00
rustc_codegen_ssa Auto merge of #93816 - bjorn3:rlib_metadata_first, r=nagisa 2022-02-20 11:32:40 +00:00
rustc_const_eval Fix pretty printing of enums without variants 2022-02-19 17:10:11 +01:00
rustc_data_structures Auto merge of #93934 - rusticstuff:inline_ensure_sufficient_stack, r=estebank 2022-02-20 15:10:19 +00:00
rustc_driver Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_error_codes Revert "Auto merge of #91403 - cjgillot:inherit-async, r=oli-obk" 2022-02-17 16:00:04 +00:00
rustc_errors Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_expand Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_feature Rollup merge of #93658 - cchiw:issue-77443-fix, r=joshtriplett 2022-02-19 06:45:29 +01:00
rustc_fs_util
rustc_graphviz
rustc_hir Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_hir_pretty Rollup merge of #93746 - cjgillot:nodefii, r=nikomatsakis 2022-02-09 14:12:22 +09:00
rustc_incremental Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_index Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_infer Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_interface Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_lexer
rustc_lint Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_lint_defs Implement --check-cfg option (RFC 3013) 2022-02-16 13:03:12 +01:00
rustc_llvm Rollup merge of #91675 - ivanloz:memtagsan, r=nagisa 2022-02-18 23:23:03 +01:00
rustc_log
rustc_macros
rustc_metadata Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_middle Improve unused_unsafe lint 2022-02-20 21:00:12 +01:00
rustc_mir_build Improve unused_unsafe lint 2022-02-20 21:00:12 +01:00
rustc_mir_dataflow Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_mir_transform Improve unused_unsafe lint 2022-02-20 21:00:12 +01:00
rustc_monomorphize Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_parse Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_parse_format Correctly mark the span of captured arguments in format_args!() 2022-02-16 07:34:06 +00:00
rustc_passes Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_plugin_impl
rustc_privacy Overhaul Const. 2022-02-15 16:19:59 +11:00
rustc_query_impl Overhaul Const. 2022-02-15 16:19:59 +11:00
rustc_query_system Revert "Auto merge of #92007 - oli-obk:lazy_tait2, r=nikomatsakis" 2022-02-11 07:18:06 +00:00
rustc_resolve Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_save_analysis Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_serialize Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_session Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_span Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_symbol_mangling Overhaul Const. 2022-02-15 16:19:59 +11:00
rustc_target Rollup merge of #94146 - est31:let_else, r=cjgillot 2022-02-20 00:37:34 +01:00
rustc_trait_selection Rollup merge of #93892 - compiler-errors:issue-92917, r=jackh726,nikomatsakis 2022-02-18 23:23:09 +01:00
rustc_traits Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_ty_utils Adopt let else in more places 2022-02-19 17:27:43 +01:00
rustc_type_ir Inline UnifyKey::index and UnifyKey::from_index 2022-02-15 19:07:06 +01:00
rustc_typeck Rollup merge of #94142 - est31:let_else_typeck, r=oli-obk 2022-02-20 00:37:33 +01:00