-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Lint request: avoid_private_access_on_open_classes
#57133
Comments
Note that even within the same class, it isn't always safe, at least if the member is abstract: abstract class A {
int get _x;
@override
String toString() => '$_x'; // This should also lint, less clear what the lint should suggest.
} There are probably other complicated cases to think through as well. Honestly, I would explore whether triggering the lint on any class with private state is an option. That would be the safest thing to do. Or, if this is the only case we can come up with, then we should extend the lint to check for it. |
Interesting, I guess I don't quite understand this case? I don't see any potential for runtime exceptions?
If we can find concrete cases where bad behavior is possible, I'd be supportive - I'd be worried about creating a lint so strict that it invalidates entire codebases that are otherwise running OK. |
You can extend |
Although I realize now the only safe thing to do would be to require |
It took me a few readings to get the motivation, but I think I get it. The key that I was missing is that this access is OK on final classes, because we know the runtime type (the exhaustive list anyhow). But for an open class, anyone could have claimed to implement it, and you know that they didn't really implement it. |
Yup you got it! |
Just to show the importance of this lint, there are probably some other Flutter APIs which are being used this way. As I had already filed flutter/flutter#148033. There are probably more "controllers" and similar classes that currently have that issue. For these cases, another solution would be creating a wrapper class or something similar that would implement that private functionality. That class could potentially be private and that would be fine, I think. |
@jakemac53 I think that's fine, right? The A#toString() call accesses A#_private, which is fine? |
It isn't fine, because A is abstract and has no implementation of
|
Not showing the error but the actual "implementation": bug.dartimport 'other.dart';
void main() {
final a = A();
final b = B();
final c = C();
print(a.toString());
print(b.toString());
print(c.toString());
}
class A {
int get _private => 0;
@override
String toString() => '$runtimeType: $_private';
} other.dartimport 'bug.dart';
class B implements A {}
class C extends A {} Output:
EditThis is exactly why I'm waiting for dart-lang/language#3748. |
Ohhhhhh, ok sorry I was stuck/rat-holed on my original example. You mean: // This is an impossible case, other classes outside of this class can't implement it.
abstract class A {
int get _private;
} I wonder if this should be a second lint, or this lint should be generalized as +1, we should also cover that use case; added in the OP as |
@matanlurey, this is great! I'd love to see improvements in the static support for detecting and avoiding these privacy related run-time failures. However, it isn't quite sufficient to do the things you mentioned. @jakemac53 already mentioned that an abstract declaration isn't safe. Here are some other things to be aware of: Safe call sitesWe have two invocations marked 'OK'. Consider the following variant: // --- Library 'n044lib.dart'.
abstract class A {
int _private() => 0;
@override
String toString() => 'A: $_private()';
}
abstract class B extends A {
@override
// OK
String toString() => 'B: $_private()';
}
abstract class Danger1 extends A {
int _private([int _]);
}
abstract class Danger2 extends B {
int _private([int _]);
}
void useIt1(Danger1 danger1) => danger1._private(0);
void useIt2(Danger2 danger2) => danger2._private(0);
void main() {} // Allow the CFE to process this library. This library is accepted by the analyzer as well as the CFE. It may be used as follows: import 'n044lib.dart';
class Danger1Sub extends Danger1 {}
class Danger2Sub extends Danger2 {}
void main() {
try {
Danger1Sub().toString(); // Would throw in `A.toString`.
} catch (_) {
print('Caught 1');
}
try {
Danger2Sub().toString(); // Would throw in `B.toString`.
} catch (_) {
print('Caught 2');
}
} This program is also accepted by the analyzer. The CFE rejects the program with a compile-time error reporting that the methods private to 'n044lib.dart' are erroneous. (I created dart-lang/language#3826 to report the discrepancy between the analyzer and the CFE, and to discuss the situation more broadly because it amounts to a violation of a principle which has been held up as desirable.)
So if we maintain that the correct behavior is to not report an error when a class in a library L does not have an implementation of some members that are private to a different library L2, then the above example would demonstrate that the call sites in Make
|
I would postulate that compile-time errors should have zero false positives. It's fine if the language forbids a construct that could be given a runtime semantic, but if the construct is allowed by the language then we can't report it as an error. Warnings need to be similarly free of false positives, though we do currently have a few violations of the policy. We could, however, create a lint for this if we think we'd enable it in the core lint set (and it doesn't require whole-program analysis). But for a lint we don't need to flag every violation in order for it to be reasonable, so there might be a tradeoff between completeness and the number of false positives. |
I thought lints would freely report situations that aren't compile-time errors, that's basically all they can do? In any case, we can under-approximate and over-approximate (obtaining a guarantee that no non-problems are reported, perhaps failing to detect some actual problems, respectively obtaining a guarantee that no problems remain unreported, perhaps reporting some situations that aren't actual problems). We just need to choose an approach which is in line with existing policies. |
Sorry for the confusion. What I meant by "error" is "compile-time error". I try to always use "error" that way, to use "warning" to mean a non-error that is enabled by default, and "lint" to mean a non-error that is disabled by default. I use "diagnostic" to mean any of the above. Somehow I'd gotten the impression that you were proposing that this be an error. I'm not sure how, and you clearly labeled the issue as a "Lint request", so I should have realized that that's not what you had in mind.
It's a bit fuzzy, but we don't allow false positives on lints unless it's not possible to avoid false positives without introducing false negatives AND the risks to a user of having false negatives is high enough to justify the annoyance of false positives. The latter is, of course, purely a judgement call, and not one we always get right. |
Very good, we agree completely on that! (Except that I've probably used "warning" less precisely now and then.) I used the word 'error' a number of times because I was reporting that the tools currently emit some compile-time errors in situations relevant to this issue (especially the CFE). If it is decided that the CFE should stop reporting these errors (in particular, in response to "this class does not have an implementation of a member which is private to a different library") then I'd very much support the introduction of a lint for the same thing. After all, if my app just crashed with a noSuchMethod exception then I am not going to be a huge lot more happy even if they tell me "but the non-existing thing was a private method!" ;-) The lint proposed in this issue is somewhat different: It attempts to make a distinction between safe and unsafe invocations of private members. That's a tricky exercise (e.g., because it involves precise knowledge about which classes are "leaking" to other libraries), but it might be possible. |
Summary: Propose a new lint, |
In Dart, private members cannot be implemented outside of their declaring library, and as such, using
implements A
whereA
has a private member that is accessed outside of it's class can easily create an invalid class state; other types assume subtypeA
will always have a private member that it cannot have by definition.An example described by @jakemac53:
I'd like to suggest a lint (that hopefully we add to recommended sets as it matures), which I'm naming
avoid_private_access_on_open_classes
(but the name is not important to me, only the semantics), it would flag private members accessed outside of their declaring class or sub-classes declared in the declaring library:The error message/quick-fixes could suggest:
A
final
A
base
_private
public// ignore
if the developer knows this type is not truly API publicUser-land issue: flutter/flutter#148692.
The text was updated successfully, but these errors were encountered: