Check unsafe attributes and methods on extensions (#2057)
Check unsafe attributes and methods on extensions
diff --git a/lib/src/rules/unsafe_html.dart b/lib/src/rules/unsafe_html.dart
index 393fbeb..04e25d3 100644
--- a/lib/src/rules/unsafe_html.dart
+++ b/lib/src/rules/unsafe_html.dart
@@ -4,6 +4,7 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import '../analyzer.dart';
@@ -53,6 +54,7 @@
final visitor = _Visitor(this);
registry.addAssignmentExpression(this, visitor);
registry.addInstanceCreationExpression(this, visitor);
+ registry.addFunctionExpressionInvocation(this, visitor);
registry.addMethodInvocation(this, visitor);
}
}
@@ -76,27 +78,33 @@
@override
void visitAssignmentExpression(AssignmentExpression node) {
final leftPart = node.leftHandSide.unParenthesized;
- if (leftPart is PropertyAccess) {
- _checkAssignment(leftPart.realTarget, leftPart.propertyName, node);
+ if (leftPart is SimpleIdentifier) {
+ var leftPartElement = leftPart.staticElement;
+ if (leftPartElement == null) return;
+ var enclosingElement = leftPartElement.enclosingElement;
+ if (enclosingElement is ClassElement) {
+ _checkAssignment(enclosingElement.thisType, leftPart, node);
+ }
+ } else if (leftPart is PropertyAccess) {
+ _checkAssignment(
+ leftPart.realTarget?.staticType, leftPart.propertyName, node);
} else if (leftPart is PrefixedIdentifier) {
- _checkAssignment(leftPart.prefix, leftPart.identifier, node);
+ _checkAssignment(leftPart.prefix?.staticType, leftPart.identifier, node);
}
}
- void _checkAssignment(Expression target, SimpleIdentifier property,
+ void _checkAssignment(DartType type, SimpleIdentifier property,
AssignmentExpression assignment) {
- if (property == null || target == null) return;
+ if (property == null || type == null) return;
// It is more efficient to check the setter's name before checking whether
// the target is an interesting type.
if (property.name == 'href') {
- final type = target.staticType;
if (type.isDynamic || type.extendsDartHtmlClass('AnchorElement')) {
rule.reportLint(assignment,
arguments: ['href'], errorCode: unsafeAttributeCode);
}
} else if (property.name == 'src') {
- final type = target.staticType;
if (type.isDynamic ||
type.extendsDartHtmlClass('EmbedElement') ||
type.extendsDartHtmlClass('IFrameElement') ||
@@ -106,7 +114,6 @@
arguments: ['src'], errorCode: unsafeAttributeCode);
}
} else if (property.name == 'srcdoc') {
- final type = target.staticType;
if (type.isDynamic || type.extendsDartHtmlClass('IFrameElement')) {
rule.reportLint(assignment,
arguments: ['srcdoc'], errorCode: unsafeAttributeCode);
@@ -134,12 +141,26 @@
@override
void visitMethodInvocation(MethodInvocation node) {
- var type = node.target?.staticType;
- if (type == null) return;
-
var methodName = node.methodName?.name;
if (methodName == null) return;
+ // The static type of the target.
+ DartType type;
+ if (node.realTarget == null) {
+ // Implicit `this` target.
+ var methodElement = node.methodName.staticElement;
+ if (methodElement == null) return;
+ var enclosingElement = methodElement.enclosingElement;
+ if (enclosingElement is ClassElement) {
+ type = enclosingElement.thisType;
+ } else {
+ return;
+ }
+ } else {
+ type = node.realTarget.staticType;
+ if (type == null) return;
+ }
+
if (methodName == 'createFragment' &&
(type.isDynamic || type.extendsDartHtmlClass('Element'))) {
rule.reportLint(node,
diff --git a/test/rules/unsafe_html.dart b/test/rules/unsafe_html.dart
index b8d94bc..7224978 100644
--- a/test/rules/unsafe_html.dart
+++ b/test/rules/unsafe_html.dart
@@ -29,9 +29,12 @@
var heading = HeadingElement.h1();
heading.createFragment('<script>'); // LINT
+ heading..createFragment('<script>'); // LINT
heading.setInnerHtml('<script>'); // LINT
+ heading..setInnerHtml('<script>'); // LINT
Window().open('url', 'name'); // LINT
+ Window()..open('url', 'name'); // LINT
DocumentFragment.html('<script>'); // LINT
Element.html('<script>'); // LINT
@@ -72,3 +75,37 @@
void setInnerHtml(String html) {}
}
+
+extension on ScriptElement {
+ void sneakySetSrc1(String url) => src = url; // LINT
+ void sneakySetSrc2(String url) => this.src = url; // LINT
+
+ // TODO(srawlins): Re-enable this when analyzer's mock SDK includes
+ // `Element.createFragment`.
+ // void sneakyCreateFragment1(String html) => createFragment(html); // LINT
+ void sneakyCreateFragment2(String html) => this.createFragment(html); // LINT
+
+ // TODO(srawlins): Re-enable this when analyzer's mock SDK includes
+ // `Element.setInnerHtml`.
+ // void sneakySetInnerHtml1(String html) => setInnerHtml(html); // LINT
+ void sneakySetInnerHtml2(String html) => this.setInnerHtml(html); // LINT
+}
+
+extension on Window {
+ void sneakyOpen1(String url, String name) => open(url, name); // LINT
+ void sneakyOpen2(String url, String name) => this.open(url, name); // LINT
+}
+
+extension on C {
+ void sneakySetSrc1(String url) => src = url; // OK
+ void sneakySetSrc2(String url) => this.src = url; // OK
+
+ void sneakyCreateFragment1(String html) => createFragment(html); // OK
+ void sneakyCreateFragment2(String html) => this.createFragment(html); // OK
+
+ void sneakySetInnerHtml1(String html) => setInnerHtml(html); // OK
+ void sneakySetInnerHtml2(String html) => this.setInnerHtml(html); // OK
+
+ void sneakyOpen1(String url, String name) => open(url, name); // OK
+ void sneakyOpen2(String url, String name) => this.open(url, name); // OK
+}