[analyzer] Report invalid use of visibility-restricted elements in object patterns

Bug: https://github.com/dart-lang/sdk/issues/51750
Change-Id: Ifefddb398cc612eb8b5af1b945a2a2641ea960e3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/290264
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Sam Rawlins 2023-03-21 23:34:05 +00:00 committed by Commit Queue
parent f59e7d3aaa
commit 6ebf04576d
6 changed files with 272 additions and 32 deletions

View file

@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
@ -200,6 +201,25 @@ extension ListOfFormalParameterExtension on List<FormalParameter> {
}
}
extension PatternFieldImplExtension on PatternFieldImpl {
/// A [SyntacticEntity] which can be used in error reporting, which is valid
/// for both explicit getter names (like `Rect(width: var w, height: var h)`)
/// and implicit getter names (like `Rect(:var width, :var height)`).
SyntacticEntity get errorEntity {
var fieldName = name;
if (fieldName == null) {
return this;
}
var fieldNameName = fieldName.name;
if (fieldNameName == null) {
var variablePattern = pattern.variablePattern;
return variablePattern?.name ?? this;
} else {
return fieldNameName;
}
}
}
extension RecordTypeAnnotationExtension on RecordTypeAnnotation {
List<RecordTypeAnnotationField> get fields {
return [

View file

@ -390,6 +390,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
_errorReporter.reportErrorForNode(
WarningCode.CAST_FROM_NULL_ALWAYS_FAILS, node);
}
super.visitCastPattern(node);
}
@override
@ -806,6 +807,12 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
super.visitNamedType(node);
}
@override
void visitPatternField(PatternField node) {
_invalidAccessVerifier.verifyPatternField(node as PatternFieldImpl);
super.visitPatternField(node);
}
@override
void visitPostfixExpression(PostfixExpression node) {
_deprecatedVerifier.postfixExpression(node);
@ -2077,6 +2084,30 @@ class _InvalidAccessVerifier {
}
}
void verifyPatternField(PatternFieldImpl node) {
var element = node.element;
if (element == null || _inCurrentLibrary(element)) {
return;
}
if (_hasInternal(element) &&
!_isLibraryInWorkspacePackage(element.library)) {
var fieldName = node.name;
if (fieldName == null) {
return;
}
var errorEntity = node.errorEntity;
_errorReporter.reportErrorForOffset(
WarningCode.INVALID_USE_OF_INTERNAL_MEMBER,
errorEntity.offset,
errorEntity.length,
[element.displayName]);
}
_checkForOtherInvalidAccess(node, element);
}
void verifySuperConstructorInvocation(SuperConstructorInvocation node) {
if (node.constructorName != null) {
// Named constructor calls are handled by [verify].
@ -2090,8 +2121,7 @@ class _InvalidAccessVerifier {
}
}
void _checkForInvalidInternalAccess(
SimpleIdentifier identifier, Element element) {
void _checkForInvalidInternalAccess(Identifier identifier, Element element) {
if (_hasInternal(element) &&
!_isLibraryInWorkspacePackage(element.library)) {
String name;
@ -2112,8 +2142,7 @@ class _InvalidAccessVerifier {
}
}
void _checkForOtherInvalidAccess(
SimpleIdentifier identifier, Element element) {
void _checkForOtherInvalidAccess(AstNode node, Element element) {
bool hasProtected = _hasProtected(element);
if (hasProtected) {
var definingClass = element.enclosingElement as InterfaceElement;
@ -2124,14 +2153,14 @@ class _InvalidAccessVerifier {
bool isVisibleForTemplateApplied = _isVisibleForTemplateApplied(element);
if (isVisibleForTemplateApplied) {
if (_inTemplateSource || _inExportDirective(identifier)) {
if (_inTemplateSource || _inExportDirective(node)) {
return;
}
}
bool hasVisibleForTesting = _hasVisibleForTesting(element);
if (hasVisibleForTesting) {
if (_inTestDirectory || _inExportDirective(identifier)) {
if (_inTestDirectory || _inExportDirective(node)) {
return;
}
}
@ -2143,37 +2172,49 @@ class _InvalidAccessVerifier {
// annotation present.
String name;
AstNode node;
SyntacticEntity errorEntity = node;
var grandparent = identifier.parent?.parent;
if (grandparent is ConstructorName) {
name = grandparent.toSource();
node = grandparent;
var grandparent = node.parent?.parent;
if (node is Identifier) {
if (grandparent is ConstructorName) {
name = grandparent.toSource();
errorEntity = grandparent;
} else {
name = node.name;
}
} else if (node is PatternFieldImpl) {
name = element.displayName;
errorEntity = node.errorEntity;
} else {
name = identifier.name;
node = identifier;
throw StateError('Can only handle Identifier or PatternField, but got '
'${node.runtimeType}');
}
var definingClass = element.enclosingElement;
if (definingClass == null) {
return;
}
if (hasProtected) {
_errorReporter.reportErrorForNode(
_errorReporter.reportErrorForOffset(
WarningCode.INVALID_USE_OF_PROTECTED_MEMBER,
node,
[name, definingClass!.source!.uri]);
errorEntity.offset,
errorEntity.length,
[name, definingClass.source!.uri]);
}
if (isVisibleForTemplateApplied) {
_errorReporter.reportErrorForNode(
_errorReporter.reportErrorForOffset(
WarningCode.INVALID_USE_OF_VISIBLE_FOR_TEMPLATE_MEMBER,
node,
[name, definingClass!.source!.uri]);
errorEntity.offset,
errorEntity.length,
[name, definingClass.source!.uri]);
}
if (hasVisibleForTesting) {
_errorReporter.reportErrorForNode(
_errorReporter.reportErrorForOffset(
WarningCode.INVALID_USE_OF_VISIBLE_FOR_TESTING_MEMBER,
node,
[name, definingClass!.source!.uri]);
errorEntity.offset,
errorEntity.length,
[name, definingClass.source!.uri]);
}
if (hasVisibleForOverriding) {
@ -2183,14 +2224,15 @@ class _InvalidAccessVerifier {
parent is PropertyAccess && parent.target is SuperExpression) {
var methodDeclaration =
grandparent?.thisOrAncestorOfType<MethodDeclaration>();
if (methodDeclaration?.name.lexeme == identifier.name) {
if (methodDeclaration?.name.lexeme == name) {
validOverride = true;
}
}
if (!validOverride) {
_errorReporter.reportErrorForNode(
_errorReporter.reportErrorForOffset(
WarningCode.INVALID_USE_OF_VISIBLE_FOR_OVERRIDING_MEMBER,
node,
errorEntity.offset,
errorEntity.length,
[name]);
}
}
@ -2282,9 +2324,8 @@ class _InvalidAccessVerifier {
bool _inCurrentLibrary(Element element) => element.library == _library;
bool _inExportDirective(SimpleIdentifier identifier) =>
identifier.parent is Combinator &&
identifier.parent!.parent is ExportDirective;
bool _inExportDirective(AstNode node) =>
node.parent is Combinator && node.parent!.parent is ExportDirective;
bool _isLibraryInWorkspacePackage(LibraryElement? library) {
if (_workspacePackage == null || library == null) {

View file

@ -29,9 +29,7 @@ class InvalidUseOfInternalMemberTest extends PubPackageResolutionTest {
..add(
name: 'foo',
rootPath: fooPackageRootPath,
languageVersion: '2.9',
),
languageVersion: '2.9',
meta: true,
);
}
@ -68,6 +66,96 @@ A a = A();
]);
}
test_outsidePackage_class_inAsExpression() async {
newFile('$fooPackageRootPath/lib/src/a.dart', '''
import 'package:meta/meta.dart';
@internal
class A {}
''');
await assertErrorsInCode('''
import 'package:foo/src/a.dart';
void f(Object o) {
o as A;
}
''', [
error(WarningCode.INVALID_USE_OF_INTERNAL_MEMBER, 60, 1),
]);
}
test_outsidePackage_class_inCastPattern() async {
newFile('$fooPackageRootPath/lib/src/a.dart', '''
import 'package:meta/meta.dart';
@internal
class A {}
''');
await assertErrorsInCode('''
import 'package:foo/src/a.dart';
void f(Object a, Object b) {
(b as A, ) = (a, );
}
''', [
error(WarningCode.INVALID_USE_OF_INTERNAL_MEMBER, 71, 1),
]);
}
test_outsidePackage_class_inIsExpression() async {
newFile('$fooPackageRootPath/lib/src/a.dart', '''
import 'package:meta/meta.dart';
@internal
class A {}
''');
await assertErrorsInCode('''
import 'package:foo/src/a.dart';
void f(Object o) {
o is A;
}
''', [
error(WarningCode.INVALID_USE_OF_INTERNAL_MEMBER, 60, 1),
]);
}
test_outsidePackage_class_inObjectPattern() async {
newFile('$fooPackageRootPath/lib/src/a.dart', '''
import 'package:meta/meta.dart';
@internal
class A {}
''');
await assertErrorsInCode('''
import 'package:foo/src/a.dart';
void f(Object a, Object b) {
switch (a) {
case A(): print('yes');
}
}
''', [
error(WarningCode.INVALID_USE_OF_INTERNAL_MEMBER, 87, 1),
]);
}
test_outsidePackage_class_inVariableDeclarationType() async {
newFile('$fooPackageRootPath/lib/src/a.dart', '''
import 'package:meta/meta.dart';
@internal
class A {}
''');
await assertErrorsInCode('''
import 'package:foo/src/a.dart';
A? a;
''', [
error(WarningCode.INVALID_USE_OF_INTERNAL_MEMBER, 34, 1),
]);
}
test_outsidePackage_constructor_named() async {
newFile('$fooPackageRootPath/lib/src/a.dart', '''
import 'package:meta/meta.dart';
@ -153,6 +241,28 @@ int a = 'hello'.f();
]);
}
test_outsidePackage_field_inObjectPattern() async {
newFile('$fooPackageRootPath/lib/src/a.dart', '''
import 'package:meta/meta.dart';
class A {
@internal
int get a => 42;
}
''');
await assertErrorsInCode('''
import 'package:foo/src/a.dart';
void f(Object o) {
switch (o) {
case A(a: 7): print('yes');
}
}
''', [
error(WarningCode.INVALID_USE_OF_INTERNAL_MEMBER, 79, 1),
]);
}
test_outsidePackage_function() async {
newFile('$fooPackageRootPath/lib/src/a.dart', '''
import 'package:meta/meta.dart';
@ -491,7 +601,7 @@ f() {
newFile('$fooPackageRootPath/lib/src/a.dart', '''
import 'package:meta/meta.dart';
class C {
int get s() => 1;
int? get s() => 1;
@internal
set s(int value) {}

View file

@ -231,6 +231,29 @@ class B {
]);
}
test_getter_outsideClassAndLibrary_inObjectPattern() async {
newFile('$testPackageLibPath/lib1.dart', r'''
import 'package:meta/meta.dart';
class A {
@protected
int get a => 42;
}
''');
newFile('$testPackageLibPath/lib2.dart', r'''
import 'lib1.dart';
void f(Object o) {
switch (o) {
case A(a: 7): print('yes');
}
}
''');
await _resolveFile('$testPackageLibPath/lib1.dart');
await _resolveFile('$testPackageLibPath/lib2.dart', [
error(WarningCode.INVALID_USE_OF_PROTECTED_MEMBER, 65, 1),
]);
}
test_getter_subclass() async {
await assertNoErrorsInCode(r'''
import 'package:meta/meta.dart';

View file

@ -111,6 +111,29 @@ class B {
]);
}
test_getter_inObjectPattern() async {
newFile('$testPackageLibPath/a.dart', '''
import 'package:meta/meta.dart';
class A {
@visibleForOverriding
int get g => 0;
}
''');
await assertErrorsInCode('''
import 'a.dart';
void f(Object o) {
switch (o) {
case A(g: 7): print('yes');
}
}
''', [
error(WarningCode.INVALID_USE_OF_VISIBLE_FOR_OVERRIDING_MEMBER, 63, 1),
]);
}
test_operator() async {
newFile('$testPackageLibPath/a.dart', '''
import 'package:meta/meta.dart';

View file

@ -198,6 +198,29 @@ void main() {
]);
}
test_getter_inObjectPattern() async {
newFile('$testPackageRootPath/lib1.dart', r'''
import 'package:meta/meta.dart';
class A {
@visibleForTesting
int get g => 7;
}
''');
newFile('$testPackageRootPath/lib2.dart', r'''
import 'lib1.dart';
void f(Object o) {
switch (o) {
case A(g: 7): print('yes');
}
}
''');
await _resolveFile('$testPackageRootPath/lib1.dart');
await _resolveFile('$testPackageRootPath/lib2.dart', [
error(WarningCode.INVALID_USE_OF_VISIBLE_FOR_TESTING_MEMBER, 65, 1),
]);
}
test_import_hide() async {
newFile('$testPackageLibPath/a.dart', r'''
import 'package:meta/meta.dart';