| // 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'); |
| } |