| // 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()) | |
| ..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_concat_ok() { | |
| _analyze( | |
| (ir) => ir | |
| ..ordinaryFunction(parameterCount: 3) | |
| ..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(3))) | |
| ..concat(3) | |
| ..onValidate((v) => check(v.valueStackDepth).equals(ValueCount(1))) | |
| ..end(), | |
| ); | |
| _validate(); | |
| } | |
| test_concat_underflow() { | |
| _analyze( | |
| (ir) => ir | |
| ..ordinaryFunction(parameterCount: 2) | |
| ..label('bad') | |
| ..concat(3) | |
| ..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() | |
| ..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()) | |
| ..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'); | |
| } |