Version 2.10.0-117.0.dev
Merge commit '62354c15403d42640ec9ed39e7d2e960742f7329' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/parser/token_stream_rewriter.dart b/pkg/_fe_analyzer_shared/lib/src/parser/token_stream_rewriter.dart
index c3e261a..df1e467 100644
--- a/pkg/_fe_analyzer_shared/lib/src/parser/token_stream_rewriter.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/parser/token_stream_rewriter.dart
@@ -18,7 +18,7 @@
Token,
TokenType;
-abstract class TokenStreamRewriter with _TokenStreamMixin {
+abstract class TokenStreamRewriter {
/// Insert a synthetic open and close parenthesis and return the new synthetic
/// open parenthesis. If [insertIdentifier] is true, then a synthetic
/// identifier is included between the open and close parenthesis.
@@ -135,6 +135,26 @@
return replacement;
}
+ /// Insert a synthetic identifier after [token] and return the new identifier.
+ Token insertSyntheticIdentifier(Token token, [String value]) {
+ return insertToken(
+ token,
+ new SyntheticStringToken(TokenType.IDENTIFIER, value ?? '',
+ token.next.charOffset, /* _length = */ 0));
+ }
+
+ /// Insert a new synthetic [keyword] after [token] and return the new token.
+ Token insertSyntheticKeyword(Token token, Keyword keyword) => insertToken(
+ token, new SyntheticKeywordToken(keyword, token.next.charOffset));
+
+ /// Insert a new simple synthetic token of [newTokenType] after [token]
+ /// and return the new token.
+ Token insertSyntheticToken(Token token, TokenType newTokenType) {
+ assert(newTokenType is! Keyword, 'use insertSyntheticKeyword instead');
+ return insertToken(
+ token, new SyntheticToken(newTokenType, token.next.charOffset));
+ }
+
Token _setNext(Token setOn, Token nextToken);
void _setEndGroup(BeginToken setOn, Token endGroup);
void _setOffset(Token setOn, int offset);
@@ -186,123 +206,6 @@
}
}
-/// Provides the capability of adding tokens that lead into a token stream
-/// without modifying the original token stream and not setting the any token's
-/// `previous` field.
-class TokenStreamGhostWriter
- with _TokenStreamMixin
- implements TokenStreamRewriter {
- @override
- Token insertParens(Token token, bool includeIdentifier) {
- Token next = token.next;
- int offset = next.charOffset;
- BeginToken leftParen =
- next = new SyntheticBeginToken(TokenType.OPEN_PAREN, offset);
- if (includeIdentifier) {
- Token identifier = new SyntheticStringToken(
- TokenType.IDENTIFIER, '', offset, /* _length = */ 0);
- next.next = identifier;
- next = identifier;
- }
- Token rightParen = new SyntheticToken(TokenType.CLOSE_PAREN, offset);
- next.next = rightParen;
- rightParen.next = token.next;
-
- return leftParen;
- }
-
- @override
- Token insertToken(Token token, Token newToken) {
- newToken.next = token.next;
- return newToken;
- }
-
- @override
- Token moveSynthetic(Token token, Token endGroup) {
- Token newEndGroup =
- new SyntheticToken(endGroup.type, token.next.charOffset);
- newEndGroup.next = token.next;
- return newEndGroup;
- }
-
- @override
- Token replaceTokenFollowing(Token previousToken, Token replacementToken) {
- Token replacedToken = previousToken.next;
-
- (replacementToken as SimpleToken).precedingComments =
- replacedToken.precedingComments;
-
- _lastTokenInChain(replacementToken).next = replacedToken.next;
- return replacementToken;
- }
-
- /// Given the [firstToken] in a chain of tokens to be inserted, return the
- /// last token in the chain.
- Token _lastTokenInChain(Token firstToken) {
- Token current = firstToken;
- while (current.next != null && current.next.type != TokenType.EOF) {
- current = current.next;
- }
- return current;
- }
-
- @override
- void _setEndGroup(BeginToken setOn, Token endGroup) {
- throw new UnimplementedError("_setEndGroup");
- }
-
- @override
- Token _setNext(Token setOn, Token nextToken) {
- throw new UnimplementedError("_setNext");
- }
-
- @override
- void _setOffset(Token setOn, int offset) {
- throw new UnimplementedError("_setOffset");
- }
-
- @override
- void _setPrecedingComments(SimpleToken setOn, CommentToken comment) {
- throw new UnimplementedError("_setPrecedingComments");
- }
-
- @override
- void _setPrevious(Token setOn, Token previous) {
- throw new UnimplementedError("_setPrevious");
- }
-
- @override
- ReplacementToken replaceNextTokenWithSyntheticToken(
- Token previousToken, TokenType newTokenType) {
- throw new UnimplementedError("replaceWithSyntheticToken");
- }
-}
-
-mixin _TokenStreamMixin {
- /// Insert a synthetic identifier after [token] and return the new identifier.
- Token insertSyntheticIdentifier(Token token, [String value]) {
- return insertToken(
- token,
- new SyntheticStringToken(TokenType.IDENTIFIER, value ?? '',
- token.next.charOffset, /* _length = */ 0));
- }
-
- /// Insert a new synthetic [keyword] after [token] and return the new token.
- Token insertSyntheticKeyword(Token token, Keyword keyword) => insertToken(
- token, new SyntheticKeywordToken(keyword, token.next.charOffset));
-
- /// Insert a new simple synthetic token of [newTokenType] after [token]
- /// and return the new token.
- Token insertSyntheticToken(Token token, TokenType newTokenType) {
- assert(newTokenType is! Keyword, 'use insertSyntheticKeyword instead');
- return insertToken(
- token, new SyntheticToken(newTokenType, token.next.charOffset));
- }
-
- /// Insert [newToken] after [token] and return [newToken].
- Token insertToken(Token token, Token newToken);
-}
-
abstract class TokenStreamChange {
void undo();
}
diff --git a/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart b/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart
index c1dae7d..5445a78 100644
--- a/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart
+++ b/pkg/front_end/test/fasta/parser/token_stream_rewriter_test.dart
@@ -13,9 +13,9 @@
main() {
defineReflectiveSuite(() {
- defineReflectiveTests(TokenStreamGhostWriterTest);
defineReflectiveTests(TokenStreamRewriterTest_NoPrevious);
defineReflectiveTests(TokenStreamRewriterTest_UsingPrevious);
+ defineReflectiveTests(TokenStreamRewriterTest_Undoable);
});
}
@@ -24,13 +24,20 @@
/// Indicates whether the tests should set up [Token.previous].
bool get setPrevious;
+ TokenStreamRewriter getTokenStreamRewriter();
+
+ void setupDone(Token first) {}
+ void normalTestDone(TokenStreamRewriter rewriter, Token first) {}
+
void test_insertParens() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var eof = _link([a, b]);
- var rewriter = new TokenStreamRewriterImpl();
- var openParen = rewriter.insertParens(a, false);
- var closeParen = openParen.next;
+ Token a = _makeToken(0, 'a');
+ Token b = _makeToken(1, 'b');
+ Token eof = _link([a, b]);
+ setupDone(a);
+
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
+ Token openParen = rewriter.insertParens(a, false);
+ Token closeParen = openParen.next;
expect(openParen.lexeme, '(');
expect(closeParen.lexeme, ')');
@@ -43,16 +50,20 @@
expect(b.previous, same(closeParen));
expect(closeParen.previous, same(openParen));
expect(openParen.previous, same(a));
+
+ normalTestDone(rewriter, a);
}
void test_insertParensWithIdentifier() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var eof = _link([a, b]);
- var rewriter = new TokenStreamRewriterImpl();
- var openParen = rewriter.insertParens(a, true);
- var identifier = openParen.next;
- var closeParen = identifier.next;
+ Token a = _makeToken(0, 'a');
+ Token b = _makeToken(1, 'b');
+ Token eof = _link([a, b]);
+ setupDone(a);
+
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
+ Token openParen = rewriter.insertParens(a, true);
+ Token identifier = openParen.next;
+ Token closeParen = identifier.next;
expect(openParen.lexeme, '(');
expect(identifier.lexeme, '');
@@ -69,14 +80,18 @@
expect(closeParen.previous, same(identifier));
expect(identifier.previous, same(openParen));
expect(openParen.previous, same(a));
+
+ normalTestDone(rewriter, a);
}
void test_insertSyntheticIdentifier() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var eof = _link([a, b]);
- var rewriter = new TokenStreamRewriterImpl();
- var identifier = rewriter.insertSyntheticIdentifier(a);
+ Token a = _makeToken(0, 'a');
+ Token b = _makeToken(1, 'b');
+ Token eof = _link([a, b]);
+ setupDone(a);
+
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
+ Token identifier = rewriter.insertSyntheticIdentifier(a);
expect(identifier.lexeme, '');
expect(identifier.isSynthetic, isTrue);
@@ -87,39 +102,51 @@
expect(b.previous, same(identifier));
expect(identifier.previous, same(a));
+
+ normalTestDone(rewriter, a);
}
void test_insertToken_end() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var eof = _link([a]);
- var rewriter = new TokenStreamRewriterImpl();
+ Token a = _makeToken(0, 'a');
+ Token b = _makeToken(1, 'b');
+ Token eof = _link([a]);
+ setupDone(a);
+
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
expect(rewriter.insertToken(a, b), same(b));
expect(a.next, same(b));
expect(b.next, same(eof));
expect(eof.previous, same(b));
expect(b.previous, same(a));
+
+ normalTestDone(rewriter, a);
}
void test_insertToken_middle() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var c = _makeToken(2, 'c');
+ Token a = _makeToken(0, 'a');
+ Token b = _makeToken(1, 'b');
+ Token c = _makeToken(2, 'c');
_link([a, c]);
- var rewriter = new TokenStreamRewriterImpl();
+ setupDone(a);
+
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
rewriter.insertToken(a, b);
expect(a.next, same(b));
expect(b.next, same(c));
+
+ normalTestDone(rewriter, a);
}
void test_insertToken_second_insertion_earlier_in_stream() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var c = _makeToken(2, 'c');
- var d = _makeToken(3, 'd');
- var e = _makeToken(4, 'e');
+ Token a = _makeToken(0, 'a');
+ Token b = _makeToken(1, 'b');
+ Token c = _makeToken(2, 'c');
+ Token d = _makeToken(3, 'd');
+ Token e = _makeToken(4, 'e');
_link([a, c, e]);
- var rewriter = new TokenStreamRewriterImpl();
+ setupDone(a);
+
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
rewriter.insertToken(c, d);
expect(c.next, same(d));
expect(d.next, same(e));
@@ -128,18 +155,21 @@
rewriter.insertToken(a, b);
expect(a.next, same(b));
expect(b.next, same(c));
+
+ normalTestDone(rewriter, a);
}
void test_replaceNextTokenWithSyntheticToken_1() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(5, 'b');
+ Token a = _makeToken(0, 'a');
+ StringToken b = _makeToken(5, 'b');
b.precedingComments = new CommentToken.fromSubstring(
TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
canonicalize: true);
- var c = _makeToken(10, 'c');
+ Token c = _makeToken(10, 'c');
_link([a, b, c]);
+ setupDone(a);
- var rewriter = new TokenStreamRewriterImpl();
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
ReplacementToken replacement =
rewriter.replaceNextTokenWithSyntheticToken(a, TokenType.AMPERSAND);
expect(b.offset, same(replacement.offset));
@@ -149,17 +179,20 @@
expect(a.next, same(replacement));
expect(replacement.next, same(c));
expect(c.next.isEof, true);
+
+ normalTestDone(rewriter, a);
}
void test_replaceNextTokenWithSyntheticToken_2() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(5, 'b');
+ Token a = _makeToken(0, 'a');
+ StringToken b = _makeToken(5, 'b');
b.precedingComments = new CommentToken.fromSubstring(
TokenType.SINGLE_LINE_COMMENT, "Test comment", 1, 9, 1,
canonicalize: true);
_link([a, b]);
+ setupDone(a);
- var rewriter = new TokenStreamRewriterImpl();
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
ReplacementToken replacement =
rewriter.replaceNextTokenWithSyntheticToken(a, TokenType.AMPERSAND);
expect(b.offset, same(replacement.offset));
@@ -168,52 +201,67 @@
expect(a.next, same(replacement));
expect(replacement.next.isEof, true);
+
+ normalTestDone(rewriter, a);
}
void test_moveSynthetic() {
ScannerResult scanResult = scanString('Foo(bar; baz=0;');
expect(scanResult.hasErrors, isTrue);
+ Token firstToken = scanResult.tokens;
+ setupDone(firstToken);
+
Token open = scanResult.tokens.next.next;
expect(open.lexeme, '(');
Token close = open.endGroup;
expect(close.isSynthetic, isTrue);
expect(close.next.isEof, isTrue);
- var rewriter = new TokenStreamRewriterImpl();
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
Token result = rewriter.moveSynthetic(open.next, close);
expect(result, close);
expect(open.endGroup, close);
expect(open.next.next, close);
expect(close.next.isEof, isFalse);
+
+ normalTestDone(rewriter, firstToken);
}
void test_replaceTokenFollowing_multiple() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var c = _makeToken(2, 'c');
- var d = _makeToken(3, 'd');
- var e = _makeToken(4, 'e');
- var f = _makeToken(5, 'f');
+ Token a = _makeToken(0, 'a');
+ Token b = _makeToken(1, 'b');
+ Token c = _makeToken(2, 'c');
+ Token d = _makeToken(3, 'd');
+ Token e = _makeToken(4, 'e');
+ Token f = _makeToken(5, 'f');
_link([a, b, e, f]);
_link([c, d]);
- var rewriter = new TokenStreamRewriterImpl();
+ setupDone(a);
+
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
rewriter.replaceTokenFollowing(b, c);
expect(a.next, same(b));
expect(b.next, same(c));
expect(c.next, same(d));
expect(d.next, same(f));
+
+ normalTestDone(rewriter, a);
}
void test_replaceTokenFollowing_single() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var c = _makeToken(2, 'c');
- var d = _makeToken(3, 'd');
+ Token a = _makeToken(0, 'a');
+ Token b = _makeToken(1, 'b');
+ Token c = _makeToken(2, 'c');
+ Token d = _makeToken(3, 'd');
_link([a, b, d]);
- var rewriter = new TokenStreamRewriterImpl();
+ setupDone(a);
+
+ TokenStreamRewriter rewriter = getTokenStreamRewriter();
rewriter.replaceTokenFollowing(a, c);
expect(a.next, same(c));
expect(c.next, same(d));
+
+ normalTestDone(rewriter, a);
}
/// Links together the given [tokens] and adds an EOF token to the end of the
@@ -223,7 +271,7 @@
Token _link(Iterable<Token> tokens) {
Token head = new Token.eof(-1);
if (!setPrevious) head.previous = null;
- for (var token in tokens) {
+ for (Token token in tokens) {
head.next = token;
if (setPrevious) token.previous = head;
head = token;
@@ -251,6 +299,8 @@
class TokenStreamRewriterTest_NoPrevious extends TokenStreamRewriterTest {
@override
bool get setPrevious => false;
+
+ TokenStreamRewriter getTokenStreamRewriter() => new TokenStreamRewriterImpl();
}
/// Concrete implementation of [TokenStreamRewriterTest] in which
@@ -263,202 +313,78 @@
class TokenStreamRewriterTest_UsingPrevious extends TokenStreamRewriterTest {
@override
bool get setPrevious => true;
+
+ TokenStreamRewriter getTokenStreamRewriter() => new TokenStreamRewriterImpl();
}
+/// Concrete implementation of [TokenStreamRewriterTest] in which
+/// the [UndoableTokenStreamRewriter] is used.
@reflectiveTest
-class TokenStreamGhostWriterTest extends TokenStreamRewriterTest {
+class TokenStreamRewriterTest_Undoable extends TokenStreamRewriterTest {
@override
- bool get setPrevious => false;
+ bool get setPrevious => true;
- void test_insertParens() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var eof = _link([a, b]);
- var rewriter = new TokenStreamGhostWriter();
- var openParen = rewriter.insertParens(a, false);
- var closeParen = openParen.next;
+ TokenStreamRewriter getTokenStreamRewriter() =>
+ new UndoableTokenStreamRewriter();
- expect(openParen.lexeme, '(');
- expect(closeParen.lexeme, ')');
+ List<CachedTokenSetup> setup;
- expect(a.next, same(b));
- expect(openParen.next, same(closeParen));
- expect(closeParen.next, same(b));
- expect(b.next, same(eof));
-
- expect(b.previous, isNull);
- expect(closeParen.previous, isNull);
- expect(openParen.previous, isNull);
+ void setupDone(Token first) {
+ setup = [];
+ Token token = first;
+ while (token != null && !token.isEof) {
+ setup.add(new CachedTokenSetup(token));
+ token = token.next;
+ }
}
- void test_insertParensWithIdentifier() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var eof = _link([a, b]);
- var rewriter = new TokenStreamGhostWriter();
- var openParen = rewriter.insertParens(a, true);
- var identifier = openParen.next;
- var closeParen = identifier.next;
+ void normalTestDone(TokenStreamRewriter rewriter, Token first) {
+ UndoableTokenStreamRewriter undoableTokenStreamRewriter = rewriter;
+ undoableTokenStreamRewriter.undo();
+ List<CachedTokenSetup> now = [];
+ Token token = first;
+ while (token != null && !token.isEof) {
+ now.add(new CachedTokenSetup(token));
+ token = token.next;
+ }
+ if (setup.length != now.length) {
+ throw "Different length: ${setup.length} vs ${now.length}";
+ }
+ for (int i = 0; i < setup.length; i++) {
+ if (setup[i] != now[i]) {
+ throw "Different at $i: ${setup[i]} vs ${now[i]}";
+ }
+ }
+ setup = null;
+ }
+}
- expect(openParen.lexeme, '(');
- expect(identifier.lexeme, '');
- expect(identifier.isSynthetic, isTrue);
- expect(closeParen.lexeme, ')');
+class CachedTokenSetup {
+ final Token token;
+ final Token prev;
+ final Token next;
+ final Token precedingComments;
- expect(a.next, same(b));
- expect(openParen.next, same(identifier));
- expect(identifier.next, same(closeParen));
- expect(closeParen.next, same(b));
- expect(b.next, same(eof));
+ CachedTokenSetup(this.token)
+ : prev = token.previous,
+ next = token.next,
+ precedingComments = token.precedingComments;
- expect(b.previous, isNull);
- expect(closeParen.previous, isNull);
- expect(identifier.previous, isNull);
- expect(openParen.previous, isNull);
+ bool operator ==(Object other) {
+ if (other is! CachedTokenSetup) return false;
+ CachedTokenSetup o = other;
+ return token == o.token &&
+ prev == o.prev &&
+ next == o.next &&
+ precedingComments == o.precedingComments;
}
- void test_insertSyntheticIdentifier() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var eof = _link([a, b]);
- var rewriter = new TokenStreamGhostWriter();
- var identifier = rewriter.insertSyntheticIdentifier(a);
-
- expect(identifier.lexeme, '');
- expect(identifier.isSynthetic, isTrue);
-
- expect(a.next, same(b));
- expect(identifier.next, same(b));
- expect(b.next, same(eof));
-
- expect(b.previous, isNull);
- expect(identifier.previous, isNull);
- }
-
- void test_insertToken_end() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var eof = _link([a]);
- var rewriter = new TokenStreamGhostWriter();
-
- expect(rewriter.insertToken(a, b), same(b));
- expect(a.next, same(eof));
- expect(b.next, same(eof));
-
- expect(eof.previous, isNull);
- expect(b.previous, isNull);
- }
-
- void test_insertToken_middle() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var c = _makeToken(2, 'c');
- _link([a, c]);
-
- var rewriter = new TokenStreamGhostWriter();
- rewriter.insertToken(a, b);
- expect(a.next, same(c));
- expect(b.next, same(c));
-
- expect(a.previous, isNull);
- expect(b.previous, isNull);
- expect(c.previous, isNull);
- }
-
- void test_insertToken_second_insertion_earlier_in_stream() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var c = _makeToken(2, 'c');
- var d = _makeToken(3, 'd');
- var e = _makeToken(4, 'e');
- _link([a, c, e]);
- var rewriter = new TokenStreamGhostWriter();
-
- rewriter.insertToken(c, d);
- expect(c.next, same(e));
- expect(d.next, same(e));
-
- // The next call to rewriter should be able to find the insertion point
- // even though it is before the insertion point used above.
- rewriter.insertToken(a, b);
- expect(a.next, same(c));
- expect(b.next, same(c));
-
- expect(a.previous, isNull);
- expect(b.previous, isNull);
- expect(c.previous, isNull);
- expect(d.previous, isNull);
- expect(e.previous, isNull);
- }
-
- void test_moveSynthetic() {
- ScannerResult scanResult = scanString('Foo(bar; baz=0;');
- expect(scanResult.hasErrors, isTrue);
- Token open = scanResult.tokens.next.next;
- expect(open.lexeme, '(');
- Token semicolon = open.next.next;
- expect(semicolon.lexeme, ';');
- Token close = open.endGroup;
- expect(close.isSynthetic, isTrue);
- expect(close.next.isEof, isTrue);
- Token semicolon2 = close.previous;
- expect(semicolon2.lexeme, ';');
- var rewriter = new TokenStreamGhostWriter();
-
- Token newClose = rewriter.moveSynthetic(open.next, close);
- expect(newClose, isNot(same(close)));
- expect(newClose.next, same(semicolon));
- expect(open.endGroup, close);
- expect(open.next.next, semicolon);
- expect(close.next.isEof, isTrue);
-
- expect(newClose.previous, isNull);
- expect(close.next.previous, close);
- expect(close.previous, semicolon2);
- }
-
- void test_replaceTokenFollowing_multiple() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var c = _makeToken(2, 'c');
- var d = _makeToken(3, 'd');
- var e = _makeToken(4, 'e');
- var f = _makeToken(5, 'f');
- _link([a, b, e, f]);
- _link([c, d]);
- var rewriter = new TokenStreamGhostWriter();
- Token result = rewriter.replaceTokenFollowing(b, c);
-
- expect(result, same(c));
- expect(a.next, same(b));
- expect(b.next, same(e));
- expect(e.next, same(f));
- expect(c.next, same(d));
- expect(d.next, same(f));
-
- expect(a.previous, isNull);
- expect(b.previous, isNull);
- expect(c.previous, isNull);
- expect(d.previous, isNull);
- expect(e.previous, isNull);
- }
-
- void test_replaceTokenFollowing_single() {
- var a = _makeToken(0, 'a');
- var b = _makeToken(1, 'b');
- var c = _makeToken(2, 'c');
- var d = _makeToken(3, 'd');
- _link([a, b, d]);
- var rewriter = new TokenStreamGhostWriter();
- Token result = rewriter.replaceTokenFollowing(a, c);
-
- expect(result, same(c));
- expect(a.next, same(b));
- expect(b.next, same(d));
- expect(c.next, same(d));
-
- expect(a.previous, isNull);
- expect(b.previous, isNull);
- expect(c.previous, isNull);
+ String toString() {
+ return "CachedTokenSetup["
+ "token = $token, "
+ "prev = $prev, "
+ "next = $next, "
+ "precedingComments = $precedingComments"
+ "]";
}
}
diff --git a/runtime/tests/vm/dart/write_barrier_register_clobber_test.dart b/runtime/tests/vm/dart/write_barrier_register_clobber_test.dart
new file mode 100644
index 0000000..ceff211
--- /dev/null
+++ b/runtime/tests/vm/dart/write_barrier_register_clobber_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This test attempts to verify that write barrier slow path does not
+// clobber any live values.
+
+import 'dart:_internal' show VMInternalsForTesting;
+
+import 'package:expect/expect.dart';
+
+class Old {
+ var f;
+ Old(this.f);
+}
+
+@pragma('vm:never-inline')
+int crashy(int v, List<Old> oldies) {
+ // This test attempts to create a lot of live values which would live across
+ // write barrier invocation so that when write-barrier calls runtime and
+ // clobbers a register this is detected.
+ var young = Object();
+ var len = oldies.length;
+ var i = 0;
+ var v00 = v + 0;
+ var v01 = v + 1;
+ var v02 = v + 2;
+ var v03 = v + 3;
+ var v04 = v + 4;
+ var v05 = v + 5;
+ var v06 = v + 6;
+ var v07 = v + 7;
+ var v08 = v + 8;
+ var v09 = v + 9;
+ var v10 = v + 10;
+ var v11 = v + 11;
+ var v12 = v + 12;
+ var v13 = v + 13;
+ var v14 = v + 14;
+ var v15 = v + 15;
+ var v16 = v + 16;
+ var v17 = v + 17;
+ var v18 = v + 18;
+ var v19 = v + 19;
+ while (i < len) {
+ // Eventually this will overflow store buffer and call runtime to acquire
+ // a new block.
+ oldies[i++].f = young;
+ }
+ return v00 +
+ v01 +
+ v02 +
+ v03 +
+ v04 +
+ v05 +
+ v06 +
+ v07 +
+ v08 +
+ v09 +
+ v10 +
+ v11 +
+ v12 +
+ v13 +
+ v14 +
+ v15 +
+ v16 +
+ v17 +
+ v18 +
+ v19;
+}
+
+void main(List<String> args) {
+ final init = args.contains('impossible') ? 1 : 0;
+ final oldies = List<Old>.generate(100000, (i) => Old(""));
+ VMInternalsForTesting.collectAllGarbage();
+ VMInternalsForTesting.collectAllGarbage();
+ Expect.equals(crashy(init, oldies), 190);
+ for (var o in oldies) {
+ Expect.isTrue(o.f is! String);
+ }
+}
diff --git a/runtime/tests/vm/dart_2/write_barrier_register_clobber_test.dart b/runtime/tests/vm/dart_2/write_barrier_register_clobber_test.dart
new file mode 100644
index 0000000..ceff211
--- /dev/null
+++ b/runtime/tests/vm/dart_2/write_barrier_register_clobber_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This test attempts to verify that write barrier slow path does not
+// clobber any live values.
+
+import 'dart:_internal' show VMInternalsForTesting;
+
+import 'package:expect/expect.dart';
+
+class Old {
+ var f;
+ Old(this.f);
+}
+
+@pragma('vm:never-inline')
+int crashy(int v, List<Old> oldies) {
+ // This test attempts to create a lot of live values which would live across
+ // write barrier invocation so that when write-barrier calls runtime and
+ // clobbers a register this is detected.
+ var young = Object();
+ var len = oldies.length;
+ var i = 0;
+ var v00 = v + 0;
+ var v01 = v + 1;
+ var v02 = v + 2;
+ var v03 = v + 3;
+ var v04 = v + 4;
+ var v05 = v + 5;
+ var v06 = v + 6;
+ var v07 = v + 7;
+ var v08 = v + 8;
+ var v09 = v + 9;
+ var v10 = v + 10;
+ var v11 = v + 11;
+ var v12 = v + 12;
+ var v13 = v + 13;
+ var v14 = v + 14;
+ var v15 = v + 15;
+ var v16 = v + 16;
+ var v17 = v + 17;
+ var v18 = v + 18;
+ var v19 = v + 19;
+ while (i < len) {
+ // Eventually this will overflow store buffer and call runtime to acquire
+ // a new block.
+ oldies[i++].f = young;
+ }
+ return v00 +
+ v01 +
+ v02 +
+ v03 +
+ v04 +
+ v05 +
+ v06 +
+ v07 +
+ v08 +
+ v09 +
+ v10 +
+ v11 +
+ v12 +
+ v13 +
+ v14 +
+ v15 +
+ v16 +
+ v17 +
+ v18 +
+ v19;
+}
+
+void main(List<String> args) {
+ final init = args.contains('impossible') ? 1 : 0;
+ final oldies = List<Old>.generate(100000, (i) => Old(""));
+ VMInternalsForTesting.collectAllGarbage();
+ VMInternalsForTesting.collectAllGarbage();
+ Expect.equals(crashy(init, oldies), 190);
+ for (var o in oldies) {
+ Expect.isTrue(o.f is! String);
+ }
+}
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.cc b/runtime/vm/compiler/assembler/assembler_arm64.cc
index 2118d49..4699f5d 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm64.cc
@@ -1485,7 +1485,7 @@
StoreToOffset(state, THR, target::Thread::exit_through_ffi_offset());
}
-void Assembler::EnterCallRuntimeFrame(intptr_t frame_size) {
+void Assembler::EnterCallRuntimeFrame(intptr_t frame_size, bool is_leaf) {
Comment("EnterCallRuntimeFrame");
EnterFrame(0);
if (!(FLAG_precompiled_mode && FLAG_use_bare_instructions)) {
@@ -1510,19 +1510,30 @@
Push(reg);
}
- ReserveAlignedFrameSpace(frame_size);
+ if (!is_leaf) { // Leaf calling sequence aligns the stack itself.
+ ReserveAlignedFrameSpace(frame_size);
+ } else {
+ PushPair(kCallLeafRuntimeCalleeSaveScratch1,
+ kCallLeafRuntimeCalleeSaveScratch2);
+ }
}
-void Assembler::LeaveCallRuntimeFrame() {
+void Assembler::LeaveCallRuntimeFrame(bool is_leaf) {
// SP might have been modified to reserve space for arguments
// and ensure proper alignment of the stack frame.
// We need to restore it before restoring registers.
+ const intptr_t fixed_frame_words_without_pc_and_fp =
+ target::frame_layout.dart_fixed_frame_size - 2;
const intptr_t kPushedRegistersSize =
- kDartVolatileCpuRegCount * target::kWordSize +
- kDartVolatileFpuRegCount * target::kWordSize +
- (target::frame_layout.dart_fixed_frame_size - 2) *
- target::kWordSize; // From EnterStubFrame (excluding PC / FP)
+ kDartVolatileFpuRegCount * sizeof(double) +
+ (kDartVolatileCpuRegCount + (is_leaf ? 2 : 0) +
+ fixed_frame_words_without_pc_and_fp) *
+ target::kWordSize;
AddImmediate(SP, FP, -kPushedRegistersSize);
+ if (is_leaf) {
+ PopPair(kCallLeafRuntimeCalleeSaveScratch1,
+ kCallLeafRuntimeCalleeSaveScratch2);
+ }
for (int i = kDartLastVolatileCpuReg; i >= kDartFirstVolatileCpuReg; i--) {
const Register reg = static_cast<Register>(i);
Pop(reg);
@@ -1547,6 +1558,37 @@
entry.Call(this, argument_count);
}
+void Assembler::CallRuntimeScope::Call(intptr_t argument_count) {
+ assembler_->CallRuntime(entry_, argument_count);
+}
+
+Assembler::CallRuntimeScope::~CallRuntimeScope() {
+ if (preserve_registers_) {
+ assembler_->LeaveCallRuntimeFrame(entry_.is_leaf());
+ if (restore_code_reg_) {
+ assembler_->Pop(CODE_REG);
+ }
+ }
+}
+
+Assembler::CallRuntimeScope::CallRuntimeScope(Assembler* assembler,
+ const RuntimeEntry& entry,
+ intptr_t frame_size,
+ bool preserve_registers,
+ const Address* caller)
+ : assembler_(assembler),
+ entry_(entry),
+ preserve_registers_(preserve_registers),
+ restore_code_reg_(caller != nullptr) {
+ if (preserve_registers_) {
+ if (caller != nullptr) {
+ assembler_->Push(CODE_REG);
+ assembler_->ldr(CODE_REG, *caller);
+ }
+ assembler_->EnterCallRuntimeFrame(frame_size, entry.is_leaf());
+ }
+}
+
void Assembler::EnterStubFrame() {
EnterDartFrame(0);
}
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index 5c1fb60..c9bd624 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -1651,10 +1651,51 @@
void EnterOsrFrame(intptr_t extra_size, Register new_pp = kNoRegister);
void LeaveDartFrame(RestorePP restore_pp = kRestoreCallerPP);
- void EnterCallRuntimeFrame(intptr_t frame_size);
- void LeaveCallRuntimeFrame();
void CallRuntime(const RuntimeEntry& entry, intptr_t argument_count);
+ // Helper method for performing runtime calls from callers requiring manual
+ // register preservation is required (e.g. outside IL instructions marked
+ // as calling).
+ class CallRuntimeScope : public ValueObject {
+ public:
+ CallRuntimeScope(Assembler* assembler,
+ const RuntimeEntry& entry,
+ intptr_t frame_size,
+ bool preserve_registers = true)
+ : CallRuntimeScope(assembler,
+ entry,
+ frame_size,
+ preserve_registers,
+ /*caller=*/nullptr) {}
+
+ CallRuntimeScope(Assembler* assembler,
+ const RuntimeEntry& entry,
+ intptr_t frame_size,
+ Address caller,
+ bool preserve_registers = true)
+ : CallRuntimeScope(assembler,
+ entry,
+ frame_size,
+ preserve_registers,
+ &caller) {}
+
+ void Call(intptr_t argument_count);
+
+ ~CallRuntimeScope();
+
+ private:
+ CallRuntimeScope(Assembler* assembler,
+ const RuntimeEntry& entry,
+ intptr_t frame_size,
+ bool preserve_registers,
+ const Address* caller);
+
+ Assembler* const assembler_;
+ const RuntimeEntry& entry_;
+ const bool preserve_registers_;
+ const bool restore_code_reg_;
+ };
+
// Set up a stub frame so that the stack traversal code can easily identify
// a stub frame.
void EnterStubFrame();
@@ -2402,6 +2443,11 @@
CanBeSmi can_be_smi,
BarrierFilterMode barrier_filter_mode);
+ // Note: leaf call sequence uses some abi callee save registers as scratch
+ // so they should be manually preserved.
+ void EnterCallRuntimeFrame(intptr_t frame_size, bool is_leaf);
+ void LeaveCallRuntimeFrame(bool is_leaf);
+
friend class dart::FlowGraphCompiler;
std::function<void(Register reg)> generate_invoke_write_barrier_wrapper_;
std::function<void()> generate_invoke_array_write_barrier_;
diff --git a/runtime/vm/compiler/runtime_api.cc b/runtime/vm/compiler/runtime_api.cc
index 8a16400..0aecfbd 100644
--- a/runtime/vm/compiler/runtime_api.cc
+++ b/runtime/vm/compiler/runtime_api.cc
@@ -278,6 +278,10 @@
return target::Thread::OffsetFromThread(runtime_entry_);
}
+bool RuntimeEntry::is_leaf() const {
+ return runtime_entry_->is_leaf();
+}
+
namespace target {
const word kOldPageSize = dart::kOldPageSize;
diff --git a/runtime/vm/compiler/runtime_api.h b/runtime/vm/compiler/runtime_api.h
index 4d70b77..1a75739 100644
--- a/runtime/vm/compiler/runtime_api.h
+++ b/runtime/vm/compiler/runtime_api.h
@@ -233,6 +233,8 @@
word OffsetFromThread() const;
+ bool is_leaf() const;
+
protected:
RuntimeEntry(const dart::RuntimeEntry* runtime_entry,
RuntimeEntryCallInternal call)
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 6ff81a8..6f29ad2 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -49,16 +49,12 @@
Label done;
__ tbnz(&done, R0, target::ObjectAlignment::kNewObjectBitPosition);
- if (preserve_registers) {
- __ EnterCallRuntimeFrame(0);
- } else {
- __ ReserveAlignedFrameSpace(0);
- }
- // [R0] already contains first argument.
- __ mov(R1, THR);
- __ CallRuntime(kEnsureRememberedAndMarkingDeferredRuntimeEntry, 2);
- if (preserve_registers) {
- __ LeaveCallRuntimeFrame();
+ {
+ Assembler::CallRuntimeScope scope(
+ assembler, kEnsureRememberedAndMarkingDeferredRuntimeEntry,
+ /*frame_size=*/0, /*preserve_registers=*/preserve_registers);
+ __ mov(R1, THR);
+ scope.Call(/*argument_count=*/2);
}
__ Bind(&done);
@@ -1973,16 +1969,13 @@
// Handle overflow: Call the runtime leaf function.
__ Bind(&overflow);
- // Setup frame, push callee-saved registers.
-
- __ Push(CODE_REG);
- __ ldr(CODE_REG, stub_code);
- __ EnterCallRuntimeFrame(0 * target::kWordSize);
- __ mov(R0, THR);
- __ CallRuntime(kStoreBufferBlockProcessRuntimeEntry, 1);
- // Restore callee-saved registers, tear down frame.
- __ LeaveCallRuntimeFrame();
- __ Pop(CODE_REG);
+ {
+ Assembler::CallRuntimeScope scope(assembler,
+ kStoreBufferBlockProcessRuntimeEntry,
+ /*frame_size=*/0, stub_code);
+ __ mov(R0, THR);
+ scope.Call(/*argument_count=*/1);
+ }
__ ret();
__ Bind(&add_to_mark_stack);
@@ -2020,13 +2013,13 @@
__ ret();
__ Bind(&marking_overflow);
- __ Push(CODE_REG);
- __ ldr(CODE_REG, stub_code);
- __ EnterCallRuntimeFrame(0 * target::kWordSize);
- __ mov(R0, THR);
- __ CallRuntime(kMarkingStackBlockProcessRuntimeEntry, 1);
- __ LeaveCallRuntimeFrame();
- __ Pop(CODE_REG);
+ {
+ Assembler::CallRuntimeScope scope(assembler,
+ kMarkingStackBlockProcessRuntimeEntry,
+ /*frame_size=*/0, stub_code);
+ __ mov(R0, THR);
+ scope.Call(/*argument_count=*/1);
+ }
__ ret();
__ Bind(&lost_race);
@@ -2059,16 +2052,13 @@
// Card table not yet allocated.
__ Bind(&remember_card_slow);
- __ Push(CODE_REG);
- __ PushPair(R0, R1);
- __ ldr(CODE_REG, stub_code);
- __ mov(R0, R1); // Arg0 = Object
- __ mov(R1, R25); // Arg1 = Slot
- __ EnterCallRuntimeFrame(0);
- __ CallRuntime(kRememberCardRuntimeEntry, 2);
- __ LeaveCallRuntimeFrame();
- __ PopPair(R0, R1);
- __ Pop(CODE_REG);
+ {
+ Assembler::CallRuntimeScope scope(assembler, kRememberCardRuntimeEntry,
+ /*frame_size=*/0, stub_code);
+ __ mov(R0, R1); // Arg0 = Object
+ __ mov(R1, R25); // Arg1 = Slot
+ scope.Call(/*argument_count=*/2);
+ }
__ ret();
}
}
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index 1c71baa..b5340bb 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -242,21 +242,22 @@
// See "Procedure Call Standard for the ARM 64-bit Architecture", document
// number "ARM IHI 0055B", May 22 2013.
+#define R(REG) (1 << REG)
+
// C++ ABI call registers.
-const RegList kAbiArgumentCpuRegs = (1 << R0) | (1 << R1) | (1 << R2) |
- (1 << R3) | (1 << R4) | (1 << R5) |
- (1 << R6) | (1 << R7);
+const RegList kAbiArgumentCpuRegs =
+ R(R0) | R(R1) | R(R2) | R(R3) | R(R4) | R(R5) | R(R6) | R(R7);
#if defined(TARGET_OS_FUCHSIA)
-const RegList kAbiPreservedCpuRegs =
- (1 << R18) | (1 << R19) | (1 << R20) | (1 << R21) | (1 << R22) |
- (1 << R23) | (1 << R24) | (1 << R25) | (1 << R26) | (1 << R27) | (1 << R28);
+const RegList kAbiPreservedCpuRegs = R(R18) | R(R19) | R(R20) | R(R21) |
+ R(R22) | R(R23) | R(R24) | R(R25) |
+ R(R26) | R(R27) | R(R28);
const Register kAbiFirstPreservedCpuReg = R18;
const Register kAbiLastPreservedCpuReg = R28;
const int kAbiPreservedCpuRegCount = 11;
#else
-const RegList kAbiPreservedCpuRegs =
- (1 << R19) | (1 << R20) | (1 << R21) | (1 << R22) | (1 << R23) |
- (1 << R24) | (1 << R25) | (1 << R26) | (1 << R27) | (1 << R28);
+const RegList kAbiPreservedCpuRegs = R(R19) | R(R20) | R(R21) | R(R22) |
+ R(R23) | R(R24) | R(R25) | R(R26) |
+ R(R27) | R(R28);
const Register kAbiFirstPreservedCpuReg = R19;
const Register kAbiLastPreservedCpuReg = R28;
const int kAbiPreservedCpuRegCount = 10;
@@ -265,11 +266,11 @@
const VRegister kAbiLastPreservedFpuReg = V15;
const int kAbiPreservedFpuRegCount = 8;
-const intptr_t kReservedCpuRegisters =
- (1 << SPREG) | // Dart SP
- (1 << FPREG) | (1 << TMP) | (1 << TMP2) | (1 << PP) | (1 << THR) |
- (1 << LR) | (1 << BARRIER_MASK) | (1 << NULL_REG) | (1 << R31) | // C++ SP
- (1 << R18) | (1 << DISPATCH_TABLE_REG);
+const intptr_t kReservedCpuRegisters = R(SPREG) | // Dart SP
+ R(FPREG) | R(TMP) | R(TMP2) | R(PP) |
+ R(THR) | R(LR) | R(BARRIER_MASK) |
+ R(NULL_REG) | R(R31) | // C++ SP
+ R(R18) | R(DISPATCH_TABLE_REG);
constexpr intptr_t kNumberOfReservedCpuRegisters = 12;
// CPU registers available to Dart allocator.
const RegList kDartAvailableCpuRegs =
@@ -284,9 +285,17 @@
const int kDartVolatileCpuRegCount = 15;
const int kDartVolatileFpuRegCount = 24;
-constexpr int kStoreBufferWrapperSize = 32;
+// Two callee save scratch registers used by leaf runtime call sequence.
+const Register kCallLeafRuntimeCalleeSaveScratch1 = R23;
+const Register kCallLeafRuntimeCalleeSaveScratch2 = R25;
+static_assert((R(kCallLeafRuntimeCalleeSaveScratch1) & kAbiPreservedCpuRegs) !=
+ 0,
+ "Need callee save scratch register for leaf runtime calls.");
+static_assert((R(kCallLeafRuntimeCalleeSaveScratch2) & kAbiPreservedCpuRegs) !=
+ 0,
+ "Need callee save scratch register for leaf runtime calls.");
-#define R(REG) (1 << REG)
+constexpr int kStoreBufferWrapperSize = 32;
class CallingConventions {
public:
diff --git a/runtime/vm/runtime_entry_arm64.cc b/runtime/vm/runtime_entry_arm64.cc
index 129dae3..5fd14b5 100644
--- a/runtime/vm/runtime_entry_arm64.cc
+++ b/runtime/vm/runtime_entry_arm64.cc
@@ -57,12 +57,12 @@
// call.
// This sequence may occur in an intrinsic, so don't use registers an
// intrinsic must preserve.
- COMPILE_ASSERT(R23 != CODE_REG);
- COMPILE_ASSERT(R25 != CODE_REG);
- COMPILE_ASSERT(R23 != ARGS_DESC_REG);
- COMPILE_ASSERT(R25 != ARGS_DESC_REG);
- __ mov(R23, CSP);
- __ mov(R25, SP);
+ COMPILE_ASSERT(kCallLeafRuntimeCalleeSaveScratch1 != CODE_REG);
+ COMPILE_ASSERT(kCallLeafRuntimeCalleeSaveScratch2 != CODE_REG);
+ COMPILE_ASSERT(kCallLeafRuntimeCalleeSaveScratch1 != ARGS_DESC_REG);
+ COMPILE_ASSERT(kCallLeafRuntimeCalleeSaveScratch2 != ARGS_DESC_REG);
+ __ mov(kCallLeafRuntimeCalleeSaveScratch1, CSP);
+ __ mov(kCallLeafRuntimeCalleeSaveScratch2, SP);
__ ReserveAlignedFrameSpace(0);
__ mov(CSP, SP);
__ ldr(TMP,
@@ -71,8 +71,8 @@
__ blr(TMP);
__ LoadImmediate(TMP, VMTag::kDartCompiledTagId);
__ str(TMP, compiler::Address(THR, Thread::vm_tag_offset()));
- __ mov(SP, R25);
- __ mov(CSP, R23);
+ __ mov(SP, kCallLeafRuntimeCalleeSaveScratch2);
+ __ mov(CSP, kCallLeafRuntimeCalleeSaveScratch1);
ASSERT((kAbiPreservedCpuRegs & (1 << THR)) != 0);
ASSERT((kAbiPreservedCpuRegs & (1 << PP)) != 0);
} else {
diff --git a/tools/VERSION b/tools/VERSION
index 72808e9..06eb9dd 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 10
PATCH 0
-PRERELEASE 116
+PRERELEASE 117
PRERELEASE_PATCH 0
\ No newline at end of file