Check that selection offset/length is valid in Extract Local refactoring.

R=brianwilkerson@google.com

Bug: https://github.com/dart-lang/sdk/issues/34475
Change-Id: Id2b44494a8464000180a8ca93cfcc770b256f59f
Reviewed-on: https://dart-review.googlesource.com/76063
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/refactoring/extract_local.dart b/pkg/analysis_server/lib/src/services/refactoring/extract_local.dart
index 41a4803..e2ade9f 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/extract_local.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/extract_local.dart
@@ -227,6 +227,15 @@
    * location of this [Expression] in AST allows extracting.
    */
   RefactoringStatus _checkSelection() {
+    if (selectionOffset <= 0) {
+      return new RefactoringStatus.fatal(
+          'The selection offset must be greater than zero.');
+    }
+    if (selectionOffset + selectionLength >= resolveResult.content.length) {
+      return new RefactoringStatus.fatal(
+          'The selection end offset must be less then the length of the file.');
+    }
+
     String selectionStr;
     // exclude whitespaces
     {
diff --git a/pkg/analysis_server/test/services/refactoring/extract_local_test.dart b/pkg/analysis_server/test/services/refactoring/extract_local_test.dart
index df1e893c..fd048d2 100644
--- a/pkg/analysis_server/test/services/refactoring/extract_local_test.dart
+++ b/pkg/analysis_server/test/services/refactoring/extract_local_test.dart
@@ -52,6 +52,28 @@
         expectedMessage: "The name 'res' is already used in the scope.");
   }
 
+  test_checkInitialCondition_false_outOfRange_length() async {
+    await indexTestUnit('''
+main() {
+  print(1 + 2);
+}
+''');
+    _createRefactoring(0, 1 << 20);
+    RefactoringStatus status = await refactoring.checkAllConditions();
+    assertRefactoringStatus(status, RefactoringProblemSeverity.FATAL);
+  }
+
+  test_checkInitialCondition_outOfRange_offset() async {
+    await indexTestUnit('''
+main() {
+  print(1 + 2);
+}
+''');
+    _createRefactoring(-10, 20);
+    RefactoringStatus status = await refactoring.checkAllConditions();
+    assertRefactoringStatus(status, RefactoringProblemSeverity.FATAL);
+  }
+
   test_checkInitialConditions_assignmentLeftHandSize() async {
     await indexTestUnit('''
 main() {
@@ -68,8 +90,7 @@
 
   test_checkInitialConditions_namePartOfDeclaration_function() async {
     await indexTestUnit('''
-main() {
-}
+void main() {}
 ''');
     _createRefactoringWithSuffix('main', '()');
     // check conditions
@@ -695,6 +716,24 @@
     expect(refactoring.names, unorderedEquals(['helloBob', 'bob']));
   }
 
+  test_isAvailable_false_notPartOfFunction() async {
+    await indexTestUnit('''
+var v = 1 + 2;
+''');
+    _createRefactoringForString('1 + 2');
+    expect(refactoring.isAvailable(), isFalse);
+  }
+
+  test_isAvailable_true() async {
+    await indexTestUnit('''
+main() {
+  print(1 + 2);
+}
+''');
+    _createRefactoringForString('1 + 2');
+    expect(refactoring.isAvailable(), isTrue);
+  }
+
   test_occurrences_differentVariable() async {
     await indexTestUnit('''
 main() {