| // 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) { |
| // Classes that cause too many false divergences. |
| if (classElement.name == 'ProcessInfo' || classElement.name == 'Platform') { |
| return; |
| } |
| // 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; |
| } |
| // Avoid some obvious false divergences. |
| if (name == 'pid' || |
| name == 'hashCode' || |
| name == 'Platform.executable' || |
| name == 'Platform.resolvedExecutable') { |
| return; |
| } |
| // Restrict parameters for a few hardcoded cases, |
| // for example, to avoid excessive runtime or memory |
| // allocation in the generated fuzzing program. |
| if (name == 'padLeft' || name == 'padRight') { |
| proto = proto.replaceAll('I', 'i'); |
| } else if (name == 'List.filled') { |
| proto = proto.replaceFirst('I', 'i'); |
| } |
| // 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('}'); |
| } |