Description
See http://go/flutter-lints-dd for overall context. Also see #40429 for a separate attempt to add a generic Unsafe
annotation which is how this proposal sprung to life.
tl;dr
We would like to define a new lint avoid_unsafe_apis
where the definition of unsafe APIs is domain specific and can be passed in as input to the analyzer.
Proposal
avoid_unsafe_apis lint would read some configuration that would contain a list of symbols and the rationale for considering them banned. We first define a class to represent Unsafe APIs and the rationale.
class Unsafe {
// The fully qualified name of the symbol
final String symbol;
// The reason why this symbol is considered unsafe
final String reason;
const Unsafe(this.symbol, this.reason);
}
Then the application can define a Dart file that contains the list of APIs it considers unsafe:
import ‘dart:analyzer’ show Unsafe;
const unsafeFlutterApis = [
Unsafe('package:mypackage/MyClass#myMethod', 'Unsafe method. Consider using other method'),
Unsafe('package:mypackage/MyClass#someValue', 'Unsafe value. Consider using other value.'),
]
In analysis_options.yaml
file, annotate the avoid_unsafe_apis lint with a list of files that contain these banned apis:
- avoid_unsafe_apis
- ./unsafe_apis.dart
- some/other/path/unsafe_apis.dart
Analyzer reads this configuration and finds the union of all unsafe APIs listed in these files. The lint itself simply walks through various expression types and flags if the statement matches to any of the unsafe symbols.
Currently the following constructs can be marked as unsafe: Top-level variables, functions, getters, setters, constructors, static and instance members of classes, enums, and enum constants. We do not want to be able to mark entire Classes or Libraries as unsafe.
FAQ
Why Dart instead of json or yaml?
We can eventually make it easier and less error prone to refer to Dart symbols in a Dart file. You can imagine an evolution of Unsafe class as follows:
class Unsafe {
final Symbol symbol;
final String reason;
final bool ignorable;
const Unsafe(this.symbol, this.reason, {this.ignorable = true});
}
and the corresponding unsafe_apis.dart file:
import ‘dart:analyzer’ show Unsafe;
import ‘package:my_package/my_package.dart’;
const unsafeFlutterApis = [
Unsafe(#MyClass.myMethod, ‘Unsafe method. Consider using other method’, ignorable: false),
Unsafe(#MyClass.someValue, ‘Unsafe value. Consider using other value.’),
]
This way, we can ensure the correctness of the symbols without having to write another set of tests to map string representations to actual symbols.
I would love it if we could get to this in v1.
Why support multiple files?
For very large projects, a single file might get too unwieldy. Developers would prefer to split these perhaps by package. Internally, we would like each unsafe_apis.dart
to live in its own package directory rather than in the application. Analyzer will be provided with the full path of these files.
Why not mark Classes and Libraries as unsafe?
Large containers of code are hard to mark as unsafe. As they get larger (class => library), it takes a lot of discipline to stick to the encapsulation. For instance, a File object might offer write method which can be considered unsafe but also absolutePath getter which might not be. If someone argues that File itself is unsafe, all of a sudden absolutePath needs to find a new place to be used safely. We would want to avoid creation of classes like UnsafeFile and SafeFile and instead rely on library maintainers marking the smallest unit of compilation that deserves the unsafe annotation.
We also don't want to mark large containers as unsafe because the only mechanism to allow an unsafe API is via //ignore
comment. This forces the developer to give a valid reason for every instance of unsafe API usage (including links to a security review). If we were to mark entire libraries as unsafe, you will end up seeing unintended effects such as:
//ignore(unsafe_apis): Just using absolutePath which really is safe.
final filePath = File('foo').absolutePath;
See #40429 for further discussion.