blob: f16423a353cfb7da4c3c035b892c3224827fb52e [file] [log] [blame]
// Copyright (c) 2016, 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 'dart:convert';
import 'dart:typed_data';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:pub_semver/pub_semver.dart';
/// An instance of [ApiSignature] collects data in the form of primitive types
/// (strings, ints, bools, etc.) from a summary "builder" object, and uses them
/// to generate an MD5 signature of a the non-informative parts of the summary
/// (i.e. those parts representing the API of the code being summarized).
///
/// Note that the data passed to the MD5 signature algorithm is untyped. So,
/// for instance, an API signature built from a sequence of `false` booleans is
/// likely to match an API signature built from a sequence of zeros. The caller
/// should take this into account; e.g. if a data structure may be represented
/// either by a boolean or an int, the caller should encode a tag distinguishing
/// the two representations before encoding the data.
class ApiSignature {
/// Version number of the code in this class. Any time this class is changed
/// in a way that affects the data collected in [_data], this version number
/// should be incremented, so that a summary signed by a newer version of the
/// signature algorithm won't accidentally have the same signature as a
/// summary signed by an older version.
static const int _VERSION = 0;
/// Data accumulated so far.
ByteData _data = ByteData(4096);
/// Offset into [_data] where the next byte should be written.
int _offset = 0;
/// Create an [ApiSignature] which is ready to accept data.
ApiSignature() {
addInt(_VERSION);
}
/// For testing only: create an [ApiSignature] which doesn't include any
/// version information. This makes it easier to unit tests, since the data
/// is stable even if [_VERSION] is changed.
ApiSignature.unversioned();
/// Collect a boolean value.
void addBool(bool b) {
_makeRoom(1);
_data.setUint8(_offset, b ? 1 : 0);
_offset++;
}
/// Collect a sequence of arbitrary bytes. Note that the length is not
/// collected, so for example `addBytes([1, 2]);` will have the same effect as
/// `addBytes([1]); addBytes([2]);`.
void addBytes(List<int> bytes) {
int length = bytes.length;
_makeRoom(length);
for (int i = 0; i < length; i++) {
_data.setUint8(_offset + i, bytes[i]);
}
_offset += length;
}
/// Collect a double-precision floating point value.
void addDouble(double d) {
_makeRoom(8);
_data.setFloat64(_offset, d, Endian.little);
_offset += 8;
}
/// Collect a [FeatureSet].
void addFeatureSet(FeatureSet featureSet) {
var knownFeatures = ExperimentStatus.knownFeatures;
addInt(knownFeatures.length);
for (var feature in knownFeatures.values) {
addBool(featureSet.isEnabled(feature));
}
}
/// Collect a 32-bit unsigned integer value.
void addInt(int i) {
_makeRoom(4);
_data.setUint32(_offset, i, Endian.little);
_offset += 4;
}
/// Collect a language version.
void addLanguageVersion(Version version) {
addInt(version.major);
addInt(version.minor);
}
/// Collect a string.
void addString(String s) {
List<int> bytes = utf8.encode(s);
addInt(bytes.length);
addBytes(bytes);
}
/// Collect the given [Uint32List].
void addUint32List(Uint32List data) {
addBytes(data.buffer.asUint8List());
}
/// For testing only: retrieve the internal representation of the data that
/// has been collected.
Uint8List getBytes_forDebug() {
return Uint8List.view(_data.buffer, 0, _offset);
}
/// Return the bytes of the MD5 hash of the data collected so far.
Uint8List toByteList() {
var data = _data.buffer.asUint8List(0, _offset);
var bytes = md5.convert(data).bytes;
return bytes is Uint8List ? bytes : Uint8List.fromList(bytes);
}
/// Return a hex-encoded MD5 signature of the data collected so far.
String toHex() {
return hex.encode(toByteList());
}
/// Return the MD5 hash of the data collected so far as [Uint32List].
Uint32List toUint32List() {
var bytes = toByteList();
return bytes.buffer.asUint32List();
}
/// Ensure that [spaceNeeded] bytes can be added to [_data] at [_offset]
/// (copying it to a larger object if necessary).
void _makeRoom(int spaceNeeded) {
int oldLength = _data.lengthInBytes;
if (_offset + spaceNeeded > oldLength) {
int newLength = 2 * (_offset + spaceNeeded);
ByteData newData = ByteData(newLength);
Uint8List.view(newData.buffer)
.setRange(0, oldLength, Uint8List.view(_data.buffer));
_data = newData;
}
}
}