Version 2.11.0-197.0.dev
Merge commit '0e9b2b8a365a53714aab1aac6a0820720ff93661' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index 1a1c1df..637e387 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -536,6 +536,25 @@
bool ifNullExpression_rightBegin(
Expression leftHandSide, Type leftHandSideType);
+ /// Call this method before visiting the condition part of an if statement.
+ ///
+ /// The order of visiting an if statement with no "else" part should be:
+ /// - Call [ifStatement_conditionBegin]
+ /// - Visit the condition
+ /// - Call [ifStatement_thenBegin]
+ /// - Visit the "then" statement
+ /// - Call [ifStatement_end], passing `false` for `hasElse`.
+ ///
+ /// The order of visiting an if statement with an "else" part should be:
+ /// - Call [ifStatement_conditionBegin]
+ /// - Visit the condition
+ /// - Call [ifStatement_thenBegin]
+ /// - Visit the "then" statement
+ /// - Call [ifStatement_elseBegin]
+ /// - Visit the "else" statement
+ /// - Call [ifStatement_end], passing `true` for `hasElse`.
+ void ifStatement_conditionBegin();
+
/// Call this method after visiting the "then" part of an if statement, and
/// before visiting the "else" part.
void ifStatement_elseBegin();
@@ -545,20 +564,6 @@
/// Call this method after visiting the condition part of an if statement.
/// [condition] should be the if statement's condition.
- ///
- /// The order of visiting an if statement with no "else" part should be:
- /// - Visit the condition
- /// - Call [ifStatement_thenBegin]
- /// - Visit the "then" statement
- /// - Call [ifStatement_end], passing `false` for `hasElse`.
- ///
- /// The order of visiting an if statement with an "else" part should be:
- /// - Visit the condition
- /// - Call [ifStatement_thenBegin]
- /// - Visit the "then" statement
- /// - Call [ifStatement_elseBegin]
- /// - Visit the "else" statement
- /// - Call [ifStatement_end], passing `true` for `hasElse`.
void ifStatement_thenBegin(Expression condition);
/// Return whether the [variable] is definitely assigned in the current state.
@@ -1015,6 +1020,12 @@
}
@override
+ void ifStatement_conditionBegin() {
+ return _wrap('ifStatement_conditionBegin()',
+ () => _wrapped.ifStatement_conditionBegin());
+ }
+
+ @override
void ifStatement_elseBegin() {
return _wrap(
'ifStatement_elseBegin()', () => _wrapped.ifStatement_elseBegin());
@@ -1658,6 +1669,17 @@
FlowModel<Variable, Type> unsplit() =>
new FlowModel<Variable, Type>.withInfo(reachable.unsplit(), variableInfo);
+ /// Removes control flow splits until a [FlowModel] is obtained whose
+ /// reachability has the given [parent].
+ FlowModel<Variable, Type> unsplitTo(Reachability parent) {
+ if (identical(this.reachable.parent, parent)) return this;
+ Reachability reachable = this.reachable.unsplit();
+ while (!identical(reachable.parent, parent)) {
+ reachable = reachable.unsplit();
+ }
+ return new FlowModel<Variable, Type>.withInfo(reachable, variableInfo);
+ }
+
/// Updates the state to indicate that an assignment was made to the given
/// [variable]. The variable is marked as definitely assigned, and any
/// previous type promotion is removed.
@@ -1808,6 +1830,37 @@
return result;
}
+ /// Models the result of joining the flow models [first] and [second] at the
+ /// merge of two control flow paths.
+ static FlowModel<Variable, Type> merge<Variable, Type>(
+ TypeOperations<Variable, Type> typeOperations,
+ FlowModel<Variable, Type> first,
+ FlowModel<Variable, Type> second,
+ Map<Variable, VariableModel<Variable, Type>> emptyVariableMap,
+ ) {
+ if (first == null) return second.unsplit();
+ if (second == null) return first.unsplit();
+
+ assert(identical(first.reachable.parent, second.reachable.parent));
+ if (first.reachable.locallyReachable &&
+ !second.reachable.locallyReachable) {
+ return first.unsplit();
+ }
+ if (!first.reachable.locallyReachable &&
+ second.reachable.locallyReachable) {
+ return second.unsplit();
+ }
+
+ Reachability newReachable =
+ Reachability.join(first.reachable, second.reachable).unsplit();
+ Map<Variable, VariableModel<Variable, Type>> newVariableInfo =
+ FlowModel.joinVariableInfo(typeOperations, first.variableInfo,
+ second.variableInfo, emptyVariableMap);
+
+ return FlowModel._identicalOrNew(
+ first, second, newReachable, newVariableInfo);
+ }
+
/// Creates a new [FlowModel] object, unless it is equivalent to either
/// [first] or [second], in which case one of those objects is re-used.
static FlowModel<Variable, Type> _identicalOrNew<Variable, Type>(
@@ -2554,9 +2607,17 @@
/// `null` if no `continue` statements have been seen yet.
FlowModel<Variable, Type> _continueModel;
+ /// The reachability checkpoint associated with this loop or switch statement.
+ /// When analyzing deeply nested `break` and `continue` statements, their flow
+ /// models need to be unsplit to this point before joining them to the control
+ /// flow paths for the loop or switch.
+ final Reachability _checkpoint;
+
+ _BranchTargetContext(this._checkpoint);
+
@override
String toString() => '_BranchTargetContext(breakModel: $_breakModel, '
- 'continueModel: $_continueModel)';
+ 'continueModel: $_continueModel, checkpoint: $_checkpoint)';
}
/// [_FlowContext] representing a conditional expression.
@@ -2710,7 +2771,7 @@
AssignedVariablesNodeInfo<Variable> info =
_assignedVariables._getInfoForNode(doStatement);
_BranchTargetContext<Variable, Type> context =
- new _BranchTargetContext<Variable, Type>();
+ new _BranchTargetContext<Variable, Type>(_current.reachable.parent);
_stack.add(context);
_current = _current.conservativeJoin(info._written, info._captured);
_statementToContext[doStatement] = context;
@@ -2794,8 +2855,8 @@
ExpressionInfo<Variable, Type> conditionInfo = condition == null
? new ExpressionInfo(_current, _current, _current.setUnreachable())
: _expressionEnd(condition);
- _WhileContext<Variable, Type> context =
- new _WhileContext<Variable, Type>(conditionInfo);
+ _WhileContext<Variable, Type> context = new _WhileContext<Variable, Type>(
+ _current.reachable.parent, conditionInfo);
_stack.add(context);
if (node != null) {
_statementToContext[node] = context;
@@ -2835,7 +2896,8 @@
_assignedVariables._getInfoForNode(node);
_current = _current.conservativeJoin(info._written, info._captured);
_SimpleStatementContext<Variable, Type> context =
- new _SimpleStatementContext<Variable, Type>(_current);
+ new _SimpleStatementContext<Variable, Type>(
+ _current.reachable.parent, _current);
_stack.add(context);
if (loopVariable != null) {
_current = _current.write(loopVariable, writtenType, typeOperations);
@@ -2880,7 +2942,8 @@
void handleBreak(Statement target) {
_BranchTargetContext<Variable, Type> context = _statementToContext[target];
if (context != null) {
- context._breakModel = _join(context._breakModel, _current);
+ context._breakModel =
+ _join(context._breakModel, _current.unsplitTo(context._checkpoint));
}
_current = _current.setUnreachable();
}
@@ -2889,7 +2952,8 @@
void handleContinue(Statement target) {
_BranchTargetContext<Variable, Type> context = _statementToContext[target];
if (context != null) {
- context._continueModel = _join(context._continueModel, _current);
+ context._continueModel = _join(
+ context._continueModel, _current.unsplitTo(context._checkpoint));
}
_current = _current.setUnreachable();
}
@@ -2924,6 +2988,11 @@
}
@override
+ void ifStatement_conditionBegin() {
+ _current = _current.split();
+ }
+
+ @override
void ifStatement_elseBegin() {
_IfContext<Variable, Type> context =
_stack.last as _IfContext<Variable, Type>;
@@ -2944,7 +3013,7 @@
afterThen = _current; // no `else`, so `then` is still current
afterElse = context._conditionInfo.ifFalse;
}
- _current = _join(afterThen, afterElse);
+ _current = _merge(afterThen, afterElse);
}
@override
@@ -2985,7 +3054,7 @@
@override
void labeledStatement_begin(Node node) {
_BranchTargetContext<Variable, Type> context =
- new _BranchTargetContext<Variable, Type>();
+ new _BranchTargetContext<Variable, Type>(_current.reachable.parent);
_stack.add(context);
_statementToContext[node] = context;
}
@@ -3140,7 +3209,8 @@
@override
void switchStatement_expressionEnd(Statement switchStatement) {
_SimpleStatementContext<Variable, Type> context =
- new _SimpleStatementContext<Variable, Type>(_current);
+ new _SimpleStatementContext<Variable, Type>(
+ _current.reachable.parent, _current);
_stack.add(context);
_statementToContext[switchStatement] = context;
}
@@ -3232,8 +3302,8 @@
void whileStatement_bodyBegin(
Statement whileStatement, Expression condition) {
ExpressionInfo<Variable, Type> conditionInfo = _expressionEnd(condition);
- _WhileContext<Variable, Type> context =
- new _WhileContext<Variable, Type>(conditionInfo);
+ _WhileContext<Variable, Type> context = new _WhileContext<Variable, Type>(
+ _current.reachable.parent, conditionInfo);
_stack.add(context);
_statementToContext[whileStatement] = context;
_current = conditionInfo.ifTrue;
@@ -3302,6 +3372,11 @@
FlowModel<Variable, Type> first, FlowModel<Variable, Type> second) =>
FlowModel.join(typeOperations, first, second, _current._emptyVariableMap);
+ FlowModel<Variable, Type> _merge(
+ FlowModel<Variable, Type> first, FlowModel<Variable, Type> second) =>
+ FlowModel.merge(
+ typeOperations, first, second, _current._emptyVariableMap);
+
/// Associates [expression], which should be the most recently visited
/// expression, with the given [expressionInfo] object, and updates the
/// current flow model state to correspond to it.
@@ -3373,11 +3448,13 @@
/// after evaluation of the switch expression.
final FlowModel<Variable, Type> _previous;
- _SimpleStatementContext(this._previous);
+ _SimpleStatementContext(Reachability checkpoint, this._previous)
+ : super(checkpoint);
@override
String toString() => '_SimpleStatementContext(breakModel: $_breakModel, '
- 'continueModel: $_continueModel, previous: $_previous)';
+ 'continueModel: $_continueModel, previous: $_previous, '
+ 'checkpoint: $_checkpoint)';
}
/// [_FlowContext] representing a try statement.
@@ -3430,9 +3507,11 @@
/// Flow models associated with the loop condition.
final ExpressionInfo<Variable, Type> _conditionInfo;
- _WhileContext(this._conditionInfo);
+ _WhileContext(Reachability checkpoint, this._conditionInfo)
+ : super(checkpoint);
@override
String toString() => '_WhileContext(breakModel: $_breakModel, '
- 'continueModel: $_continueModel, conditionInfo: $_conditionInfo)';
+ 'continueModel: $_continueModel, conditionInfo: $_conditionInfo, '
+ 'checkpoint: $_checkpoint)';
}
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
index 16ae7ff..0af6a2f 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
@@ -179,6 +179,7 @@
var x = h.addVar('x', 'int?');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
var varExpr = _Expression();
flow.variableRead(varExpr, x);
flow.equalityOp_rightBegin(varExpr, _Type('int?'));
@@ -203,6 +204,7 @@
var x = h.addVar('x', 'int');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
var varExpr = _Expression();
flow.variableRead(varExpr, x);
flow.equalityOp_rightBegin(varExpr, _Type('int'));
@@ -227,6 +229,7 @@
test('equalityOp(<expr> == <expr>) has no special effect', () {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
flow.equalityOp_rightBegin(_Expression(), _Type('int?'));
var expr = _Expression();
var successIsReachable = flow.equalityOp_end(
@@ -244,6 +247,7 @@
test('equalityOp(<expr> != <expr>) has no special effect', () {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
flow.equalityOp_rightBegin(_Expression(), _Type('int?'));
var expr = _Expression();
var successIsReachable = flow
@@ -262,6 +266,7 @@
var x = h.addVar('x', 'int?');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
var varExpr = _Expression();
flow.variableRead(varExpr, x);
flow.equalityOp_rightBegin(varExpr, _Type('int?'));
@@ -281,6 +286,7 @@
var x = h.addVar('x', 'int?');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
var varExpr = _Expression();
flow.variableRead(varExpr, x);
flow.equalityOp_rightBegin(varExpr, _Type('int?'));
@@ -305,6 +311,7 @@
var x = h.addVar('x', 'int');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
var varExpr = _Expression();
flow.variableRead(varExpr, x);
flow.equalityOp_rightBegin(varExpr, _Type('int'));
@@ -331,6 +338,7 @@
var x = h.addVar('x', 'int?');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
var nullExpr = _Expression();
flow.nullLiteral(nullExpr);
flow.equalityOp_rightBegin(nullExpr, _Type('Null'));
@@ -351,6 +359,7 @@
var x = h.addVar('x', 'int?');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
var nullExpr = _Expression();
flow.equalityOp_rightBegin(nullExpr, _Type('Null'));
var varExpr = _Expression();
@@ -370,6 +379,7 @@
var x = h.addVar('x', 'int?');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
var nullExpr = _Expression();
flow.nullLiteral(nullExpr);
flow.equalityOp_rightBegin(nullExpr, _Type('Null'));
@@ -388,6 +398,7 @@
test('equalityOp(null == null) equivalent to true', () {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
var null1 = _Expression();
flow.equalityOp_rightBegin(null1, _Type('Null'));
var null2 = _Expression();
@@ -406,6 +417,7 @@
test('equalityOp(null != null) equivalent to false', () {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
var null1 = _Expression();
flow.equalityOp_rightBegin(null1, _Type('Null'));
var null2 = _Expression();
@@ -424,6 +436,7 @@
test('equalityOp(null == non-null) is not equivalent to false', () {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
var null1 = _Expression();
flow.equalityOp_rightBegin(null1, _Type('Null'));
var null2 = _Expression();
@@ -443,6 +456,7 @@
test('equalityOp(null != non-null) is not equivalent to true', () {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
var null1 = _Expression();
flow.equalityOp_rightBegin(null1, _Type('Null'));
var null2 = _Expression();
@@ -463,6 +477,7 @@
test('equalityOp(non-null == null) is not equivalent to false', () {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
var null1 = _Expression();
flow.equalityOp_rightBegin(null1, _Type('int'));
var null2 = _Expression();
@@ -483,6 +498,7 @@
test('equalityOp(non-null != null) is not equivalent to true', () {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
var null1 = _Expression();
flow.equalityOp_rightBegin(null1, _Type('int'));
var null2 = _Expression();
@@ -599,6 +615,7 @@
var h = _Harness();
var expr = _Expression();
var flow = h.createFlow();
+ flow.ifStatement_conditionBegin();
flow.ifStatement_thenBegin(expr);
expect(() => flow.finish(), _asserts);
});
@@ -1040,6 +1057,94 @@
});
});
+ test('handleBreak handles deep nesting', () {
+ var h = _Harness();
+ var whileStatement = _Statement();
+ h.assignedVariables((vars) {
+ vars.nest(whileStatement, () {});
+ });
+ h.run((flow) {
+ flow.whileStatement_conditionBegin(whileStatement);
+ flow.whileStatement_bodyBegin(whileStatement, h.booleanLiteral(true)());
+ h.if_(h.expr, () {
+ h.if_(h.expr, () {
+ flow.handleBreak(whileStatement);
+ });
+ });
+ flow.handleExit();
+ expect(flow.isReachable, false);
+ flow.whileStatement_end();
+ expect(flow.isReachable, true);
+ });
+ });
+
+ test('handleBreak handles mixed nesting', () {
+ var h = _Harness();
+ var whileStatement = _Statement();
+ h.assignedVariables((vars) {
+ vars.nest(whileStatement, () {});
+ });
+ h.run((flow) {
+ flow.whileStatement_conditionBegin(whileStatement);
+ flow.whileStatement_bodyBegin(whileStatement, h.booleanLiteral(true)());
+ h.if_(h.expr, () {
+ h.if_(h.expr, () {
+ flow.handleBreak(whileStatement);
+ });
+ flow.handleBreak(whileStatement);
+ });
+ flow.handleBreak(whileStatement);
+ expect(flow.isReachable, false);
+ flow.whileStatement_end();
+ expect(flow.isReachable, true);
+ });
+ });
+
+ test('handleContinue handles deep nesting', () {
+ var h = _Harness();
+ var doStatement = _Statement();
+ h.assignedVariables((vars) {
+ vars.nest(doStatement, () {});
+ });
+ h.run((flow) {
+ flow.doStatement_bodyBegin(doStatement);
+ h.if_(h.expr, () {
+ h.if_(h.expr, () {
+ flow.handleContinue(doStatement);
+ });
+ });
+ flow.handleExit();
+ expect(flow.isReachable, false);
+ flow.doStatement_conditionBegin();
+ expect(flow.isReachable, true);
+ flow.doStatement_end(h.booleanLiteral(true)());
+ expect(flow.isReachable, false);
+ });
+ });
+
+ test('handleContinue handles mixed nesting', () {
+ var h = _Harness();
+ var doStatement = _Statement();
+ h.assignedVariables((vars) {
+ vars.nest(doStatement, () {});
+ });
+ h.run((flow) {
+ flow.doStatement_bodyBegin(doStatement);
+ h.if_(h.expr, () {
+ h.if_(h.expr, () {
+ flow.handleContinue(doStatement);
+ });
+ flow.handleContinue(doStatement);
+ });
+ flow.handleContinue(doStatement);
+ expect(flow.isReachable, false);
+ flow.doStatement_conditionBegin();
+ expect(flow.isReachable, true);
+ flow.doStatement_end(h.booleanLiteral(true)());
+ expect(flow.isReachable, false);
+ });
+ });
+
test('ifNullExpression allows ensure guarding', () {
var h = _Harness();
var x = h.addVar('x', 'int?');
@@ -1119,11 +1224,28 @@
});
});
+ test('ifStatement with early exit promotes in unreachable code', () {
+ var h = _Harness();
+ var x = h.addVar('x', 'int?');
+ h.run((flow) {
+ h.declare(x, initialized: true);
+ flow.handleExit();
+ expect(flow.isReachable, false);
+ flow.ifStatement_conditionBegin();
+ flow.ifStatement_thenBegin(h.eqNull(x, _Type('int?'))());
+ flow.handleExit();
+ flow.ifStatement_end(false);
+ expect(flow.isReachable, false);
+ expect(flow.promotedType(x).type, 'int');
+ });
+ });
+
test('ifStatement_end(false) keeps else branch if then branch exits', () {
var h = _Harness();
var x = h.addVar('x', 'int?');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
flow.ifStatement_thenBegin(h.eqNull(x, _Type('int?'))());
flow.handleExit();
flow.ifStatement_end(false);
@@ -1138,6 +1260,7 @@
var x = h.addVar('x', declaredType);
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
var read = _Expression();
flow.variableRead(read, x);
var expr = _Expression();
@@ -1190,6 +1313,7 @@
test('isExpression_end does nothing if applied to a non-variable', () {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
var subExpr = _Expression();
var expr = _Expression();
var failureReachable =
@@ -1207,6 +1331,7 @@
() {
var h = _Harness();
h.run((flow) {
+ flow.ifStatement_conditionBegin();
var subExpr = _Expression();
var expr = _Expression();
var failureReachable =
@@ -1305,6 +1430,7 @@
var x = h.addVar('x', 'int?');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
flow.logicalBinaryOp_rightBegin(_Expression(), isAnd: true);
var wholeExpr = _Expression();
flow.logicalBinaryOp_end(wholeExpr, h.notNull(x, _Type('int?'))(),
@@ -1321,6 +1447,7 @@
var x = h.addVar('x', 'int?');
h.run((flow) {
h.declare(x, initialized: true);
+ flow.ifStatement_conditionBegin();
flow.logicalBinaryOp_rightBegin(_Expression(), isAnd: false);
var wholeExpr = _Expression();
flow.logicalBinaryOp_end(wholeExpr, h.eqNull(x, _Type('int?'))(),
@@ -2163,6 +2290,7 @@
h.promote(x, 'int');
expect(flow.promotedType(x).type, 'int');
// if (false) {
+ flow.ifStatement_conditionBegin();
var falseExpression = _Expression();
flow.booleanLiteral(falseExpression, false);
flow.ifStatement_thenBegin(falseExpression);
@@ -2328,6 +2456,55 @@
expect(s2.reachable, same(Reachability.initial));
});
+ group('unsplitTo', () {
+ test('no change', () {
+ var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+ var result = s1.unsplitTo(s1.reachable.parent);
+ expect(result, same(s1));
+ });
+
+ test('unsplit once, reachable', () {
+ var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+ var s2 = s1.split();
+ var result = s2.unsplitTo(s1.reachable.parent);
+ expect(result.reachable, same(s1.reachable));
+ });
+
+ test('unsplit once, unreachable', () {
+ var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+ var s2 = s1.split().setUnreachable();
+ var result = s2.unsplitTo(s1.reachable.parent);
+ expect(result.reachable.locallyReachable, false);
+ expect(result.reachable.parent, same(s1.reachable.parent));
+ });
+
+ test('unsplit twice, reachable', () {
+ var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+ var s2 = s1.split();
+ var s3 = s2.split();
+ var result = s3.unsplitTo(s1.reachable.parent);
+ expect(result.reachable, same(s1.reachable));
+ });
+
+ test('unsplit twice, top unreachable', () {
+ var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+ var s2 = s1.split();
+ var s3 = s2.split().setUnreachable();
+ var result = s3.unsplitTo(s1.reachable.parent);
+ expect(result.reachable.locallyReachable, false);
+ expect(result.reachable.parent, same(s1.reachable.parent));
+ });
+
+ test('unsplit twice, previous unreachable', () {
+ var s1 = FlowModel<_Var, _Type>(Reachability.initial.split());
+ var s2 = s1.split().setUnreachable();
+ var s3 = s2.split();
+ var result = s3.unsplitTo(s1.reachable.parent);
+ expect(result.reachable.locallyReachable, false);
+ expect(result.reachable.parent, same(s1.reachable.parent));
+ });
+ });
+
group('tryPromoteForTypeCheck', () {
test('unpromoted -> unchanged (same)', () {
var h = _Harness();
@@ -3581,6 +3758,101 @@
});
});
});
+
+ group('merge', () {
+ var x = _Var('x', _Type('Object?'));
+ var intType = _Type('int');
+ var stringType = _Type('String');
+ const emptyMap = const <_Var, VariableModel<_Var, _Type>>{};
+
+ VariableModel<_Var, _Type> varModel(List<_Type> promotionChain,
+ {bool assigned = false}) =>
+ VariableModel<_Var, _Type>(
+ promotionChain,
+ promotionChain ?? [],
+ assigned,
+ !assigned,
+ false,
+ );
+
+ test('first is null', () {
+ var h = _Harness();
+ var s1 = FlowModel.withInfo(Reachability.initial.split(), {});
+ var result = FlowModel.merge(h, null, s1, emptyMap);
+ expect(result.reachable, same(Reachability.initial));
+ });
+
+ test('second is null', () {
+ var h = _Harness();
+ var splitPoint = Reachability.initial.split();
+ var afterSplit = splitPoint.split();
+ var s1 = FlowModel.withInfo(afterSplit, {});
+ var result = FlowModel.merge(h, s1, null, emptyMap);
+ expect(result.reachable, same(splitPoint));
+ });
+
+ test('both are reachable', () {
+ var h = _Harness();
+ var splitPoint = Reachability.initial.split();
+ var afterSplit = splitPoint.split();
+ var s1 = FlowModel.withInfo(afterSplit, {
+ x: varModel([intType])
+ });
+ var s2 = FlowModel.withInfo(afterSplit, {
+ x: varModel([stringType])
+ });
+ var result = FlowModel.merge(h, s1, s2, emptyMap);
+ expect(result.reachable, same(splitPoint));
+ expect(result.variableInfo[x].promotedTypes, isNull);
+ });
+
+ test('first is unreachable', () {
+ var h = _Harness();
+ var splitPoint = Reachability.initial.split();
+ var afterSplit = splitPoint.split();
+ var s1 = FlowModel.withInfo(afterSplit.setUnreachable(), {
+ x: varModel([intType])
+ });
+ var s2 = FlowModel.withInfo(afterSplit, {
+ x: varModel([stringType])
+ });
+ var result = FlowModel.merge(h, s1, s2, emptyMap);
+ expect(result.reachable, same(splitPoint));
+ expect(result.variableInfo, same(s2.variableInfo));
+ });
+
+ test('second is unreachable', () {
+ var h = _Harness();
+ var splitPoint = Reachability.initial.split();
+ var afterSplit = splitPoint.split();
+ var s1 = FlowModel.withInfo(afterSplit, {
+ x: varModel([intType])
+ });
+ var s2 = FlowModel.withInfo(afterSplit.setUnreachable(), {
+ x: varModel([stringType])
+ });
+ var result = FlowModel.merge(h, s1, s2, emptyMap);
+ expect(result.reachable, same(splitPoint));
+ expect(result.variableInfo, same(s1.variableInfo));
+ });
+
+ test('both are unreachable', () {
+ var h = _Harness();
+ var splitPoint = Reachability.initial.split();
+ var afterSplit = splitPoint.split();
+ var s1 = FlowModel.withInfo(afterSplit.setUnreachable(), {
+ x: varModel([intType])
+ });
+ var s2 = FlowModel.withInfo(afterSplit.setUnreachable(), {
+ x: varModel([stringType])
+ });
+ var result = FlowModel.merge(h, s1, s2, emptyMap);
+ expect(result.reachable.locallyReachable, false);
+ expect(result.reachable.parent, same(splitPoint.parent));
+ expect(result.variableInfo[x].promotedTypes, isNull);
+ });
+ });
+
group('inheritTested', () {
var x = _Var('x', _Type('Object?'));
var intType = _Type('int');
@@ -3881,6 +4153,12 @@
callback(_AssignedVariablesHarness(_assignedVariables));
}
+ LazyExpression booleanLiteral(bool value) => () {
+ var expr = _Expression();
+ _flow.booleanLiteral(expr, value);
+ return expr;
+ };
+
@override
TypeClassification classifyType(_Type type) {
if (isSubtypeOf(type, _Type('Object'))) {
@@ -3943,6 +4221,7 @@
/// Invokes flow analysis of an `if` statement with no `else` part.
void if_(LazyExpression cond, void ifTrue()) {
+ _flow.ifStatement_conditionBegin();
_flow.ifStatement_thenBegin(cond());
ifTrue();
_flow.ifStatement_end(false);
@@ -3950,6 +4229,7 @@
/// Invokes flow analysis of an `if` statement with an `else` part.
void ifElse(LazyExpression cond, void ifTrue(), void ifFalse()) {
+ _flow.ifStatement_conditionBegin();
_flow.ifStatement_thenBegin(cond());
ifTrue();
_flow.ifStatement_elseBegin();
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/promotion_in_dead_code.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/promotion_in_dead_code.dart
new file mode 100644
index 0000000..4a3c9a5
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/promotion_in_dead_code.dart
@@ -0,0 +1,31 @@
+// 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.
+
+// These tests verify that the kinds of constructs we expect to cause type
+// promotion continue to function properly even when used inside unreachable
+// code.
+
+ifIsNot(Object o) {
+ return;
+ if (o is! int) return;
+ /*int*/ o;
+}
+
+ifIsNot_listElement(Object o) {
+ return;
+ [if (o is! int) throw 'x'];
+ /*int*/ o;
+}
+
+ifIsNot_setElement(Object o) {
+ return;
+ ({if (o is! int) throw 'x'});
+ /*int*/ o;
+}
+
+ifIsNot_mapElement(Object o) {
+ return;
+ ({if (o is! int) 0: throw 'x'});
+ /*int*/ o;
+}
diff --git a/pkg/analysis_server/analysis_options.yaml b/pkg/analysis_server/analysis_options.yaml
index 5995e86..31a937b 100644
--- a/pkg/analysis_server/analysis_options.yaml
+++ b/pkg/analysis_server/analysis_options.yaml
@@ -1,4 +1,4 @@
-include: package:pedantic/analysis_options.1.8.0.yaml
+include: package:pedantic/analysis_options.1.9.0.yaml
analyzer:
# This currently finds ~1,200 implicit-casts issues when enabled.
@@ -16,25 +16,11 @@
linter:
rules:
- await_only_futures
+ - avoid_single_cascade_in_expression_statements
- empty_statements
+ - iterable_contains_unrelated_type
+ - list_remove_unrelated_type
+ - prefer_initializing_formals
- unnecessary_brace_in_string_interps
- #
- # Delta from pedantic 1.8.0 to 1.9.0
- #
- - always_declare_return_types
- - always_require_non_null_named_parameters
- - annotate_overrides
- - avoid_null_checks_in_equality_operators
- - camel_case_extensions
- - omit_local_variable_types
- - prefer_adjacent_string_concatenation
- - prefer_collection_literals
- - prefer_conditional_assignment
- - prefer_final_fields
- - prefer_for_elements_to_map_fromIterable
- - prefer_generic_function_type_aliases
- - prefer_if_null_operators
- - prefer_single_quotes
- - prefer_spread_collections
- - unnecessary_this
- - use_function_type_syntax_for_parameters
+ - unnecessary_overrides
+ - void_checks
diff --git a/pkg/analysis_server/lib/protocol/protocol.dart b/pkg/analysis_server/lib/protocol/protocol.dart
index 53b008f..f995858 100644
--- a/pkg/analysis_server/lib/protocol/protocol.dart
+++ b/pkg/analysis_server/lib/protocol/protocol.dart
@@ -305,7 +305,7 @@
/// with the given [id]. If [_result] is provided, it will be used as the
/// result; otherwise an empty result will be used. If an [error] is provided
/// then the response will represent an error condition.
- Response(this.id, {Map<String, Object> result, this.error}) : result = result;
+ Response(this.id, {this.result, this.error});
/// Create and return the `DEBUG_PORT_COULD_NOT_BE_OPENED` error response.
Response.debugPortCouldNotBeOpened(Request request, dynamic error)
diff --git a/pkg/analysis_server/lib/src/context_manager.dart b/pkg/analysis_server/lib/src/context_manager.dart
index 0a4dbf9..c16084d 100644
--- a/pkg/analysis_server/lib/src/context_manager.dart
+++ b/pkg/analysis_server/lib/src/context_manager.dart
@@ -108,10 +108,9 @@
/// added to the context.
Map<String, Source> sources = HashMap<String, Source>();
- ContextInfo(ContextManagerImpl contextManager, this.parent, Folder folder,
+ ContextInfo(ContextManagerImpl contextManager, this.parent, this.folder,
File packagespecFile, this.disposition)
- : folder = folder,
- pathFilter = PathFilter(
+ : pathFilter = PathFilter(
folder.path, null, contextManager.resourceProvider.pathContext) {
packageDescriptionPath = packagespecFile.path;
parent.children.add(this);
diff --git a/pkg/analysis_server/lib/src/search/search_domain.dart b/pkg/analysis_server/lib/src/search/search_domain.dart
index 93b2db4..9dd70bf 100644
--- a/pkg/analysis_server/lib/src/search/search_domain.dart
+++ b/pkg/analysis_server/lib/src/search/search_domain.dart
@@ -25,9 +25,7 @@
/// Initialize a newly created handler to handle requests for the given
/// [server].
- SearchDomainHandler(AnalysisServer server)
- : server = server,
- searchEngine = server.searchEngine;
+ SearchDomainHandler(this.server) : searchEngine = server.searchEngine;
Future findElementReferences(protocol.Request request) async {
var params =
diff --git a/pkg/analysis_server/lib/src/services/completion/completion_core.dart b/pkg/analysis_server/lib/src/services/completion/completion_core.dart
index 5339b4d..a84b968 100644
--- a/pkg/analysis_server/lib/src/services/completion/completion_core.dart
+++ b/pkg/analysis_server/lib/src/services/completion/completion_core.dart
@@ -40,9 +40,8 @@
/// Initialize a newly created completion request based on the given
/// arguments.
CompletionRequestImpl(
- this.result, int offset, this.useNewRelevance, this.performance)
- : offset = offset,
- replacementOffset = offset,
+ this.result, this.offset, this.useNewRelevance, this.performance)
+ : replacementOffset = offset,
replacementLength = 0;
@override
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
index 9bbc308..df843b8 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/local_reference_contributor.dart
@@ -451,7 +451,7 @@
@override
void visitExtendsClause(ExtendsClause node) {
inExtendsClause = true;
- return super.visitExtendsClause(node);
+ super.visitExtendsClause(node);
}
/// Return `true` if the [identifier] is composed of one or more underscore
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index e506f30..d04d443 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -253,7 +253,7 @@
List.from(fixList[0].change.linkedEditGroups);
for (var i = 1; i < fixList.length; i++) {
edits.addAll(fixList[i].change.edits[0].edits);
- sourceChange.linkedEditGroups..addAll(fixList[i].change.linkedEditGroups);
+ sourceChange.linkedEditGroups.addAll(fixList[i].change.linkedEditGroups);
}
// Sort the list of SourceEdits so that when the edits are applied, they
// are applied from the end of the file to the top of the file.
diff --git a/pkg/analysis_server/lib/src/services/correction/sort_members.dart b/pkg/analysis_server/lib/src/services/correction/sort_members.dart
index 34e0ba4..cf3db41 100644
--- a/pkg/analysis_server/lib/src/services/correction/sort_members.dart
+++ b/pkg/analysis_server/lib/src/services/correction/sort_members.dart
@@ -262,10 +262,8 @@
final int end;
final String text;
- _MemberInfo(this.item, this.name, int offset, int length, this.text)
- : offset = offset,
- length = length,
- end = offset + length;
+ _MemberInfo(this.item, this.name, this.offset, this.length, this.text)
+ : end = offset + length;
@override
String toString() {
diff --git a/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart b/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart
index 398620c..99e8dd2 100644
--- a/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart
+++ b/pkg/analysis_server/lib/src/services/kythe/kythe_visitors.dart
@@ -1083,7 +1083,7 @@
_enclosingVName =
_vNameFromElement(_enclosingElement, schema.FUNCTION_KIND);
}
- return f();
+ f();
} finally {
_enclosingElement = outerEnclosingElement;
_enclosingClassElement = outerEnclosingClassElement;
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
index 7cfe3fe..7f96d3a 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_class_member.dart
@@ -148,10 +148,9 @@
elementKind = ElementKind.METHOD;
_ClassMemberValidator.forRename(
- this.searchEngine, this.sessionHelper, Element element, this.name)
+ this.searchEngine, this.sessionHelper, this.element, this.name)
: isRename = true,
library = element.library,
- element = element,
elementClass = element.enclosingElement,
elementKind = element.kind;
diff --git a/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart b/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart
index 3a4a3a5..6187abe 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/rename_extension_member.dart
@@ -104,10 +104,9 @@
final List<SearchMatch> references = <SearchMatch>[];
_ExtensionMemberValidator.forRename(
- this.searchEngine, this.sessionHelper, Element element, this.name)
+ this.searchEngine, this.sessionHelper, this.element, this.name)
: isRename = true,
library = element.library,
- element = element,
elementExtension = element.enclosingElement,
elementKind = element.kind;
diff --git a/pkg/analysis_server/pubspec.yaml b/pkg/analysis_server/pubspec.yaml
index 842ac85..da3e6a6 100644
--- a/pkg/analysis_server/pubspec.yaml
+++ b/pkg/analysis_server/pubspec.yaml
@@ -35,5 +35,5 @@
logging: any
matcher: any
mockito: any
- pedantic: ^1.8.0
+ pedantic: ^1.9.0
test_reflective_loader: any
diff --git a/pkg/analysis_server/test/integration/support/integration_tests.dart b/pkg/analysis_server/test/integration/support/integration_tests.dart
index e51eace..f58ca4d 100644
--- a/pkg/analysis_server/test/integration/support/integration_tests.dart
+++ b/pkg/analysis_server/test/integration/support/integration_tests.dart
@@ -690,9 +690,7 @@
/// Iterable matcher which we use to test the contents of the list.
final Matcher iterableMatcher;
- _ListOf(elementMatcher)
- : elementMatcher = elementMatcher,
- iterableMatcher = everyElement(elementMatcher);
+ _ListOf(this.elementMatcher) : iterableMatcher = everyElement(elementMatcher);
@override
Description describe(Description description) =>
diff --git a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
index b911615..a9f9ce5 100644
--- a/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
+++ b/pkg/analysis_server/tool/lsp_spec/codegen_dart.dart
@@ -365,7 +365,7 @@
..writeIndentedln('switch (obj) {')
..indent();
consts.forEach((cons) {
- buffer..writeIndentedln('case ${cons.valueAsLiteral}:');
+ buffer.writeIndentedln('case ${cons.valueAsLiteral}:');
});
buffer
..indent()
@@ -380,9 +380,8 @@
..writeIndentedln('}');
namespace.members.whereType<Const>().forEach((cons) {
_writeDocCommentsAndAnnotations(buffer, cons);
- buffer
- ..writeIndentedln(
- 'static const ${_makeValidIdentifier(cons.name)} = ${namespace.name}$constructorName(${cons.valueAsLiteral});');
+ buffer.writeIndentedln(
+ 'static const ${_makeValidIdentifier(cons.name)} = ${namespace.name}$constructorName(${cons.valueAsLiteral});');
});
buffer
..writeln()
@@ -662,7 +661,7 @@
final nullOp = shouldBeOmittedIfNoValue ? '' : '?';
final valueCode =
_isSpecType(field.type) ? '${field.name}$nullOp.toJson()' : field.name;
- buffer..writeIndented('''$mapName['${field.name}'] = $valueCode''');
+ buffer.writeIndented('''$mapName['${field.name}'] = $valueCode''');
if (!field.allowsUndefined && !field.allowsNull) {
buffer.write(''' ?? (throw '${field.name} is required but was not set')''');
}
@@ -792,7 +791,7 @@
buffer..write(' && (')..write('$valueCode.keys.every((item) => ');
_writeTypeCheckCondition(
buffer, interface, 'item', type.indexType, reporter);
- buffer..write('&& $valueCode.values.every((item) => ');
+ buffer.write('&& $valueCode.values.every((item) => ');
_writeTypeCheckCondition(
buffer, interface, 'item', type.valueType, reporter);
buffer.write(')))');
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index bc2bbad..0d46f4a 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -1498,6 +1498,7 @@
@override
void visitIfElement(IfElement node) {
+ _flowAnalysis?.flow?.ifStatement_conditionBegin();
Expression condition = node.condition;
InferenceContext.setType(condition, typeProvider.boolType);
// TODO(scheglov) Do we need these checks for null?
@@ -1535,6 +1536,7 @@
@override
void visitIfStatement(IfStatement node) {
checkUnreachableNode(node);
+ _flowAnalysis?.flow?.ifStatement_conditionBegin();
Expression condition = node.condition;
diff --git a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
index 3f0e79b..7288dd8 100644
--- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -1184,6 +1184,7 @@
@override
StatementInferenceResult visitIfStatement(IfStatement node) {
+ inferrer.flowAnalysis.ifStatement_conditionBegin();
InterfaceType expectedType =
inferrer.coreTypes.boolRawType(inferrer.library.nonNullable);
ExpressionInferenceResult conditionResult = inferrer.inferExpression(
@@ -1411,6 +1412,7 @@
element.elementType = spreadElementType ?? const DynamicType();
return new ExpressionInferenceResult(element.elementType, replacement);
} else if (element is IfElement) {
+ inferrer.flowAnalysis.ifStatement_conditionBegin();
DartType boolType =
inferrer.coreTypes.boolRawType(inferrer.library.nonNullable);
ExpressionInferenceResult conditionResult = inferrer.inferExpression(
@@ -1914,6 +1916,7 @@
return replacement;
} else if (entry is IfMapEntry) {
+ inferrer.flowAnalysis.ifStatement_conditionBegin();
DartType boolType =
inferrer.coreTypes.boolRawType(inferrer.library.nonNullable);
ExpressionInferenceResult conditionResult = inferrer.inferExpression(
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart b/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart
index af42f14..2ae32bf 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_promotion.dart
@@ -377,9 +377,7 @@
factMap[variable],
isCheck.functionNestingLevel,
isCheck.checkedType,
- []
- ..addAll(isCheck._blockingScopes)
- ..add(_currentScope));
+ [...isCheck._blockingScopes, _currentScope]);
factMap[variable] = facts;
_factCacheState = facts;
}
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt
index b370368..47cfd38 100644
--- a/pkg/front_end/test/spell_checking_list_common.txt
+++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -2783,6 +2783,7 @@
speed
speedup
split
+splits
splitter
spread
spreadable
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index 7363e60..7d4a32e 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -947,6 +947,7 @@
@override
DecoratedType visitIfStatement(IfStatement node) {
+ _flowAnalysis.ifStatement_conditionBegin();
_checkExpressionNotNull(node.condition);
NullabilityNode trueGuard;
NullabilityNode falseGuard;
diff --git a/runtime/tests/vm/dart/regress_43682_test.dart b/runtime/tests/vm/dart/regress_43682_test.dart
new file mode 100644
index 0000000..63ef705
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_43682_test.dart
@@ -0,0 +1,32 @@
+// 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.
+
+// VMOptions=--deterministic --optimization_counter_threshold=20
+
+// Verifies that SSA construction doesn't crash when handling a Phi
+// corresponding to an expression temp in case of OSR with non-empty
+// expression stack.
+// Regression test for https://github.com/dart-lang/sdk/issues/43682.
+
+import 'package:expect/expect.dart';
+
+class Foo {
+ List<Object> data;
+ Foo(this.data);
+}
+
+Map<String, Foo> foo(List<Object> objects) {
+ Map<String, Foo> map = {};
+ // OSR happens during '...objects' spread, and Foo instance is already
+ // allocated and remains on the stack during OSR.
+ // OSR Phi corresponding to that value is stored into 'foo' local and
+ // then loaded from it, but it doesn't correspond to 'foo' environment slot.
+ final foo = new Foo([...objects]);
+ map['hi'] = foo;
+ return map;
+}
+
+main() {
+ Expect.equals(30, foo(List.filled(30, Object()))['hi']!.data.length);
+}
diff --git a/runtime/tests/vm/dart_2/regress_43682_test.dart b/runtime/tests/vm/dart_2/regress_43682_test.dart
new file mode 100644
index 0000000..15924b1
--- /dev/null
+++ b/runtime/tests/vm/dart_2/regress_43682_test.dart
@@ -0,0 +1,32 @@
+// 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.
+
+// VMOptions=--deterministic --optimization_counter_threshold=20
+
+// Verifies that SSA construction doesn't crash when handling a Phi
+// corresponding to an expression temp in case of OSR with non-empty
+// expression stack.
+// Regression test for https://github.com/dart-lang/sdk/issues/43682.
+
+import 'package:expect/expect.dart';
+
+class Foo {
+ List<Object> data;
+ Foo(this.data);
+}
+
+Map<String, Foo> foo(List<Object> objects) {
+ Map<String, Foo> map = {};
+ // OSR happens during '...objects' spread, and Foo instance is already
+ // allocated and remains on the stack during OSR.
+ // OSR Phi corresponding to that value is stored into 'foo' local and
+ // then loaded from it, but it doesn't correspond to 'foo' environment slot.
+ final foo = new Foo([...objects]);
+ map['hi'] = foo;
+ return map;
+}
+
+main() {
+ Expect.equals(30, foo(List.filled(30, Object()))['hi'].data.length);
+}
diff --git a/runtime/vm/compiler/backend/flow_graph.cc b/runtime/vm/compiler/backend/flow_graph.cc
index 9196b2d..82430b4 100644
--- a/runtime/vm/compiler/backend/flow_graph.cc
+++ b/runtime/vm/compiler/backend/flow_graph.cc
@@ -1453,13 +1453,23 @@
// there as incoming value by renaming or it was stored there by
// StoreLocal which took this Phi from another local via LoadLocal,
// to which this reasoning applies recursively.
+ //
// This means that we are guaranteed to process LoadLocal for a
- // matching variable first.
+ // matching variable first, unless there was an OSR with a non-empty
+ // expression stack. In the latter case, Phi inserted by
+ // FlowGraph::AddSyntheticPhis for expression temp will not have an
+ // assigned type and may be accessed by StoreLocal and subsequent
+ // LoadLocal.
+ //
if (!phi->HasType()) {
- ASSERT((index < phi->block()->phis()->length()) &&
- ((*phi->block()->phis())[index] == phi));
- phi->UpdateType(
- CompileType::FromAbstractType(load->local().type()));
+ // Check if phi corresponds to the same slot.
+ auto* phis = phi->block()->phis();
+ if ((index < phis->length()) && (*phis)[index] == phi) {
+ phi->UpdateType(
+ CompileType::FromAbstractType(load->local().type()));
+ } else {
+ ASSERT(IsCompiledForOsr() && (phi->block()->stack_depth() > 0));
+ }
}
}
break;
diff --git a/runtime/vm/flags.cc b/runtime/vm/flags.cc
index ee2708f..901856c 100644
--- a/runtime/vm/flags.cc
+++ b/runtime/vm/flags.cc
@@ -95,11 +95,13 @@
Flag(const char* name, const char* comment, FlagHandler handler)
: name_(name),
comment_(comment),
+ string_value_("false"),
flag_handler_(handler),
type_(kFlagHandler) {}
Flag(const char* name, const char* comment, OptionHandler handler)
: name_(name),
comment_(comment),
+ string_value_(nullptr),
option_handler_(handler),
type_(kOptionHandler) {}
@@ -148,6 +150,7 @@
const char* name_;
const char* comment_;
+ const char* string_value_;
union {
void* addr_;
bool* bool_ptr_;
@@ -332,9 +335,11 @@
} else {
return false;
}
+ flag->string_value_ = argument;
break;
}
case Flag::kOptionHandler: {
+ flag->string_value_ = argument;
(flag->option_handler_)(argument);
break;
}
@@ -487,8 +492,7 @@
#ifndef PRODUCT
void Flags::PrintFlagToJSONArray(JSONArray* jsarr, const Flag* flag) {
- if (flag->IsUnrecognized() || flag->type_ == Flag::kFlagHandler ||
- flag->type_ == Flag::kOptionHandler) {
+ if (flag->IsUnrecognized()) {
return;
}
JSONObject jsflag(jsarr);
@@ -521,6 +525,20 @@
}
break;
}
+ case Flag::kFlagHandler: {
+ jsflag.AddProperty("_flagType", "Bool");
+ jsflag.AddProperty("valueAsString", flag->string_value_);
+ break;
+ }
+ case Flag::kOptionHandler: {
+ jsflag.AddProperty("_flagType", "String");
+ if (flag->string_value_ != nullptr) {
+ jsflag.AddProperty("valueAsString", flag->string_value_);
+ } else {
+ // valueAsString missing means NULL.
+ }
+ break;
+ }
default:
UNREACHABLE();
break;
diff --git a/sdk/lib/async/stream.dart b/sdk/lib/async/stream.dart
index 8d2a625aa..150a759 100644
--- a/sdk/lib/async/stream.dart
+++ b/sdk/lib/async/stream.dart
@@ -483,11 +483,13 @@
* On errors from this stream, the [onError] handler is called with the
* error object and possibly a stack trace.
*
- * The [onError] callback must be of type `void onError(Object error)` or
- * `void onError(Object error, StackTrace stackTrace)`. If [onError] accepts
- * two arguments it is called with the error object and the stack trace
- * (which could be `null` if this stream itself received an error without
- * stack trace).
+ * The [onError] callback must be of type `void Function(Object error)` or
+ * `void Function(Object error, StackTrace)`.
+ * The function type determines whether [onError] is invoked with a stack
+ * trace argument.
+ * The stack trace argument may be [StackTrace.empty] if this stream received
+ * an error without a stack trace.
+ *
* Otherwise it is called with just the error object.
* If [onError] is omitted, any errors on this stream are considered unhandled,
* and will be passed to the current [Zone]'s error handler.
diff --git a/tools/VERSION b/tools/VERSION
index e82a042..7d7e434 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 11
PATCH 0
-PRERELEASE 196
+PRERELEASE 197
PRERELEASE_PATCH 0
\ No newline at end of file