blob: 2715d97308fcd8d6c0c3879d7ccb2a6c4193aadb [file]
// Copyright (c) 2023, 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.
import 'package:analyzer/src/wolf/ir/ir.dart';
import 'package:analyzer/src/wolf/ir/validator.dart';
import 'package:checks/checks.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'utils.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ValidatorTest);
});
}
@reflectiveTest
class ValidatorTest {
final _addressToOnValidateCallbacks =
<int, List<void Function(ValidationEventListener)>>{};
late TestIRContainer ir;
test_alloc_negativeCount() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..alloc(-1)
..end());
_checkInvalidMessageAt('bad').equals('Negative alloc count');
}
test_alloc_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..onValidate((v) => check(v.localCount).equals(0))
..alloc(1)
..onValidate((v) => check(v.localCount).equals(1))
..release(1)
..end());
_validate();
}
test_await_inSynchronousFunction() {
_analyze((ir) => ir
..function(
ir.encodeFunctionType(parameterCount: 1), FunctionFlags(async: false))
..label('bad')
..await_()
..end());
_checkInvalidMessageAt('bad').equals('Await in synchronous function');
}
test_await_ok() {
_analyze((ir) => ir
..function(
ir.encodeFunctionType(parameterCount: 1), FunctionFlags(async: true))
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..await_()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
_validate();
}
test_await_underflow() {
_analyze((ir) => ir
..function(
ir.encodeFunctionType(parameterCount: 0), FunctionFlags(async: true))
..label('bad')
..await_()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_block_negativeInputCount() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..block(-1, 0)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Negative input count');
}
test_block_negativeOutputCount() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..block(0, -1)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Negative output count');
}
test_block_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..block(2, 1)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..drop()
..end()
..end());
_validate();
}
test_block_underflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..block(2, 1)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_br_controlFlowStackUnderflow() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..br(1)
..end());
_checkInvalidMessageAt('bad').equals('Control flow stack underflow');
}
test_br_fromBlock_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..block(2, 1)
..drop()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..br(0)
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..end()
..end());
_validate();
}
test_br_fromBlock_stackUnderflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..block(2, 1)
..drop()
..drop()
..label('bad')
..br(0)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_br_fromFunction_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..br(0)
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..end());
_validate();
}
test_br_fromFunction_stackUnderflow() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..br(0)
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_br_fromLoop_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..loop(2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..br(0)
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..end()
..end());
_validate();
}
test_br_fromLoop_stackUnderflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..loop(2)
..drop()
..label('bad')
..br(0)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_br_negativeNesting() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..br(-1)
..end());
_checkInvalidMessageAt('bad').equals('Negative branch nesting');
}
test_br_outsideOfEnclosingFunction() {
_analyze((ir) => ir
..ordinaryFunction()
..ordinaryFunction(parameterCount: 1)
..label('bad')
..br(1)
..end()
..end());
_checkInvalidMessageAt('bad')
.equals('Cannot branch outside of enclosing function');
}
test_brIf_controlFlowStackUnderflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..label('bad')
..brIf(1)
..end());
_checkInvalidMessageAt('bad').equals('Control flow stack underflow');
}
test_brIf_fromBlock_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..block(2, 1)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..brIf(0)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end()
..end());
_validate();
}
test_brIf_fromBlock_stackUnderflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..block(2, 1)
..drop()
..label('bad')
..brIf(0)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_brIf_fromFunction_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..brIf(0)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
_validate();
}
test_brIf_fromFunction_stackUnderflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..brIf(0)
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_brIf_fromLoop_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..loop(2)
..dup()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(3)))
..brIf(0)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..end()
..end());
_validate();
}
test_brIf_fromLoop_stackUnderflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..loop(2)
..label('bad')
..brIf(0)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_brIf_negativeNesting() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..label('bad')
..brIf(-1)
..end());
_checkInvalidMessageAt('bad').equals('Negative branch nesting');
}
test_brIf_outsideOfEnclosingFunction() {
_analyze((ir) => ir
..ordinaryFunction()
..ordinaryFunction(parameterCount: 2)
..label('bad')
..br(1)
..end()
..end());
_checkInvalidMessageAt('bad')
.equals('Cannot branch outside of enclosing function');
}
test_call_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..call(ir.encodeCallDescriptor('f'), ir.encodeArgumentNames([null, null]))
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
_validate();
}
test_call_underflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..call(ir.encodeCallDescriptor('f'), ir.encodeArgumentNames([null, null]))
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_drop_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..drop()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
_validate();
}
test_drop_underflow() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..drop()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_dup_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..dup()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..drop()
..end());
_validate();
}
test_dup_underflow() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..dup()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_end_block_indeterminate() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..block(2, 1)
..br(1)
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..end()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
_validate();
}
test_end_block_preservesStackValuesBelowInput() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 3)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(3)))
..block(2, 1)
..drop()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..drop()
..end());
_validate();
}
test_end_block_superfluousValues() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..block(2, 1)
..label('bad')
..end()
..end());
_checkInvalidMessageAt('bad').equals('1 superfluous value(s) remaining');
}
test_end_block_underflow() {
_analyze((ir) => ir
..ordinaryFunction()
..block(0, 1)
..label('bad')
..end()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_end_function_indeterminate() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..br(0)
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..ordinaryFunction(parameterCount: 1)
..end()
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..end());
_validate();
}
test_end_function_pushesOneValue() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..ordinaryFunction(parameterCount: 1)
..end()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..drop()
..end());
_validate();
}
test_end_function_superfluousValues() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..label('bad')
..end());
_checkInvalidMessageAt('bad').equals('1 superfluous value(s) remaining');
}
test_end_function_valueStackUnderflow() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_end_loop_indeterminate() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..loop(2)
..br(1)
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..end()
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..end());
_validate();
}
test_end_loop_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 3)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(3)))
..loop(2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..end()
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..end());
_validate();
}
test_end_loop_superfluousValues() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..loop(2)
..dup()
..label('bad')
..end()
..end());
_checkInvalidMessageAt('bad').equals('1 superfluous value(s) remaining');
}
test_end_loop_underflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..loop(2)
..drop()
..label('bad')
..end()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_end_unmatched() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..end()
..label('bad')
..end());
_checkInvalidMessageAt('bad').equals('Unmatched end');
}
test_end_unreleasedLocals() {
_analyze((ir) => ir
..ordinaryFunction()
..alloc(1)
..label('bad')
..end());
_checkInvalidMessageAt('bad').equals('Unreleased locals');
}
test_eq_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..eq()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
}
test_eq_underflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..eq()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_firstInstruction_function_ok() {
_analyze((ir) => ir
..ordinaryFunction()
..literal(ir.encodeLiteral(null))
..end());
_validate();
}
test_firstInstruction_instanceFunction_ok() {
_analyze((ir) => ir
..function(ir.encodeFunctionType(parameterCount: 0),
FunctionFlags(instance: true))
..end());
_validate();
}
test_firstInstruction_notFunction() {
_analyze((ir) => ir
..label('bad')
..literal(ir.encodeLiteral(null)));
_checkInvalidMessageAt('bad').equals('First instruction must be function');
}
test_function_nested_instanceFunction() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..function(ir.encodeFunctionType(parameterCount: 0),
FunctionFlags(instance: true))
..end()
..end());
_checkInvalidMessageAt('bad')
.equals('Instance function may only be used at instruction address 0');
}
test_function_nested_notInstanceFunction_ok() {
_analyze((ir) => ir
..ordinaryFunction()
..ordinaryFunction()
..literal(ir.encodeLiteral(null))
..end()
..end());
_validate();
}
test_function_parameterCount_instanceFunction() {
_analyze((ir) => ir
..function(ir.encodeFunctionType(parameterCount: 2),
FunctionFlags(instance: true))
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(3)))
..drop()
..drop()
..end());
_validate();
}
test_function_parameterCount_notInstanceFunction() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..drop()
..end());
_validate();
}
test_identical_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..identical()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
}
test_identical_underflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..identical()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_is_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..is_(ir.encodeFunctionType(parameterCount: 0))
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
_validate();
}
test_is_underflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 0)
..label('bad')
..is_(ir.encodeFunctionType(parameterCount: 0))
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_literal_ok() {
_analyze((ir) => ir
..ordinaryFunction()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(0)))
..literal(ir.encodeLiteral(null)) // Push `null`
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
_validate();
}
test_loop_negativeInputCount() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..loop(-1)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Negative input count');
}
test_loop_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..loop(2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..end()
..end());
_validate();
}
test_loop_underflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..loop(2)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_missingEnd() {
_analyze((ir) => ir
..ordinaryFunction()
..literal(ir.encodeLiteral(null))
..label('bad'));
_checkInvalidMessageAt('bad').equals('Missing end');
}
test_noInstructions() {
_analyze((ir) => ir..label('bad'));
_checkInvalidMessageAt('bad').equals('No instructions');
}
test_not_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..not()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
_validate();
}
test_not_underflow() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..not()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_readLocal_negativeLocalIndex() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..readLocal(-1)
..end());
_checkInvalidMessageAt('bad').equals('Negative local index');
}
test_readLocal_noSuchLocal() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..readLocal(0)
..end());
_checkInvalidMessageAt('bad').equals('No such local');
}
test_readLocal_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..alloc(1)
..writeLocal(0)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(0)))
..readLocal(0)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..release(1)
..end());
_validate();
}
test_release_negativeCount() {
_analyze((ir) => ir
..ordinaryFunction()
..label('bad')
..release(-1)
..end());
_checkInvalidMessageAt('bad').equals('Negative release count');
}
test_release_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..alloc(1)
..onValidate((v) => check(v.localCount).equals(1))
..release(1)
..onValidate((v) => check(v.localCount).equals(0))
..end());
_validate();
}
test_release_underflow() {
_analyze((ir) => ir
..ordinaryFunction()
..alloc(1)
..ordinaryFunction()
..label('bad')
..release(1)
..end()
..end());
_checkInvalidMessageAt('bad').equals('Local variable stack underflow');
}
test_shuffle_negativePopCount() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..shuffle(-1, ir.encodeStackIndices([]))
..end());
_checkInvalidMessageAt('bad').equals('Negative pop count');
}
test_shuffle_negativeStackIndex() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..label('bad')
..shuffle(2, ir.encodeStackIndices([-1]))
..end());
_checkInvalidMessageAt('bad').equals('Negative stack index');
}
test_shuffle_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(2)))
..shuffle(2, ir.encodeStackIndices([0, 1, 0, 1]))
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(4)))
..drop()
..drop()
..drop()
..end());
_validate();
}
test_shuffle_stackIndexTooLarge() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 2)
..label('bad')
..shuffle(2, ir.encodeStackIndices([2]))
..end());
_checkInvalidMessageAt('bad').equals('Stack index too large');
}
test_shuffle_underflow() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..shuffle(2, ir.encodeStackIndices([0, 1, 0, 1]))
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_stack_push() {
_analyze((ir) => ir
..ordinaryFunction()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(0)))
..literal(ir.encodeLiteral(null))
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..end());
_validate();
}
test_stack_push_indeterminate() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..br(0)
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..literal(ir.encodeLiteral(null))
..onValidate(
(v) => check(v.valueStackDepth).equals(ValueCount.indeterminate))
..end());
_validate();
}
test_writeLocal_negativeLocalIndex() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..writeLocal(-1)
..end());
_checkInvalidMessageAt('bad').equals('Negative local index');
}
test_writeLocal_noSuchLocal() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..label('bad')
..writeLocal(0)
..end());
_checkInvalidMessageAt('bad').equals('No such local');
}
test_writeLocal_ok() {
_analyze((ir) => ir
..ordinaryFunction(parameterCount: 1)
..alloc(1)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..writeLocal(0)
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(0)))
..release(1)
..literal(ir.encodeLiteral(null))
..end());
_validate();
}
test_writeLocal_underflow() {
_analyze((ir) => ir
..ordinaryFunction()
..alloc(1)
..label('bad')
..writeLocal(0)
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
test_yield_inNonGeneratorFunction() {
_analyze((ir) => ir
..function(ir.encodeFunctionType(parameterCount: 1),
FunctionFlags(generator: false))
..label('bad')
..yield_()
..end());
_checkInvalidMessageAt('bad').equals('Yield in non-generator function');
}
test_yield_ok() {
_analyze((ir) => ir
..function(ir.encodeFunctionType(parameterCount: 1),
FunctionFlags(generator: true))
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1)))
..yield_()
..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(0)))
..literal(ir.encodeLiteral(null))
..end());
_validate();
}
test_yield_underflow() {
_analyze((ir) => ir
..function(ir.encodeFunctionType(parameterCount: 0),
FunctionFlags(generator: true))
..label('bad')
..yield_()
..end());
_checkInvalidMessageAt('bad').equals('Value stack underflow');
}
void _analyze(void Function(_ValidationTestIRWriter) writeIR) {
var writer = _ValidationTestIRWriter(_addressToOnValidateCallbacks);
writeIR(writer);
ir = TestIRContainer(writer);
}
Subject<String> _checkInvalidMessageAt(String label) =>
(check(_validate).throws<ValidationError>()
..address.equals(ir.labelToAddress('bad')!))
.message;
void _validate() {
validate(ir, eventListener: _ValidationEventListener(this));
check(
because: 'make sure all callbacks got invoked',
_addressToOnValidateCallbacks)
.isEmpty();
}
}
/// Validation event listener that executes callbacks installed by
/// [_ValidationTestIRWriter].
base class _ValidationEventListener extends ValidationEventListener {
final ValidatorTest test;
_ValidationEventListener(this.test);
@override
void onFinished() => _onAddress(test.ir.endAddress);
@override
void onInstruction(int address) => _onAddress(address);
void _onAddress(int address) {
if (test._addressToOnValidateCallbacks.remove(address)
case var callbacks?) {
for (var callback in callbacks) {
callback(this);
}
}
}
}
/// IR writer that can record callbacks to be executed during validation.
///
/// These callbacks will have access to the [ValidationEventListener] so they
/// can query some internal validator state.
class _ValidationTestIRWriter extends TestIRWriter {
final Map<int, List<void Function(ValidationEventListener)>>
_addressToOnValidateCallbacks;
_ValidationTestIRWriter(this._addressToOnValidateCallbacks);
void onValidate(void Function(ValidationEventListener) callback) {
_addressToOnValidateCallbacks
.putIfAbsent(nextInstructionAddress, () => [])
.add(callback);
}
}
extension on Subject<ValidationError> {
Subject<int> get address => has((e) => e.address, 'address');
Subject<String> get message => has((e) => e.message, 'message');
}