2019-03-28 16:31:45 +00:00
|
|
|
// Copyright (c) 2019, 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.
|
|
|
|
|
|
|
|
// Generates the API tables used by DartFuzz. Automatically generating these
|
|
|
|
// tables is less error-prone than generating such tables by hand. Furthermore,
|
|
|
|
// it simplifies regenerating the table when the libraries change.
|
|
|
|
//
|
|
|
|
// Usage:
|
|
|
|
// dart gen_api_table.dart > dartfuzz_api_table.dart
|
|
|
|
//
|
|
|
|
// Then send out modified dartfuzz_api_table.dart for review together
|
|
|
|
// with a modified dartfuzz.dart that increases the version.
|
|
|
|
|
|
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
import 'package:analyzer/dart/analysis/results.dart';
|
2019-08-26 21:22:12 +00:00
|
|
|
import 'package:analyzer/dart/analysis/session.dart';
|
2019-03-28 16:31:45 +00:00
|
|
|
import 'package:analyzer/dart/element/element.dart';
|
|
|
|
import 'package:analyzer/dart/element/type.dart';
|
|
|
|
import 'package:analyzer/file_system/physical_file_system.dart';
|
|
|
|
|
|
|
|
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
|
|
|
|
|
|
|
|
// Class to represent a library method by name and prototype representation.
|
|
|
|
class DartLib {
|
|
|
|
final String name;
|
|
|
|
final String proto;
|
|
|
|
const DartLib(this.name, this.proto);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lists of recognized methods, organized by return type.
|
2019-08-26 21:22:12 +00:00
|
|
|
var boolTable = <DartLib>[];
|
|
|
|
var intTable = <DartLib>[];
|
|
|
|
var doubleTable = <DartLib>[];
|
|
|
|
var stringTable = <DartLib>[];
|
|
|
|
var listTable = <DartLib>[];
|
|
|
|
var setTable = <DartLib>[];
|
|
|
|
var mapTable = <DartLib>[];
|
2019-03-28 16:31:45 +00:00
|
|
|
|
|
|
|
main() async {
|
|
|
|
// Set paths. Note that for this particular use case, packageRoot can be
|
|
|
|
// any directory. Here, we set it to the top of the SDK development, and
|
|
|
|
// derive the required sdkPath from there.
|
2019-08-26 21:22:12 +00:00
|
|
|
final String packageRoot = Platform.environment['DART_TOP'];
|
2019-03-28 16:31:45 +00:00
|
|
|
if (packageRoot == null) {
|
2019-08-26 21:22:12 +00:00
|
|
|
throw StateError('No environment variable DART_TOP');
|
2019-03-28 16:31:45 +00:00
|
|
|
}
|
2019-08-26 21:22:12 +00:00
|
|
|
final sdkPath = '$packageRoot/tools/sdks/dart-sdk';
|
2019-03-28 16:31:45 +00:00
|
|
|
|
|
|
|
// This does most of the hard work of getting the analyzer configured
|
|
|
|
// correctly. Typically the included paths are the files and directories
|
|
|
|
// that need to be analyzed, but the SDK is always available, so it isn't
|
|
|
|
// really important for this particular use case. We use the implementation
|
|
|
|
// class in order to pass in the sdkPath directly.
|
2019-08-26 21:22:12 +00:00
|
|
|
final provider = PhysicalResourceProvider.INSTANCE;
|
|
|
|
final collection = AnalysisContextCollectionImpl(
|
2019-03-28 16:31:45 +00:00
|
|
|
includedPaths: <String>[packageRoot],
|
|
|
|
resourceProvider: provider,
|
|
|
|
sdkPath: sdkPath);
|
2019-08-26 21:22:12 +00:00
|
|
|
final AnalysisSession session = collection.contexts[0].currentSession;
|
2019-03-28 16:31:45 +00:00
|
|
|
|
|
|
|
// Visit libraries for table generation.
|
|
|
|
await visitLibraryAtUri(session, 'dart:async');
|
|
|
|
await visitLibraryAtUri(session, 'dart:cli');
|
|
|
|
await visitLibraryAtUri(session, 'dart:collection');
|
|
|
|
await visitLibraryAtUri(session, 'dart:convert');
|
|
|
|
await visitLibraryAtUri(session, 'dart:core');
|
|
|
|
await visitLibraryAtUri(session, 'dart:io');
|
|
|
|
await visitLibraryAtUri(session, 'dart:isolate');
|
|
|
|
await visitLibraryAtUri(session, 'dart:math');
|
|
|
|
await visitLibraryAtUri(session, 'dart:typed_data');
|
|
|
|
|
|
|
|
// Generate the tables in a stand-alone Dart class.
|
|
|
|
dumpHeader();
|
|
|
|
dumpTable('boolLibs', boolTable);
|
|
|
|
dumpTable('intLibs', intTable);
|
|
|
|
dumpTable('doubleLibs', doubleTable);
|
|
|
|
dumpTable('stringLibs', stringTable);
|
|
|
|
dumpTable('listLibs', listTable);
|
|
|
|
dumpTable('setLibs', setTable);
|
|
|
|
dumpTable('mapLibs', mapTable);
|
|
|
|
dumpFooter();
|
|
|
|
}
|
|
|
|
|
|
|
|
visitLibraryAtUri(AnalysisSession session, String uri) async {
|
2019-08-26 21:22:12 +00:00
|
|
|
final String libPath = session.uriConverter.uriToPath(Uri.parse(uri));
|
2019-03-28 16:31:45 +00:00
|
|
|
ResolvedLibraryResult result = await session.getResolvedLibrary(libPath);
|
|
|
|
if (result.state != ResultState.VALID) {
|
2019-08-26 21:22:12 +00:00
|
|
|
throw StateError('Unable to resolve "$uri"');
|
2019-03-28 16:31:45 +00:00
|
|
|
}
|
|
|
|
visitLibrary(result.element);
|
|
|
|
}
|
|
|
|
|
|
|
|
visitLibrary(LibraryElement library) async {
|
|
|
|
// This uses the element model to traverse the code. The element model
|
|
|
|
// represents the semantic structure of the code. A library consists of
|
|
|
|
// one or more compilation units.
|
|
|
|
for (CompilationUnitElement unit in library.units) {
|
|
|
|
visitCompilationUnit(unit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
visitCompilationUnit(CompilationUnitElement unit) {
|
|
|
|
// Each compilation unit contains elements for all of the top-level
|
|
|
|
// declarations in a single file, such as variables, functions, and
|
|
|
|
// classes. Note that `types` only returns classes. You can use
|
|
|
|
// `mixins` to visit mixins, `enums` to visit enum, `functionTypeAliases`
|
|
|
|
// to visit typedefs, etc.
|
|
|
|
for (TopLevelVariableElement variable in unit.topLevelVariables) {
|
|
|
|
if (variable.isPublic) {
|
|
|
|
addToTable(typeString(variable.type), variable.name, 'Vv');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (FunctionElement function in unit.functions) {
|
|
|
|
if (function.isPublic && !function.isOperator) {
|
|
|
|
addToTable(typeString(function.returnType), function.name,
|
|
|
|
protoString(null, function.parameters));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (ClassElement classElement in unit.types) {
|
|
|
|
if (classElement.isPublic) {
|
|
|
|
visitClass(classElement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void visitClass(ClassElement classElement) {
|
2019-04-10 21:05:12 +00:00
|
|
|
// Classes that cause too many false divergences.
|
2019-07-30 20:49:56 +00:00
|
|
|
if (classElement.name == 'ProcessInfo' ||
|
|
|
|
classElement.name == 'Platform' ||
|
|
|
|
classElement.name.startsWith('FileSystem')) {
|
2019-04-10 01:07:51 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-03-28 16:31:45 +00:00
|
|
|
// Every class element contains elements for the members, viz. `methods` visits
|
|
|
|
// methods, `fields` visits fields, `accessors` visits getters and setters, etc.
|
|
|
|
// There are also accessors to get the superclass, mixins, interfaces, type
|
|
|
|
// parameters, etc.
|
|
|
|
for (ConstructorElement constructor in classElement.constructors) {
|
|
|
|
if (constructor.isPublic &&
|
|
|
|
constructor.isFactory &&
|
2019-08-27 18:37:07 +00:00
|
|
|
constructor.name.isNotEmpty) {
|
2019-03-28 16:31:45 +00:00
|
|
|
addToTable(
|
|
|
|
typeString(classElement.type),
|
|
|
|
'${classElement.name}.${constructor.name}',
|
|
|
|
protoString(null, constructor.parameters));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (MethodElement method in classElement.methods) {
|
|
|
|
if (method.isPublic && !method.isOperator) {
|
|
|
|
if (method.isStatic) {
|
|
|
|
addToTable(
|
|
|
|
typeString(method.returnType),
|
|
|
|
'${classElement.name}.${method.name}',
|
|
|
|
protoString(null, method.parameters));
|
|
|
|
} else {
|
|
|
|
addToTable(typeString(method.returnType), method.name,
|
|
|
|
protoString(classElement.type, method.parameters));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (PropertyAccessorElement accessor in classElement.accessors) {
|
|
|
|
if (accessor.isPublic && accessor.isGetter) {
|
|
|
|
var variable = accessor.variable;
|
|
|
|
if (accessor.isStatic) {
|
|
|
|
addToTable(typeString(variable.type),
|
|
|
|
'${classElement.name}.${variable.name}', 'Vv');
|
|
|
|
} else {
|
|
|
|
addToTable(typeString(variable.type), variable.name,
|
|
|
|
'${typeString(classElement.type)}v');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Types are represented by an instance of `DartType`. For classes, the type
|
|
|
|
// will be an instance of `InterfaceType`, which will provide access to the
|
|
|
|
// defining (class) element, as well as any type arguments.
|
|
|
|
String typeString(DartType type) {
|
|
|
|
if (type.isDartCoreBool) {
|
|
|
|
return 'B';
|
|
|
|
} else if (type.isDartCoreInt) {
|
|
|
|
return 'I';
|
|
|
|
} else if (type.isDartCoreDouble) {
|
|
|
|
return 'D';
|
|
|
|
} else if (type.isDartCoreString) {
|
|
|
|
return 'S';
|
|
|
|
}
|
|
|
|
// Supertypes or type parameters are specialized in a consistent manner.
|
|
|
|
// TODO(ajcbik): inspect type structure semantically, not by display name
|
|
|
|
// and unify DartFuzz's DartType with analyzer DartType.
|
|
|
|
switch (type.displayName) {
|
|
|
|
case 'E':
|
|
|
|
return 'I';
|
|
|
|
case 'num':
|
|
|
|
return 'D';
|
|
|
|
case 'List<E>':
|
|
|
|
case 'List<Object>':
|
|
|
|
case 'List<dynamic>':
|
|
|
|
case 'List<int>':
|
|
|
|
case 'List':
|
|
|
|
return 'L';
|
|
|
|
case 'Set<E>':
|
|
|
|
case 'Set<Object>':
|
|
|
|
case 'Set<dynamic>':
|
|
|
|
case 'Set<int>':
|
|
|
|
case 'Set':
|
|
|
|
return 'X';
|
|
|
|
case 'Map<K, V>':
|
|
|
|
case 'Map<dynamic, dynamic>':
|
|
|
|
case 'Map<int, String>':
|
|
|
|
case 'Map':
|
|
|
|
return 'M';
|
|
|
|
}
|
|
|
|
return '?';
|
|
|
|
}
|
|
|
|
|
|
|
|
String protoString(DartType receiver, List<ParameterElement> parameters) {
|
|
|
|
var proto = receiver == null ? 'V' : typeString(receiver);
|
|
|
|
// Construct prototype for non-named parameters.
|
|
|
|
for (ParameterElement parameter in parameters) {
|
|
|
|
if (!parameter.isNamed) {
|
|
|
|
proto += typeString(parameter.type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Use 'void' for an empty parameter list.
|
|
|
|
return proto.length == 1 ? proto + 'V' : proto;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<DartLib> getTable(String ret) {
|
|
|
|
switch (ret) {
|
|
|
|
case 'B':
|
|
|
|
return boolTable;
|
|
|
|
case 'I':
|
|
|
|
return intTable;
|
|
|
|
case 'D':
|
|
|
|
return doubleTable;
|
|
|
|
case 'S':
|
|
|
|
return stringTable;
|
|
|
|
case 'L':
|
|
|
|
return listTable;
|
|
|
|
case 'X':
|
|
|
|
return setTable;
|
|
|
|
case 'M':
|
|
|
|
return mapTable;
|
2019-08-26 21:22:12 +00:00
|
|
|
default:
|
|
|
|
throw ArgumentError('Invalid ret value: $ret');
|
2019-03-28 16:31:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void addToTable(String ret, String name, String proto) {
|
|
|
|
// If any of the type representations contains a question
|
|
|
|
// mark, this means that DartFuzz' type system cannot
|
|
|
|
// deal with such an expression yet. So drop the entry.
|
|
|
|
if (ret.contains('?') || proto.contains('?')) {
|
|
|
|
return;
|
|
|
|
}
|
2019-04-24 18:48:25 +00:00
|
|
|
// Avoid some obvious false divergences.
|
2019-07-30 20:49:56 +00:00
|
|
|
if (name == 'pid' || name == 'hashCode' || name == 'exitCode') {
|
2019-04-24 18:48:25 +00:00
|
|
|
return;
|
|
|
|
}
|
2019-04-10 21:05:12 +00:00
|
|
|
// Restrict parameters for a few hardcoded cases,
|
|
|
|
// for example, to avoid excessive runtime or memory
|
2019-04-24 18:48:25 +00:00
|
|
|
// allocation in the generated fuzzing program.
|
2019-03-28 16:31:45 +00:00
|
|
|
if (name == 'padLeft' || name == 'padRight') {
|
2019-07-23 00:19:48 +00:00
|
|
|
proto = proto.replaceFirst('IS', 'is');
|
2019-04-27 00:00:14 +00:00
|
|
|
} else if (name == 'List.filled') {
|
|
|
|
proto = proto.replaceFirst('I', 'i');
|
2019-03-28 16:31:45 +00:00
|
|
|
}
|
|
|
|
// Add to table.
|
|
|
|
getTable(ret).add(DartLib(name, proto));
|
|
|
|
}
|
|
|
|
|
|
|
|
void dumpHeader() {
|
|
|
|
print("""
|
|
|
|
// Copyright (c) 2019, 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.
|
|
|
|
|
|
|
|
/// Class that represents Dart library methods.
|
|
|
|
///
|
|
|
|
/// The invididual lists are organized by return type.
|
|
|
|
/// The proto string has the following format:
|
|
|
|
/// +-------> receiver type (V denotes none)
|
|
|
|
/// |+------> param1 type (V denotes none, v denotes getter)
|
|
|
|
/// ||+-----> param2 type
|
|
|
|
/// |||+----> ..
|
|
|
|
/// ||||
|
|
|
|
/// 'TTTT..'
|
|
|
|
/// where:
|
|
|
|
/// V void
|
|
|
|
/// v void (special)
|
|
|
|
/// B bool
|
|
|
|
/// I int
|
|
|
|
/// i int (small)
|
|
|
|
/// D double
|
|
|
|
/// S String
|
2019-07-23 00:19:48 +00:00
|
|
|
/// s String (small)
|
2019-03-28 16:31:45 +00:00
|
|
|
/// L List<int>
|
|
|
|
/// X Set<int>
|
|
|
|
/// M Map<int, String>
|
|
|
|
///
|
|
|
|
/// NOTE: this code has been generated automatically.
|
|
|
|
///
|
|
|
|
class DartLib {
|
|
|
|
final String name;
|
|
|
|
final String proto;
|
|
|
|
const DartLib(this.name, this.proto);
|
|
|
|
""");
|
|
|
|
}
|
|
|
|
|
|
|
|
void dumpTable(String identifier, List<DartLib> table) {
|
|
|
|
print(' static const $identifier = [');
|
|
|
|
table.forEach((t) => print(' DartLib(\'${t.name}\', \'${t.proto}\'),'));
|
|
|
|
print(' ];');
|
|
|
|
}
|
|
|
|
|
|
|
|
void dumpFooter() {
|
|
|
|
print('}');
|
|
|
|
}
|