-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Introduce @Unsafe annotation #40429
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
Introduce @Unsafe annotation #40429
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -277,6 +277,24 @@ class Required { | |||||
const Required([this.reason = '']); | ||||||
} | ||||||
|
||||||
|
||||||
/// Used to annotate an unsafe method or value in a class. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean by value? Can top-level members and all class members be tagged Unsafe? What about library declarations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it an error to annotate a private member? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, the comment could use some work if this annotation is added here, but see my other review comment. For me, the important parts are defining where the annotation can validly be used, what the annotation means, and what behavior the analyzer should implement related to the annotation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be used to annotate API. If the unsafe code can appear in client code, we should be able to mark it. Some examples:
Private members should not be marked IMO. If there's a public API that is using the private method, the library maintainer should be responsible for marking that Is this a library declaration?
and is library the same thing as a pub package? (We don't use library declarations in google3 so I am not familiar with them). I am not sure what would be the effect of marking this unsafe. If you feel that's useful, then sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that, I'd write the description something like the following:
Are the semantics above for classes and enums correct? I was just guessing about those. Are there annotations other than Do we also need to be able to use the annotation on mixins and extensions (and their members)? How about typedefs (that seems unlikely to me, but I'm not as well versed in security as you probably are).
Possibly a nit, but we produce hints for violations of annotations, not lints. The distinction is that hints are enabled by default in the analyzer whereas lints are disabled by default. Probably not important to the point you were making, but it does impact how we implement checking, so you might want to know the distinction for later work.
Yes, that's a library declaration. In Dart, a library is a compilation unit that does not contain a part-of directive. A package is a collection of libraries and any parts associated with those libraries. Internally a package is a directory with a build file, though just to be confusing we refer to that directory as a "library". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am unclear whether you can mark a whole library as unsafe. Ack on lint vs hint. I do think this should be enabled by default. However, I am not sure how we treat hints in google3 @davidmorgan .
Yes. A mixin or an extension can easily contain unsafe code.
I would think not since typedefs by themselves do not contain any logic. I was trying to figure out whether there's a case where a library author might accidentally hide an unsafe class behind a typedef. In that case though, they should simply mark the method(s) that use that Typedef as unsafe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I also think that's the right choice.
David can correct me if I'm wrong, but I'm fairly sure they cause the build to fail (though there might be a whitelist that allows some to be ignored for that purpose).
The only way I can think of to hide an unsafe class would be by using Then again, I initially wondered why an enum constant would be marked unsafe (after all, writing The question of marking libraries also touches on the semantics of putting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point about classes vs libraries. You are absolutely right that containers 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 OTOH, it would be easy to move an unsafe declaration to more specific elements. The opposite direction would be harder. We could allow unsafe classes and libraries with the explanation that they should be sparingly used. I am not sure of the right way to bootstrap annotations. If we want to be conservative and only allow it on constructs we have a use case for, I am happy to drop classes from the list of supported constructs as we don't have a use case for it. We have use cases for method declarations (both class and top level), public members (both class and top level) and enum values.
Correct. Case in point: enum JavascriptMode {
@Unsafe('Allowing javascript execution is unsafe. Consider using [disallow].')
allow,
disallow,
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I generally prefer to limit the places to which they can be applied to the places we know we need them. If someone tries to use it somewhere new we can always allow that case later, but removing support is a breaking change. Also, the fewer places they're allowed the less code we write and maintain to support and test the feature. But it's always a judgement call, and I'm happy to trust your judgement in this case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like @bwilkerson's proposals. However, we could still make the adjustment about where the metadata is supported, cf. my comment on CHANGELOG.md:
Suggested change
If a |
||||||
/// | ||||||
/// This annotation is expected to be used in sensitive libraries where API | ||||||
/// usage typically requires a security review. | ||||||
class Unsafe { | ||||||
|
||||||
/// Human readable reason of why this construct is unsafe. | ||||||
/// | ||||||
/// This is required because unsafe annotations are not self-explanatory. | ||||||
/// Explanation should give alternative APIs to use if applicable. It should | ||||||
/// also link to documentation with more details as to why the API is unsafe. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps an example? |
||||||
final String reason; | ||||||
|
||||||
/// Initialize a newly created instance with a given reason. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. => instance with a given [reason]. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say :
Generally document constructors as "Creates a ... where ..." |
||||||
const Unsafe(this.reason); | ||||||
} | ||||||
|
||||||
class _AlwaysThrows { | ||||||
const _AlwaysThrows(); | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
name: meta | ||
version: 1.1.8 | ||
version: 1.1.9 | ||
author: Dart Team <[email protected]> | ||
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/meta | ||
description: > | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See below w/ a question about what can be annotated
@Unsafe
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't that be
@unsafe
or@Unsafe()
in practice?Why not include a possible qualification, e.g., a constructor argument of type
String
, such that it could be indicated that one API typically requires a security audit (e.g.,@Unsafe('security-audit')
), another one could be unsafe because it runs an algorithm that uses a lot of resources (time and/or memory, e.g.,@Unsafe('memory')
), etc.I can see that the declaration in 'meta.dart' already supports a
String
valued constructor argument giving areason
, and the notion of having more than one kind of lack-of-safety would then simply be a matter of standardization with respect to that string (@Unsafe("security-audit: ...<reason>...")
).About the supported targets: We could say
This would allow all the constructs that @mehmetf mentions in the comment on line 281. It would rule out "smaller" things (type parameters, formal parameters, local functions and variables including
for
loop iteration variables), and library level entities (imports/exports,part
andpart of
directives, andlibrary
directives).You could make the case that an optional formal parameter should be allowed to carry this metadata as well: An
@unsafe
optional parameter would be one where only the default value is safe. But it's probably not sufficiently useful to carry its own weight.