f29e100f42
Rationale: Rather than manually constructing the API tables for DartFuzz, this introduces a utility to generate API tables based on the analyzer traversal of common libraries. The utility recognizes DartTypes that are currently understood by DartFuzz and constructs the tables organized by return type. TBD: DartFuzz's type system is rather simple; as this improves, more and more methods will be accepted by the utility, thereby increasing fuzzing coverage of our libraries. Change-Id: Idcc607a4a592dbbf80bd79cdfec429cb397b7f68 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98041 Reviewed-by: Siva Annamalai <asiva@google.com> Commit-Queue: Aart Bik <ajcbik@google.com>
311 lines
10 KiB
Dart
Executable File
311 lines
10 KiB
Dart
Executable File
// 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/analysis_context_collection.dart';
|
|
import 'package:analyzer/dart/analysis/session.dart';
|
|
import 'package:analyzer/dart/analysis/results.dart';
|
|
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.
|
|
var boolTable = List<DartLib>();
|
|
var intTable = List<DartLib>();
|
|
var doubleTable = List<DartLib>();
|
|
var stringTable = List<DartLib>();
|
|
var listTable = List<DartLib>();
|
|
var setTable = List<DartLib>();
|
|
var mapTable = List<DartLib>();
|
|
|
|
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.
|
|
String packageRoot = Platform.environment['DART_TOP'];
|
|
if (packageRoot == null) {
|
|
throw new StateError('No environment variable DART_TOP');
|
|
}
|
|
String sdkPath = '$packageRoot/tools/sdks/dart-sdk';
|
|
|
|
// 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.
|
|
PhysicalResourceProvider provider = PhysicalResourceProvider.INSTANCE;
|
|
AnalysisContextCollection collection = new AnalysisContextCollectionImpl(
|
|
includedPaths: <String>[packageRoot],
|
|
resourceProvider: provider,
|
|
sdkPath: sdkPath);
|
|
AnalysisSession session = collection.contexts[0].currentSession;
|
|
|
|
// 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 {
|
|
String libPath = session.uriConverter.uriToPath(Uri.parse(uri));
|
|
ResolvedLibraryResult result = await session.getResolvedLibrary(libPath);
|
|
if (result.state != ResultState.VALID) {
|
|
throw new StateError('Unable to resolve "$uri"');
|
|
}
|
|
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) {
|
|
// 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 &&
|
|
!constructor.name.isEmpty) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
// Restrict parameters for a few hardcoded cases, for
|
|
// example, to avoid excessive runtime or memory allocation
|
|
// in the generated fuzzing program or to avoid obvious
|
|
// divergences.
|
|
if (name == 'padLeft' || name == 'padRight') {
|
|
proto = proto.replaceAll('I', 'i');
|
|
} else if (name == 'Platform.executable' ||
|
|
name == 'Platform.resolvedExecutable') {
|
|
return;
|
|
}
|
|
// 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
|
|
/// 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('}');
|
|
}
|