File: WakelockReleasedDangerously.md

package info (click to toggle)
error-prone-java 2.18.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 23,204 kB
  • sloc: java: 222,992; xml: 1,319; sh: 25; makefile: 7
file content (41 lines) | stat: -rw-r--r-- 1,844 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
If `release()` is called on an already-released `WakeLock`, a `RuntimeException`
is thrown, which can crash your app if not handled properly.

This may happen even in cases where you don't expect it. For example:

```java
wakelock.acquire(TIMEOUT_MS);
...
if (wakelock.isHeld()) {
  wakelock.release();
}
```

Looks fine, right? *Nope.* Since this wakelock was acquired with a timeout, the
timeout may expire and the wakelock may be released by the system, even between
the `isHeld()` check and `release()`. If the system releases the wakelock, and
the programmer calls `release()` again, a `RuntimeException` will be thrown, and
the app may crash.

Note: The suggested fix removes checks for `wakelock.isHeld()`, for this very
reason. This pattern - checking if a wakelock with timeout is held before
releasing - is subject to a race condition, so leaving this call in the code is
ineffectual and misleading.

To prevent crashes like this, `WakeLock`s acquired with timeout should be
released *only in a `try/catch(RuntimeException)` block*.

This does not hold for `WakeLock`s that are
[not reference counted](https://android.googlesource.com/platform/frameworks/base/+/nougat-release/core/java/android/os/PowerManager.java#1267).
`WakeLock`s are reference counted by default, but if
`setReferenceCounted(false)` has been called on the `WakeLock` in question, the
OS does not check whether the `WakeLock` has been released too many times, and
no `RuntimeException` is thrown.

This check will flag any call of any overload of `wakelock.release()` that is:

*   not in a try block that has a clause to catch `RuntimeException`
*   called on a WakeLock instance that is:
    *   acquired with a timeout in the same class
    *   and is reference counted (i.e., has not had the default changed via
        `wakelock.setReferenceCounted(false)`)