-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Adding a _debugAssert annotation
#130101
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
Adding a _debugAssert annotation
#130101
Conversation
…lutter into flutter-internal-lints
69d82a3 to
e61e97f
Compare
e61e97f to
1cc26e6
Compare
cd98289 to
ab6ce8d
Compare
|
Should we just make anything called |
dev/bots/custom_rules/analyze.dart
Outdated
|
|
||
| import '../utils.dart'; | ||
|
|
||
| /// Analyzes the given `workingDirectory` with the given set of [AnalyzeRule]s. |
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.
This doesn't work for any workingDirectory, though. The working directory must contain the flutter package at package/flutter/lib. Typically we call this directory the flutter root, so I would just call this parameter flutterRoot (or actually make analyzeDirectoryWithRules work on any directory).
dev/bots/custom_rules/analyze.dart
Outdated
| /// Reports all violations in the resolved compilation units [applyTo] was | ||
| /// called all. |
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.
I don't understand this sentence...
dev/bots/custom_rules/analyze.dart
Outdated
| /// The implementation typically calls [foundErrors] to report violations. | ||
| void reportViolations(String workingDirectory); | ||
| /// Applies this rule to the given resolved compilation unit (which is | ||
| /// typically a file). |
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.
document what one should do with failures (i.e. this shouldn't throw or anything and just record the failures internally until reportViolations is called)
dev/bots/custom_rules/analyze.dart
Outdated
| void reportViolations(String workingDirectory); | ||
| /// Applies this rule to the given resolved compilation unit (which is | ||
| /// typically a file). | ||
| void applyTo(ResolvedUnitResult unit); |
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.
nit: I would switch the order of these methods around, logically, you need to call applyTo first and then worry about how to get the failures back out.
| import '../utils.dart'; | ||
| import 'analyze.dart'; | ||
|
|
||
| /// An [AnalyzeRule] that verifies annotated getters, setters, constructors, |
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.
document: annotated with what?
I originally made that an annotation because I wanted to mark an entire library debug-only (primarily for diagnostics.dart but it's also used in profile mode so that didn't work). Also according to @goderbauer we use |
4dd7474 to
b55969e
Compare
srawlins
left a comment
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.
I've made it halfway through debug_assert.dart, but have to pause for a while, so I'm sending out my comments so far.
| /// outside of an assert. | ||
| /// | ||
| /// The annotation can also be added to classes, mixins, extensions, or | ||
| /// libraries, in which case all non-synthetic memebers defined within that |
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.
nit: 'members'
|
|
||
| export 'print.dart' show DebugPrintCallback; | ||
|
|
||
| /// An annotation that prevents annotated getters, setters, constructors, |
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.
and operators?
| /// functions, methods and fields to be called/accessed directly or indirectly | ||
| /// outside of an assert. | ||
| /// | ||
| /// The annotation can also be added to classes, mixins, extensions, or |
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.
Just to have great documentation while this is all loaded into our brains: what about inheritence?
- If B extends A and A.m is annotated with
@debugAssert, canB.mbe called outside an assert? - If B extends A and A is annotated with
@debugAssert, can any of B's members be called outside of an assert?
(I think maybe I don't know what "non-synthetic" members are...)
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.
By "non-synthetic" I guess I just meant the implicit default constructors.
Updated the documentation to talk more about inheritance.
|
|
||
| const String debugAssert = ''; | ||
|
|
||
| String globalVaraibleFromDebugLib = ''; |
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.
nit: 'Variable'
| /// inside asserts. | ||
| /// | ||
| /// The annotation can also be applied to [InterfaceElement]s (classes, mixins | ||
| /// and extensions) or libraries, in which case all non-synthetic elements |
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.
nit: extensions are not InterfaceElements.
|
|
||
| // Subclasses may "inherit" default constructors from the superclass. Since | ||
| // constructors can't be invoked by the class members (unlike methods that | ||
| // can have "bad annotations"). Defer to the first non-synthetic default |
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.
I'm not sure where this sentence starts/stops. Should it be, "), defer to ..."?
| assert(!constructorElement.isFactory); | ||
| assert(constructorElement.isDefaultConstructor); | ||
|
|
||
| // Subclasses may "inherit" default constructors from the superclass. Since |
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.
I don't think this is correct, even with the quotes. Classes in no way inherit constructors.
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.
If a class doesn't define any constructors then you get class.new(). for free which invokes super() (I think that's the case?) is what I meant. Updated the comment to be more specific.
|
|
||
| // Subclasses may "inherit" default constructors from the superclass. Since | ||
| // constructors can't be invoked by the class members (unlike methods that | ||
| // can have "bad annotations"). Defer to the first non-synthetic default |
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.
Hmm, why the first non-synthetic (which I think means "non-default"? Take this example:
class A {
@debugAssert
A();
}
class B extends A {
B();
}
class C extends B {}
void main() {
C();
}It seems that the call to C() should be reported even though the first non-default super-constructor called is B() which has no annotation.
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.
I think this case will be caught by the code in visitConstructorDeclaration, since B.new is not debug-only but A.new is, and B.new invokes A.new implicitly.
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.
regarding first non-synthetic, I would like to make this annotation more forgiving, so in this case
class A {
@debugAssert
A();
}
class B1 {
}
class B2 extends A {
B2(); // illegal, implicitly invokes A.new and doesn't have @debugAssert
}you don't have to add a default constructor to B1 to add the annotation to it.
| // constructor in the class hierarchy. | ||
| final ConstructorElement? superConstructor = constructorElement.enclosingElement.thisType | ||
| .superclass?.element.constructors | ||
| .firstWhereOrNull((ConstructorElement constructor) => constructor.isDefaultConstructor); |
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.
Might be better performance to just ask if the unnamed constructor is default, instead of walking all of the named ones.
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.
I'm a bit scared of the TODO(scheglov) Deprecate and remove it. comment on this property, it sounds like unnamedConstructor will be removed soon?
| // (see the non-static ExecutableElement case), but a default synthetic | ||
| // constructor should defer to its superclass since it's not explictly | ||
| // defined. | ||
| case ConstructorElement(isSynthetic: true): |
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.
I'm not sure why the synthetic nature makes a difference. Shouldn't this code be reported?
class C {
@debugAssert
C();
}
class D extends C {
D(); // Implicitly calls C().
}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.
Also that code will be reported I think? Neither C.new nor D.new is synthetic here?
f903b51 to
3dc2d5a
Compare
|
@srawlins thanks for the review! Ideally I would like to also check if a piece of code is reachable when a const variable, or a set of const variables are set to certain values, is there an easy way to do so? E.g., if (_kReleaseMode && ....) {
// debug only code
} |
3dc2d5a to
29a5a1b
Compare
29a5a1b to
276be09
Compare
|
This pull request executed golden file tests, but it has not been updated in a while (20+ days). Test results from Gold expire after as many days, so this pull request will need to be updated with a fresh commit in order to get results from Gold. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
|
Hey @LongCatIsLooong, I'm excited about this change, and would like to add my own custom rules for the tool. Are you still actively working on this? |
Also fixes #103917 (but resolving the entire framework takes about 50 seconds while the original implementation only takes a few seconds).
Introduces an
_debugAssertannotation that verifies the annotated getters/setters/constructors/functions/methods in the framework are only directly or indirectly called inside asserts (checkingkDebugModewill be nice but there doesn't seem to be an easy way of doing this kind of control flow analysis).In the rare case where a
_debugAssertannotated method gets overridden, the override is also considered debug-only. The analyzing code reports an error if a method override has@_debugAssertbut the super implementation isn't debug only (unless the super implementation isn't defined in the framework).Synthesized default constructors are considered debug-only if it's superclass's default constructor is debug-only.
It would be nice to have a real lint so the feedback is instantaneous and works outside of the framework codebase. Would be even nicer if it accounts for
kDebugMode/kProfileModein addition to--enable-asserts.TODOs after this:
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.