mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
439 lines
14 KiB
Dart
439 lines
14 KiB
Dart
// Copyright 2015 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
library validation_input_parser;
|
|
|
|
import 'dart:typed_data';
|
|
|
|
class ValidationParseResult {
|
|
final Iterable<_Entry> _entries;
|
|
final int numHandles;
|
|
final ByteData data;
|
|
|
|
ValidationParseResult(this._entries, this.data, this.numHandles);
|
|
|
|
String toString() => _entries.map((e) => '$e').join('\n');
|
|
}
|
|
|
|
ValidationParseResult parse(String input) =>
|
|
new _ValidationTestParser(input).parse();
|
|
|
|
class ValidationParseError {
|
|
final String _message;
|
|
ValidationParseError(this._message);
|
|
String toString() => _message;
|
|
}
|
|
|
|
abstract class _Entry {
|
|
final int size;
|
|
_Entry(this.size);
|
|
void write(ByteData buffer, int offset, Map pointers);
|
|
}
|
|
|
|
class _UnsignedEntry implements _Entry {
|
|
final int size;
|
|
final int value;
|
|
|
|
_UnsignedEntry(this.size, this.value) {
|
|
if ((value >= (1 << (size * 8))) || (value < 0)) {
|
|
throw new ValidationParseError('$value does not fit in a u$size');
|
|
}
|
|
}
|
|
|
|
void write(ByteData buffer, int offset, Map pointers) {
|
|
switch (size) {
|
|
case 1: buffer.setUint8(offset, value); break;
|
|
case 2: buffer.setUint16(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
case 4: buffer.setUint32(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
case 8: buffer.setUint64(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
default: throw new ValidationParseError('Unexpected size: $size');
|
|
}
|
|
}
|
|
|
|
String toString() => "[u$size]$value";
|
|
bool operator==(_UnsignedEntry other) =>
|
|
(size == other.size) && (value == other.value);
|
|
}
|
|
|
|
class _SignedEntry implements _Entry {
|
|
final int size;
|
|
final int value;
|
|
|
|
_SignedEntry(this.size, this.value) {
|
|
if ((value >= (1 << ((size * 8) - 1))) ||
|
|
(value < -(1 << ((size * 8) - 1)))) {
|
|
throw new ValidationParseError('$value does not fit in a s$size');
|
|
}
|
|
}
|
|
|
|
void write(ByteData buffer, int offset, Map pointers) {
|
|
switch (size) {
|
|
case 1: buffer.setInt8(offset, value); break;
|
|
case 2: buffer.setInt16(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
case 4: buffer.setInt32(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
case 8: buffer.setInt64(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
default: throw new ValidationParseError('Unexpected size: $size');
|
|
}
|
|
}
|
|
|
|
String toString() => "[s$size]$value";
|
|
bool operator==(_SignedEntry other) =>
|
|
(size == other.size) && (value == other.value);
|
|
}
|
|
|
|
class _FloatEntry implements _Entry {
|
|
final int size;
|
|
final double value;
|
|
|
|
_FloatEntry(this.size, this.value);
|
|
|
|
void write(ByteData buffer, int offset, Map pointers) {
|
|
switch (size) {
|
|
case 4: buffer.setFloat32(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
case 8: buffer.setFloat64(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
default: throw new ValidationParseError('Unexpected size: $size');
|
|
}
|
|
}
|
|
|
|
String toString() => "[f$size]$value";
|
|
bool operator==(_FloatEntry other) =>
|
|
(size == other.size) && (value == other.value);
|
|
}
|
|
|
|
class _DistEntry implements _Entry {
|
|
final int size;
|
|
final String id;
|
|
int offset;
|
|
bool matched = false;
|
|
|
|
_DistEntry(this.size, this.id);
|
|
|
|
void write(ByteData buffer, int off, Map pointers) {
|
|
offset = off;
|
|
if (pointers[id] != null) {
|
|
throw new ValidationParseError(
|
|
'Pointer of same name already exists: $id');
|
|
}
|
|
pointers[id] = this;
|
|
}
|
|
|
|
String toString() => "[dist$size]$id matched = $matched";
|
|
bool operator==(_DistEntry other) =>
|
|
(size == other.size) && (id == other.id);
|
|
}
|
|
|
|
class _AnchrEntry implements _Entry {
|
|
final int size = 0;
|
|
final String id;
|
|
|
|
_AnchrEntry(this.id);
|
|
|
|
void write(ByteData buffer, int off, Map pointers) {
|
|
_DistEntry dist = pointers[id];
|
|
if (dist == null) {
|
|
throw new ValidationParseError('Did not find "$id" in pointers map.');
|
|
}
|
|
int value = off - dist.offset;
|
|
if (value < 0) {
|
|
throw new ValidationParseError('Found a backwards pointer: $id');
|
|
}
|
|
int offset = dist.offset;
|
|
switch (dist.size) {
|
|
case 4: buffer.setUint32(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
case 8: buffer.setUint64(offset, value, Endianness.LITTLE_ENDIAN); break;
|
|
default: throw new ValidationParseError('Unexpected size: $size');
|
|
}
|
|
dist.matched = true;
|
|
}
|
|
|
|
String toString() => "[anchr]$id";
|
|
bool operator==(_AnchrEntry other) => (id == other.id);
|
|
}
|
|
|
|
class _HandlesEntry implements _Entry {
|
|
final int size = 0;
|
|
final int value;
|
|
|
|
_HandlesEntry(this.value);
|
|
|
|
void write(ByteData buffer, int offset, Map pointers) {}
|
|
|
|
String toString() => "[handles]$value";
|
|
bool operator==(_HandlesEntry other) => (value == other.value);
|
|
}
|
|
|
|
class _CommentEntry implements _Entry {
|
|
final int size = 0;
|
|
final String value;
|
|
|
|
_CommentEntry(this.value);
|
|
|
|
void write(ByteData buffer, int offset, Map pointers) {}
|
|
|
|
String toString() => "// $value";
|
|
bool operator==(_CommentEntry other) => (value == other.value);
|
|
}
|
|
|
|
class _ValidationTestParser {
|
|
static final RegExp newline = new RegExp(r'[\r\n]+');
|
|
static final RegExp whitespace = new RegExp(r'[ \t\n\r]+');
|
|
static final RegExp nakedUintRegExp =
|
|
new RegExp(r'^0$|^[1-9][0-9]*$|^0[xX][0-9a-fA-F]+$');
|
|
static final RegExp unsignedRegExp =
|
|
new RegExp(r'^\[u([1248])\](0$|[1-9][0-9]*$|0[xX][0-9a-fA-F]+$)');
|
|
static final RegExp signedRegExp = new RegExp(
|
|
r'^\[s([1248])\]([-+]?0$|[-+]?[1-9][0-9]*$|[-+]?0[xX][0-9a-fA-F]+$)');
|
|
static final RegExp binaryRegExp =
|
|
new RegExp(r'^\[(b)\]([01]{8}$)');
|
|
static final RegExp floatRegExp =
|
|
new RegExp(r'^\[([fd])\]([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$)');
|
|
static final RegExp distRegExp =
|
|
new RegExp(r'^\[dist([48])\]([0-9a-zA-Z_]+$)');
|
|
static final RegExp anchrRegExp =
|
|
new RegExp(r'^\[(anchr)\]([0-9a-zA-Z_]+$)');
|
|
static final RegExp handlesRegExp =
|
|
new RegExp(r'^\[(handles)\](0$|([1-9][0-9]*$)|(0[xX][0-9a-fA-F]+$))');
|
|
static final RegExp commentRegExp =
|
|
new RegExp(r'//(.*)');
|
|
|
|
String _input;
|
|
Map<String, _DistEntry> _pointers;
|
|
|
|
_ValidationTestParser(this._input) : _pointers = {};
|
|
|
|
String _stripComment(String line) => line.replaceFirst(commentRegExp, "");
|
|
|
|
ValidationParseResult parse() {
|
|
var entries = _input.split(newline)
|
|
.map(_stripComment)
|
|
.expand((s) => s.split(whitespace))
|
|
.where((s) => s != "")
|
|
.map(_parseLine);
|
|
int size = _calculateSize(entries);
|
|
var data = (size > 0) ? new ByteData(size) : null;
|
|
int numHandles = 0;
|
|
int offset = 0;
|
|
bool first = true;
|
|
|
|
for (var entry in entries) {
|
|
entry.write(data, offset, _pointers);
|
|
offset += entry.size;
|
|
|
|
if (entry is _HandlesEntry) {
|
|
if (!first) {
|
|
throw new ValidationParseError('Handles entry was not first');
|
|
}
|
|
numHandles = entry.value;
|
|
}
|
|
first = false;
|
|
}
|
|
|
|
for (var entry in entries) {
|
|
if (entry is _DistEntry) {
|
|
if (!_pointers[entry.id].matched) {
|
|
throw new ValidationParseError('Unmatched dist: $entry');
|
|
}
|
|
}
|
|
}
|
|
|
|
return new ValidationParseResult(entries, data, numHandles);
|
|
}
|
|
|
|
_Entry _parseLine(String line) {
|
|
if (unsignedRegExp.hasMatch(line)) {
|
|
var match = unsignedRegExp.firstMatch(line);
|
|
return new _UnsignedEntry(
|
|
int.parse(match.group(1)), int.parse(match.group(2)));
|
|
} else if (signedRegExp.hasMatch(line)) {
|
|
var match = signedRegExp.firstMatch(line);
|
|
return new _SignedEntry(
|
|
int.parse(match.group(1)), int.parse(match.group(2)));
|
|
} else if (binaryRegExp.hasMatch(line)) {
|
|
var match = binaryRegExp.firstMatch(line);
|
|
return new _UnsignedEntry(1, int.parse(match.group(2), radix: 2));
|
|
} else if (floatRegExp.hasMatch(line)) {
|
|
var match = floatRegExp.firstMatch(line);
|
|
int size = match.group(1) == 'f' ? 4 : 8;
|
|
return new _FloatEntry(size, double.parse(match.group(2)));
|
|
} else if (distRegExp.hasMatch(line)) {
|
|
var match = distRegExp.firstMatch(line);
|
|
return new _DistEntry(int.parse(match.group(1)), match.group(2));
|
|
} else if (anchrRegExp.hasMatch(line)) {
|
|
var match = anchrRegExp.firstMatch(line);
|
|
return new _AnchrEntry(match.group(2));
|
|
} else if (handlesRegExp.hasMatch(line)) {
|
|
var match = handlesRegExp.firstMatch(line);
|
|
return new _HandlesEntry(int.parse(match.group(2)));
|
|
} else if (nakedUintRegExp.hasMatch(line)) {
|
|
var match = nakedUintRegExp.firstMatch(line);
|
|
return new _UnsignedEntry(1, int.parse(match.group(0)));
|
|
} else if (commentRegExp.hasMatch(line)) {
|
|
var match = commentRegExp.firstMatch(line);
|
|
return new _CommentEntry(match.group(1));
|
|
} else if (line == "") {
|
|
return new _CommentEntry("");
|
|
} else {
|
|
throw new ValidationParseError('Unkown entry: "$line" in \n$_input');
|
|
}
|
|
}
|
|
|
|
int _calculateSize(Iterable<_Entry> entries) =>
|
|
entries.fold(0, (value, entry) => value + entry.size);
|
|
}
|
|
|
|
bool _listEquals(Iterable i1, Iterable i2) {
|
|
var l1 = i1.toList();
|
|
var l2 = i2.toList();
|
|
if (l1.length != l2.length) return false;
|
|
for (int i = 0; i < l1.length; i++) {
|
|
if (l1[i] != l2[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
parserTests() {
|
|
{
|
|
var input = " \t // hello world \n\r \t// the answer is 42 ";
|
|
var result = parse(input);
|
|
assert(result.data == null);
|
|
assert(result.numHandles == 0);
|
|
}
|
|
{
|
|
var input = "[u1]0x10// hello world !! \n\r \t [u2]65535 \n"
|
|
"[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff";
|
|
var result = parse(input);
|
|
|
|
// Check the parse results.
|
|
var expected = [new _UnsignedEntry(1, 0x10),
|
|
new _UnsignedEntry(2, 65535),
|
|
new _UnsignedEntry(4, 65536),
|
|
new _UnsignedEntry(8, 0xFFFFFFFFFFFFFFFF),
|
|
new _UnsignedEntry(1, 0),
|
|
new _UnsignedEntry(1, 0xff)];
|
|
assert(_listEquals(result._entries, expected));
|
|
|
|
//Check the bits.
|
|
var buffer = new ByteData(17);
|
|
var offset = 0;
|
|
buffer.setUint8(offset, 0x10); offset++;
|
|
buffer.setUint16(offset, 65535, Endianness.LITTLE_ENDIAN); offset += 2;
|
|
buffer.setUint32(offset, 65536, Endianness.LITTLE_ENDIAN); offset += 4;
|
|
buffer.setUint64(offset, 0xFFFFFFFFFFFFFFFF, Endianness.LITTLE_ENDIAN); offset += 8;
|
|
buffer.setUint8(offset, 0); offset++;
|
|
buffer.setUint8(offset, 0xff); offset++;
|
|
assert(_listEquals(buffer.buffer.asUint8List(),
|
|
result.data.buffer.asUint8List()));
|
|
}
|
|
{
|
|
var input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40";
|
|
var result = parse(input);
|
|
|
|
// Check the parse results.
|
|
var expected = [new _SignedEntry(8, -0x800),
|
|
new _SignedEntry(1, -128),
|
|
new _SignedEntry(2, 0),
|
|
new _SignedEntry(4, -40)];
|
|
assert(_listEquals(result._entries, expected));
|
|
|
|
// Check the bits.
|
|
var buffer = new ByteData(15);
|
|
var offset = 0;
|
|
buffer.setInt64(offset, -0x800, Endianness.LITTLE_ENDIAN); offset += 8;
|
|
buffer.setInt8(offset, -128); offset += 1;
|
|
buffer.setInt16(offset, 0, Endianness.LITTLE_ENDIAN); offset += 2;
|
|
buffer.setInt32(offset, -40, Endianness.LITTLE_ENDIAN); offset += 4;
|
|
assert(_listEquals(buffer.buffer.asUint8List(),
|
|
result.data.buffer.asUint8List()));
|
|
}
|
|
{
|
|
var input = "[b]00001011 [b]10000000 // hello world\r [b]00000000";
|
|
var result = parse(input);
|
|
|
|
// Check the parse results;
|
|
var expected = [new _UnsignedEntry(1, 11),
|
|
new _UnsignedEntry(1, 128),
|
|
new _UnsignedEntry(1, 0)];
|
|
assert(_listEquals(result._entries, expected));
|
|
|
|
// Check the bits.
|
|
var buffer = new ByteData(3);
|
|
var offset = 0;
|
|
buffer.setUint8(offset, 11); offset += 1;
|
|
buffer.setUint8(offset, 128); offset += 1;
|
|
buffer.setUint8(offset, 0); offset += 1;
|
|
assert(_listEquals(buffer.buffer.asUint8List(),
|
|
result.data.buffer.asUint8List()));
|
|
}
|
|
{
|
|
var input = "[f]+.3e9 [d]-10.03";
|
|
var result = parse(input);
|
|
|
|
// Check the parse results.
|
|
var expected = [new _FloatEntry(4, 0.3e9),
|
|
new _FloatEntry(8,-10.03)];
|
|
assert(_listEquals(result._entries, expected));
|
|
|
|
// Check the bits.
|
|
var buffer = new ByteData(12);
|
|
var offset = 0;
|
|
buffer.setFloat32(offset, 0.3e9, Endianness.LITTLE_ENDIAN); offset += 4;
|
|
buffer.setFloat64(offset, -10.03, Endianness.LITTLE_ENDIAN); offset += 8;
|
|
assert(_listEquals(buffer.buffer.asUint8List(),
|
|
result.data.buffer.asUint8List()));
|
|
}
|
|
{
|
|
var input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar";
|
|
var result = parse(input);
|
|
|
|
// Check the parse results.
|
|
var expected = [new _DistEntry(4, "foo"),
|
|
new _UnsignedEntry(1, 0),
|
|
new _DistEntry(8, "bar"),
|
|
new _UnsignedEntry(1, 0),
|
|
new _AnchrEntry("foo"),
|
|
new _AnchrEntry("bar")];
|
|
assert(_listEquals(result._entries, expected));
|
|
|
|
// Check the bits.
|
|
var buffer = new ByteData(14);
|
|
var offset = 0;
|
|
buffer.setUint32(offset, 14, Endianness.LITTLE_ENDIAN); offset += 4;
|
|
buffer.setUint8(offset, 0); offset += 1;
|
|
buffer.setUint64(offset, 9, Endianness.LITTLE_ENDIAN); offset += 8;
|
|
buffer.setUint8(offset, 0); offset += 1;
|
|
assert(_listEquals(buffer.buffer.asUint8List(),
|
|
result.data.buffer.asUint8List()));
|
|
}
|
|
{
|
|
var input = "// This message has handles! \n[handles]50 [u8]2";
|
|
var result = parse(input);
|
|
var expected = [new _HandlesEntry(50),
|
|
new _UnsignedEntry(8, 2)];
|
|
assert(_listEquals(result._entries, expected));
|
|
}
|
|
{
|
|
var errorInputs = ["/ hello world",
|
|
"[u1]x",
|
|
"[u2]-1000",
|
|
"[u1]0x100",
|
|
"[s2]-0x8001",
|
|
"[b]1",
|
|
"[b]1111111k",
|
|
"[dist4]unmatched",
|
|
"[anchr]hello [dist8]hello",
|
|
"[dist4]a [dist4]a [anchr]a",
|
|
"[dist4]a [anchr]a [dist4]a [anchr]a",
|
|
"0 [handles]50"];
|
|
for (var input in errorInputs) {
|
|
try {
|
|
var result = parse(input);
|
|
assert(false);
|
|
} on ValidationParseError catch(e) {
|
|
// Pass.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|