From c95568aede3f26fda36b58b081ec697a74960815 Mon Sep 17 00:00:00 2001 From: Jens Johansen Date: Thu, 9 Jan 2025 23:13:33 -0800 Subject: [PATCH] [CFE] Fuzzer using Dart.G This is the first commit of this effort. It's not polished and mostly generates nonsense output that, though, should not crash the compiler. Further work might add options to create less-nonsense output). Change-Id: Ic7f140ea6f248bfa9a0e62ca37326def1d0a5086 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403601 Reviewed-by: Johnni Winther Commit-Queue: Jens Johansen --- .../lib/src/util/parser_ast_helper.dart | 1519 +++++++++++ .../crashing_test_case_minimizer_impl.dart | 357 +-- .../test/spell_checking_list_tests.txt | 24 + pkg/front_end/tool/fuzz/Dart.g | 2288 +++++++++++++++++ pkg/front_end/tool/fuzz/analyzer_helper.dart | 489 ++++ pkg/front_end/tool/fuzz/compile_helper.dart | 94 + pkg/front_end/tool/fuzz/fuzz.dart | 142 + pkg/front_end/tool/fuzz/gReader.dart | 1140 ++++++++ pkg/front_end/tool/fuzz/minimizer.dart | 244 ++ pkg/front_end/tool/fuzz/post_fix_checker.dart | 34 + pkg/front_end/tool/fuzz/reorganizer.dart | 62 + pkg/front_end/tool/fuzz/stacktrace_utils.dart | 76 + pkg/front_end/tool/interval_list.dart | 6 + .../tool/parser_ast_helper_creator.dart | 50 + 14 files changed, 6377 insertions(+), 148 deletions(-) create mode 100644 pkg/front_end/tool/fuzz/Dart.g create mode 100644 pkg/front_end/tool/fuzz/analyzer_helper.dart create mode 100644 pkg/front_end/tool/fuzz/compile_helper.dart create mode 100644 pkg/front_end/tool/fuzz/fuzz.dart create mode 100644 pkg/front_end/tool/fuzz/gReader.dart create mode 100644 pkg/front_end/tool/fuzz/minimizer.dart create mode 100644 pkg/front_end/tool/fuzz/post_fix_checker.dart create mode 100644 pkg/front_end/tool/fuzz/reorganizer.dart create mode 100644 pkg/front_end/tool/fuzz/stacktrace_utils.dart diff --git a/pkg/front_end/lib/src/util/parser_ast_helper.dart b/pkg/front_end/lib/src/util/parser_ast_helper.dart index 28d33a69ebae..2b126be228de 100644 --- a/pkg/front_end/lib/src/util/parser_ast_helper.dart +++ b/pkg/front_end/lib/src/util/parser_ast_helper.dart @@ -42,6 +42,37 @@ abstract class ParserAstNode { } } + void debugPrint() { + StringBuffer sb = new StringBuffer(); + _debugPrintImpl(0, sb); + print(sb.toString()); + } + + void _debugPrintImpl(int indentation, StringBuffer sb) { + sb.write(" " * indentation); + sb.write(what); + sb.write(type.name); + Token? tokenWithSmallestOffset; + for (Object? value in deprecatedArguments.values) { + if (value is Token) { + if (tokenWithSmallestOffset == null || + value.charOffset < tokenWithSmallestOffset.charOffset) { + tokenWithSmallestOffset = value; + } + } + } + if (tokenWithSmallestOffset != null) { + sb.write(" (${tokenWithSmallestOffset.lexeme} @ " + "${tokenWithSmallestOffset.charOffset})"); + } + sb.writeln(); + List? children = this.children; + if (children == null) return; + for (ParserAstNode child in children) { + child._debugPrintImpl(indentation + 2, sb); + } + } + // TODO(jensj): Compare two ASTs. } @@ -11886,3 +11917,1491 @@ class RecursiveParserAstVisitor implements ParserAstVisitor { void visitPatternAssignmentHandle(PatternAssignmentHandle node) => node.visitChildren(this); } + +class RecursiveParserAstVisitorWithDefaultNodeAsync + implements ParserAstVisitor> { + Future defaultNode(ParserAstNode node) async { + List? children = node.children; + if (children == null) return; + for (ParserAstNode child in children) { + await child.accept(this); + } + } + + @override + Future visitArgumentsBegin(ArgumentsBegin node) => defaultNode(node); + + @override + Future visitArgumentsEnd(ArgumentsEnd node) => defaultNode(node); + + @override + Future visitObjectPatternFieldsHandle(ObjectPatternFieldsHandle node) => + defaultNode(node); + + @override + Future visitAsyncModifierHandle(AsyncModifierHandle node) => + defaultNode(node); + + @override + Future visitAwaitExpressionBegin(AwaitExpressionBegin node) => + defaultNode(node); + + @override + Future visitAwaitExpressionEnd(AwaitExpressionEnd node) => + defaultNode(node); + + @override + Future visitInvalidAwaitExpressionEnd(InvalidAwaitExpressionEnd node) => + defaultNode(node); + + @override + Future visitBlockBegin(BlockBegin node) => defaultNode(node); + + @override + Future visitBlockEnd(BlockEnd node) => defaultNode(node); + + @override + Future visitInvalidTopLevelBlockHandle( + InvalidTopLevelBlockHandle node) => + defaultNode(node); + + @override + Future visitCascadeBegin(CascadeBegin node) => defaultNode(node); + + @override + Future visitCascadeEnd(CascadeEnd node) => defaultNode(node); + + @override + Future visitCaseExpressionBegin(CaseExpressionBegin node) => + defaultNode(node); + + @override + Future visitCaseExpressionEnd(CaseExpressionEnd node) => + defaultNode(node); + + @override + Future visitClassOrMixinOrExtensionBodyBegin( + ClassOrMixinOrExtensionBodyBegin node) => + defaultNode(node); + + @override + Future visitClassOrMixinOrExtensionBodyEnd( + ClassOrMixinOrExtensionBodyEnd node) => + defaultNode(node); + + @override + Future visitClassOrMixinOrNamedMixinApplicationPreludeBegin( + ClassOrMixinOrNamedMixinApplicationPreludeBegin node) => + defaultNode(node); + + @override + Future visitClassDeclarationBegin(ClassDeclarationBegin node) => + defaultNode(node); + + @override + Future visitClassExtendsHandle(ClassExtendsHandle node) => + defaultNode(node); + + @override + Future visitImplementsHandle(ImplementsHandle node) => + defaultNode(node); + + @override + Future visitClassHeaderHandle(ClassHeaderHandle node) => + defaultNode(node); + + @override + Future visitRecoverDeclarationHeaderHandle( + RecoverDeclarationHeaderHandle node) => + defaultNode(node); + + @override + Future visitClassDeclarationEnd(ClassDeclarationEnd node) => + defaultNode(node); + + @override + Future visitMixinDeclarationBegin(MixinDeclarationBegin node) => + defaultNode(node); + + @override + Future visitMixinOnHandle(MixinOnHandle node) => defaultNode(node); + + @override + Future visitMixinHeaderHandle(MixinHeaderHandle node) => + defaultNode(node); + + @override + Future visitRecoverMixinHeaderHandle(RecoverMixinHeaderHandle node) => + defaultNode(node); + + @override + Future visitMixinDeclarationEnd(MixinDeclarationEnd node) => + defaultNode(node); + + @override + Future visitUncategorizedTopLevelDeclarationBegin( + UncategorizedTopLevelDeclarationBegin node) => + defaultNode(node); + + @override + Future visitExtensionDeclarationPreludeBegin( + ExtensionDeclarationPreludeBegin node) => + defaultNode(node); + + @override + Future visitExtensionDeclarationBegin(ExtensionDeclarationBegin node) => + defaultNode(node); + + @override + Future visitExtensionDeclarationEnd(ExtensionDeclarationEnd node) => + defaultNode(node); + + @override + Future visitExtensionTypeDeclarationBegin( + ExtensionTypeDeclarationBegin node) => + defaultNode(node); + + @override + Future visitExtensionTypeDeclarationEnd( + ExtensionTypeDeclarationEnd node) => + defaultNode(node); + + @override + Future visitPrimaryConstructorBegin(PrimaryConstructorBegin node) => + defaultNode(node); + + @override + Future visitPrimaryConstructorEnd(PrimaryConstructorEnd node) => + defaultNode(node); + + @override + Future visitNoPrimaryConstructorHandle( + NoPrimaryConstructorHandle node) => + defaultNode(node); + + @override + Future visitCombinatorsBegin(CombinatorsBegin node) => + defaultNode(node); + + @override + Future visitCombinatorsEnd(CombinatorsEnd node) => defaultNode(node); + + @override + Future visitCompilationUnitBegin(CompilationUnitBegin node) => + defaultNode(node); + + @override + Future visitDirectivesOnlyHandle(DirectivesOnlyHandle node) => + defaultNode(node); + + @override + Future visitCompilationUnitEnd(CompilationUnitEnd node) => + defaultNode(node); + + @override + Future visitConstLiteralBegin(ConstLiteralBegin node) => + defaultNode(node); + + @override + Future visitConstLiteralEnd(ConstLiteralEnd node) => defaultNode(node); + + @override + Future visitConstructorReferenceBegin(ConstructorReferenceBegin node) => + defaultNode(node); + + @override + Future visitConstructorReferenceEnd(ConstructorReferenceEnd node) => + defaultNode(node); + + @override + Future visitDoWhileStatementBegin(DoWhileStatementBegin node) => + defaultNode(node); + + @override + Future visitDoWhileStatementEnd(DoWhileStatementEnd node) => + defaultNode(node); + + @override + Future visitDoWhileStatementBodyBegin(DoWhileStatementBodyBegin node) => + defaultNode(node); + + @override + Future visitDoWhileStatementBodyEnd(DoWhileStatementBodyEnd node) => + defaultNode(node); + + @override + Future visitWhileStatementBodyBegin(WhileStatementBodyBegin node) => + defaultNode(node); + + @override + Future visitWhileStatementBodyEnd(WhileStatementBodyEnd node) => + defaultNode(node); + + @override + Future visitEnumBegin(EnumBegin node) => defaultNode(node); + + @override + Future visitEnumEnd(EnumEnd node) => defaultNode(node); + + @override + Future visitEnumConstructorEnd(EnumConstructorEnd node) => + defaultNode(node); + + @override + Future visitEnumElementsHandle(EnumElementsHandle node) => + defaultNode(node); + + @override + Future visitEnumHeaderHandle(EnumHeaderHandle node) => + defaultNode(node); + + @override + Future visitEnumElementHandle(EnumElementHandle node) => + defaultNode(node); + + @override + Future visitEnumFactoryMethodEnd(EnumFactoryMethodEnd node) => + defaultNode(node); + + @override + Future visitExportBegin(ExportBegin node) => defaultNode(node); + + @override + Future visitExportEnd(ExportEnd node) => defaultNode(node); + + @override + Future visitExtraneousExpressionHandle( + ExtraneousExpressionHandle node) => + defaultNode(node); + + @override + Future visitExpressionStatementHandle(ExpressionStatementHandle node) => + defaultNode(node); + + @override + Future visitFactoryMethodBegin(FactoryMethodBegin node) => + defaultNode(node); + + @override + Future visitClassFactoryMethodEnd(ClassFactoryMethodEnd node) => + defaultNode(node); + + @override + Future visitMixinFactoryMethodEnd(MixinFactoryMethodEnd node) => + defaultNode(node); + + @override + Future visitExtensionFactoryMethodEnd(ExtensionFactoryMethodEnd node) => + defaultNode(node); + + @override + Future visitExtensionTypeFactoryMethodEnd( + ExtensionTypeFactoryMethodEnd node) => + defaultNode(node); + + @override + Future visitFormalParameterBegin(FormalParameterBegin node) => + defaultNode(node); + + @override + Future visitFormalParameterEnd(FormalParameterEnd node) => + defaultNode(node); + + @override + Future visitNoFormalParametersHandle(NoFormalParametersHandle node) => + defaultNode(node); + + @override + Future visitFormalParametersBegin(FormalParametersBegin node) => + defaultNode(node); + + @override + Future visitFormalParametersEnd(FormalParametersEnd node) => + defaultNode(node); + + @override + Future visitClassFieldsEnd(ClassFieldsEnd node) => defaultNode(node); + + @override + Future visitMixinFieldsEnd(MixinFieldsEnd node) => defaultNode(node); + + @override + Future visitExtensionFieldsEnd(ExtensionFieldsEnd node) => + defaultNode(node); + + @override + Future visitExtensionTypeFieldsEnd(ExtensionTypeFieldsEnd node) => + defaultNode(node); + + @override + Future visitEnumFieldsEnd(EnumFieldsEnd node) => defaultNode(node); + + @override + Future visitEnumMethodEnd(EnumMethodEnd node) => defaultNode(node); + + @override + Future visitForInitializerEmptyStatementHandle( + ForInitializerEmptyStatementHandle node) => + defaultNode(node); + + @override + Future visitForInitializerExpressionStatementHandle( + ForInitializerExpressionStatementHandle node) => + defaultNode(node); + + @override + Future visitForInitializerLocalVariableDeclarationHandle( + ForInitializerLocalVariableDeclarationHandle node) => + defaultNode(node); + + @override + Future visitForInitializerPatternVariableAssignmentHandle( + ForInitializerPatternVariableAssignmentHandle node) => + defaultNode(node); + + @override + Future visitForStatementBegin(ForStatementBegin node) => + defaultNode(node); + + @override + Future visitForLoopPartsHandle(ForLoopPartsHandle node) => + defaultNode(node); + + @override + Future visitForStatementEnd(ForStatementEnd node) => defaultNode(node); + + @override + Future visitForStatementBodyBegin(ForStatementBodyBegin node) => + defaultNode(node); + + @override + Future visitForStatementBodyEnd(ForStatementBodyEnd node) => + defaultNode(node); + + @override + Future visitForInLoopPartsHandle(ForInLoopPartsHandle node) => + defaultNode(node); + + @override + Future visitForInEnd(ForInEnd node) => defaultNode(node); + + @override + Future visitForInExpressionBegin(ForInExpressionBegin node) => + defaultNode(node); + + @override + Future visitForInExpressionEnd(ForInExpressionEnd node) => + defaultNode(node); + + @override + Future visitForInBodyBegin(ForInBodyBegin node) => defaultNode(node); + + @override + Future visitForInBodyEnd(ForInBodyEnd node) => defaultNode(node); + + @override + Future visitNamedFunctionExpressionBegin( + NamedFunctionExpressionBegin node) => + defaultNode(node); + + @override + Future visitNamedFunctionExpressionEnd( + NamedFunctionExpressionEnd node) => + defaultNode(node); + + @override + Future visitLocalFunctionDeclarationBegin( + LocalFunctionDeclarationBegin node) => + defaultNode(node); + + @override + Future visitLocalFunctionDeclarationEnd( + LocalFunctionDeclarationEnd node) => + defaultNode(node); + + @override + Future visitBlockFunctionBodyBegin(BlockFunctionBodyBegin node) => + defaultNode(node); + + @override + Future visitBlockFunctionBodyEnd(BlockFunctionBodyEnd node) => + defaultNode(node); + + @override + Future visitNoFunctionBodyHandle(NoFunctionBodyHandle node) => + defaultNode(node); + + @override + Future visitFunctionBodySkippedHandle(FunctionBodySkippedHandle node) => + defaultNode(node); + + @override + Future visitFunctionNameBegin(FunctionNameBegin node) => + defaultNode(node); + + @override + Future visitFunctionNameEnd(FunctionNameEnd node) => defaultNode(node); + + @override + Future visitTypedefBegin(TypedefBegin node) => defaultNode(node); + + @override + Future visitTypedefEnd(TypedefEnd node) => defaultNode(node); + + @override + Future visitClassWithClauseHandle(ClassWithClauseHandle node) => + defaultNode(node); + + @override + Future visitClassNoWithClauseHandle(ClassNoWithClauseHandle node) => + defaultNode(node); + + @override + Future visitEnumWithClauseHandle(EnumWithClauseHandle node) => + defaultNode(node); + + @override + Future visitEnumNoWithClauseHandle(EnumNoWithClauseHandle node) => + defaultNode(node); + + @override + Future visitMixinWithClauseHandle(MixinWithClauseHandle node) => + defaultNode(node); + + @override + Future visitNamedMixinApplicationBegin( + NamedMixinApplicationBegin node) => + defaultNode(node); + + @override + Future visitNamedMixinApplicationWithClauseHandle( + NamedMixinApplicationWithClauseHandle node) => + defaultNode(node); + + @override + Future visitNamedMixinApplicationEnd(NamedMixinApplicationEnd node) => + defaultNode(node); + + @override + Future visitHideBegin(HideBegin node) => defaultNode(node); + + @override + Future visitHideEnd(HideEnd node) => defaultNode(node); + + @override + Future visitIdentifierListHandle(IdentifierListHandle node) => + defaultNode(node); + + @override + Future visitTypeListBegin(TypeListBegin node) => defaultNode(node); + + @override + Future visitTypeListEnd(TypeListEnd node) => defaultNode(node); + + @override + Future visitIfStatementBegin(IfStatementBegin node) => + defaultNode(node); + + @override + Future visitIfStatementEnd(IfStatementEnd node) => defaultNode(node); + + @override + Future visitThenStatementBegin(ThenStatementBegin node) => + defaultNode(node); + + @override + Future visitThenStatementEnd(ThenStatementEnd node) => + defaultNode(node); + + @override + Future visitElseStatementBegin(ElseStatementBegin node) => + defaultNode(node); + + @override + Future visitElseStatementEnd(ElseStatementEnd node) => + defaultNode(node); + + @override + Future visitImportBegin(ImportBegin node) => defaultNode(node); + + @override + Future visitImportPrefixHandle(ImportPrefixHandle node) => + defaultNode(node); + + @override + Future visitImportEnd(ImportEnd node) => defaultNode(node); + + @override + Future visitRecoverImportHandle(RecoverImportHandle node) => + defaultNode(node); + + @override + Future visitConditionalUrisBegin(ConditionalUrisBegin node) => + defaultNode(node); + + @override + Future visitConditionalUrisEnd(ConditionalUrisEnd node) => + defaultNode(node); + + @override + Future visitConditionalUriBegin(ConditionalUriBegin node) => + defaultNode(node); + + @override + Future visitConditionalUriEnd(ConditionalUriEnd node) => + defaultNode(node); + + @override + Future visitDottedNameHandle(DottedNameHandle node) => + defaultNode(node); + + @override + Future visitImplicitCreationExpressionBegin( + ImplicitCreationExpressionBegin node) => + defaultNode(node); + + @override + Future visitImplicitCreationExpressionEnd( + ImplicitCreationExpressionEnd node) => + defaultNode(node); + + @override + Future visitInitializedIdentifierBegin( + InitializedIdentifierBegin node) => + defaultNode(node); + + @override + Future visitInitializedIdentifierEnd(InitializedIdentifierEnd node) => + defaultNode(node); + + @override + Future visitFieldInitializerBegin(FieldInitializerBegin node) => + defaultNode(node); + + @override + Future visitFieldInitializerEnd(FieldInitializerEnd node) => + defaultNode(node); + + @override + Future visitNoFieldInitializerHandle(NoFieldInitializerHandle node) => + defaultNode(node); + + @override + Future visitVariableInitializerBegin(VariableInitializerBegin node) => + defaultNode(node); + + @override + Future visitVariableInitializerEnd(VariableInitializerEnd node) => + defaultNode(node); + + @override + Future visitNoVariableInitializerHandle( + NoVariableInitializerHandle node) => + defaultNode(node); + + @override + Future visitInitializerBegin(InitializerBegin node) => + defaultNode(node); + + @override + Future visitInitializerEnd(InitializerEnd node) => defaultNode(node); + + @override + Future visitInitializersBegin(InitializersBegin node) => + defaultNode(node); + + @override + Future visitInitializersEnd(InitializersEnd node) => defaultNode(node); + + @override + Future visitNoInitializersHandle(NoInitializersHandle node) => + defaultNode(node); + + @override + Future visitInvalidExpressionHandle(InvalidExpressionHandle node) => + defaultNode(node); + + @override + Future visitInvalidFunctionBodyHandle(InvalidFunctionBodyHandle node) => + defaultNode(node); + + @override + Future visitInvalidTypeReferenceHandle( + InvalidTypeReferenceHandle node) => + defaultNode(node); + + @override + Future visitLabelHandle(LabelHandle node) => defaultNode(node); + + @override + Future visitLabeledStatementBegin(LabeledStatementBegin node) => + defaultNode(node); + + @override + Future visitLabeledStatementEnd(LabeledStatementEnd node) => + defaultNode(node); + + @override + Future visitLibraryAugmentationBegin(LibraryAugmentationBegin node) => + defaultNode(node); + + @override + Future visitLibraryAugmentationEnd(LibraryAugmentationEnd node) => + defaultNode(node); + + @override + Future visitLibraryNameBegin(LibraryNameBegin node) => + defaultNode(node); + + @override + Future visitLibraryNameEnd(LibraryNameEnd node) => defaultNode(node); + + @override + Future visitLiteralMapEntryHandle(LiteralMapEntryHandle node) => + defaultNode(node); + + @override + Future visitMapPatternEntryHandle(MapPatternEntryHandle node) => + defaultNode(node); + + @override + Future visitLiteralStringBegin(LiteralStringBegin node) => + defaultNode(node); + + @override + Future visitInterpolationExpressionHandle( + InterpolationExpressionHandle node) => + defaultNode(node); + + @override + Future visitLiteralStringEnd(LiteralStringEnd node) => + defaultNode(node); + + @override + Future visitAdjacentStringLiteralsHandle( + AdjacentStringLiteralsHandle node) => + defaultNode(node); + + @override + Future visitMemberBegin(MemberBegin node) => defaultNode(node); + + @override + Future visitInvalidMemberHandle(InvalidMemberHandle node) => + defaultNode(node); + + @override + Future visitMemberEnd(MemberEnd node) => defaultNode(node); + + @override + Future visitMethodBegin(MethodBegin node) => defaultNode(node); + + @override + Future visitClassMethodEnd(ClassMethodEnd node) => defaultNode(node); + + @override + Future visitMixinMethodEnd(MixinMethodEnd node) => defaultNode(node); + + @override + Future visitExtensionMethodEnd(ExtensionMethodEnd node) => + defaultNode(node); + + @override + Future visitExtensionTypeMethodEnd(ExtensionTypeMethodEnd node) => + defaultNode(node); + + @override + Future visitClassConstructorEnd(ClassConstructorEnd node) => + defaultNode(node); + + @override + Future visitMixinConstructorEnd(MixinConstructorEnd node) => + defaultNode(node); + + @override + Future visitExtensionConstructorEnd(ExtensionConstructorEnd node) => + defaultNode(node); + + @override + Future visitExtensionTypeConstructorEnd( + ExtensionTypeConstructorEnd node) => + defaultNode(node); + + @override + Future visitMetadataStarBegin(MetadataStarBegin node) => + defaultNode(node); + + @override + Future visitMetadataStarEnd(MetadataStarEnd node) => defaultNode(node); + + @override + Future visitMetadataBegin(MetadataBegin node) => defaultNode(node); + + @override + Future visitMetadataEnd(MetadataEnd node) => defaultNode(node); + + @override + Future visitOptionalFormalParametersBegin( + OptionalFormalParametersBegin node) => + defaultNode(node); + + @override + Future visitOptionalFormalParametersEnd( + OptionalFormalParametersEnd node) => + defaultNode(node); + + @override + Future visitPartBegin(PartBegin node) => defaultNode(node); + + @override + Future visitPartEnd(PartEnd node) => defaultNode(node); + + @override + Future visitPartOfBegin(PartOfBegin node) => defaultNode(node); + + @override + Future visitPartOfEnd(PartOfEnd node) => defaultNode(node); + + @override + Future visitRedirectingFactoryBodyBegin( + RedirectingFactoryBodyBegin node) => + defaultNode(node); + + @override + Future visitRedirectingFactoryBodyEnd(RedirectingFactoryBodyEnd node) => + defaultNode(node); + + @override + Future visitReturnStatementBegin(ReturnStatementBegin node) => + defaultNode(node); + + @override + Future visitNativeFunctionBodyHandle(NativeFunctionBodyHandle node) => + defaultNode(node); + + @override + Future visitNativeFunctionBodyIgnoredHandle( + NativeFunctionBodyIgnoredHandle node) => + defaultNode(node); + + @override + Future visitNativeFunctionBodySkippedHandle( + NativeFunctionBodySkippedHandle node) => + defaultNode(node); + + @override + Future visitEmptyFunctionBodyHandle(EmptyFunctionBodyHandle node) => + defaultNode(node); + + @override + Future visitExpressionFunctionBodyHandle( + ExpressionFunctionBodyHandle node) => + defaultNode(node); + + @override + Future visitReturnStatementEnd(ReturnStatementEnd node) => + defaultNode(node); + + @override + Future visitSendHandle(SendHandle node) => defaultNode(node); + + @override + Future visitShowBegin(ShowBegin node) => defaultNode(node); + + @override + Future visitShowEnd(ShowEnd node) => defaultNode(node); + + @override + Future visitSwitchStatementBegin(SwitchStatementBegin node) => + defaultNode(node); + + @override + Future visitSwitchStatementEnd(SwitchStatementEnd node) => + defaultNode(node); + + @override + Future visitSwitchExpressionBegin(SwitchExpressionBegin node) => + defaultNode(node); + + @override + Future visitSwitchExpressionEnd(SwitchExpressionEnd node) => + defaultNode(node); + + @override + Future visitSwitchBlockBegin(SwitchBlockBegin node) => + defaultNode(node); + + @override + Future visitSwitchBlockEnd(SwitchBlockEnd node) => defaultNode(node); + + @override + Future visitSwitchExpressionBlockBegin( + SwitchExpressionBlockBegin node) => + defaultNode(node); + + @override + Future visitSwitchExpressionBlockEnd(SwitchExpressionBlockEnd node) => + defaultNode(node); + + @override + Future visitLiteralSymbolBegin(LiteralSymbolBegin node) => + defaultNode(node); + + @override + Future visitLiteralSymbolEnd(LiteralSymbolEnd node) => + defaultNode(node); + + @override + Future visitThrowExpressionHandle(ThrowExpressionHandle node) => + defaultNode(node); + + @override + Future visitRethrowStatementBegin(RethrowStatementBegin node) => + defaultNode(node); + + @override + Future visitRethrowStatementEnd(RethrowStatementEnd node) => + defaultNode(node); + + @override + Future visitTopLevelDeclarationEnd(TopLevelDeclarationEnd node) => + defaultNode(node); + + @override + Future visitInvalidTopLevelDeclarationHandle( + InvalidTopLevelDeclarationHandle node) => + defaultNode(node); + + @override + Future visitTopLevelMemberBegin(TopLevelMemberBegin node) => + defaultNode(node); + + @override + Future visitFieldsBegin(FieldsBegin node) => defaultNode(node); + + @override + Future visitTopLevelFieldsEnd(TopLevelFieldsEnd node) => + defaultNode(node); + + @override + Future visitTopLevelMethodBegin(TopLevelMethodBegin node) => + defaultNode(node); + + @override + Future visitTopLevelMethodEnd(TopLevelMethodEnd node) => + defaultNode(node); + + @override + Future visitTryStatementBegin(TryStatementBegin node) => + defaultNode(node); + + @override + Future visitCatchClauseBegin(CatchClauseBegin node) => + defaultNode(node); + + @override + Future visitCatchClauseEnd(CatchClauseEnd node) => defaultNode(node); + + @override + Future visitCatchBlockHandle(CatchBlockHandle node) => + defaultNode(node); + + @override + Future visitFinallyBlockHandle(FinallyBlockHandle node) => + defaultNode(node); + + @override + Future visitTryStatementEnd(TryStatementEnd node) => defaultNode(node); + + @override + Future visitTypeHandle(TypeHandle node) => defaultNode(node); + + @override + Future visitNonNullAssertExpressionHandle( + NonNullAssertExpressionHandle node) => + defaultNode(node); + + @override + Future visitNullAssertPatternHandle(NullAssertPatternHandle node) => + defaultNode(node); + + @override + Future visitNullCheckPatternHandle(NullCheckPatternHandle node) => + defaultNode(node); + + @override + Future visitAssignedVariablePatternHandle( + AssignedVariablePatternHandle node) => + defaultNode(node); + + @override + Future visitDeclaredVariablePatternHandle( + DeclaredVariablePatternHandle node) => + defaultNode(node); + + @override + Future visitWildcardPatternHandle(WildcardPatternHandle node) => + defaultNode(node); + + @override + Future visitNoNameHandle(NoNameHandle node) => defaultNode(node); + + @override + Future visitRecordTypeBegin(RecordTypeBegin node) => defaultNode(node); + + @override + Future visitRecordTypeEnd(RecordTypeEnd node) => defaultNode(node); + + @override + Future visitRecordTypeEntryBegin(RecordTypeEntryBegin node) => + defaultNode(node); + + @override + Future visitRecordTypeEntryEnd(RecordTypeEntryEnd node) => + defaultNode(node); + + @override + Future visitRecordTypeNamedFieldsBegin( + RecordTypeNamedFieldsBegin node) => + defaultNode(node); + + @override + Future visitRecordTypeNamedFieldsEnd(RecordTypeNamedFieldsEnd node) => + defaultNode(node); + + @override + Future visitFunctionTypeBegin(FunctionTypeBegin node) => + defaultNode(node); + + @override + Future visitFunctionTypeEnd(FunctionTypeEnd node) => defaultNode(node); + + @override + Future visitTypeArgumentsBegin(TypeArgumentsBegin node) => + defaultNode(node); + + @override + Future visitTypeArgumentsEnd(TypeArgumentsEnd node) => + defaultNode(node); + + @override + Future visitInvalidTypeArgumentsHandle( + InvalidTypeArgumentsHandle node) => + defaultNode(node); + + @override + Future visitNoTypeArgumentsHandle(NoTypeArgumentsHandle node) => + defaultNode(node); + + @override + Future visitTypeVariableBegin(TypeVariableBegin node) => + defaultNode(node); + + @override + Future visitTypeVariablesDefinedHandle( + TypeVariablesDefinedHandle node) => + defaultNode(node); + + @override + Future visitTypeVariableEnd(TypeVariableEnd node) => defaultNode(node); + + @override + Future visitTypeVariablesBegin(TypeVariablesBegin node) => + defaultNode(node); + + @override + Future visitTypeVariablesEnd(TypeVariablesEnd node) => + defaultNode(node); + + @override + Future visitFunctionExpressionBegin(FunctionExpressionBegin node) => + defaultNode(node); + + @override + Future visitFunctionExpressionEnd(FunctionExpressionEnd node) => + defaultNode(node); + + @override + Future visitVariablesDeclarationBegin(VariablesDeclarationBegin node) => + defaultNode(node); + + @override + Future visitVariablesDeclarationEnd(VariablesDeclarationEnd node) => + defaultNode(node); + + @override + Future visitWhileStatementBegin(WhileStatementBegin node) => + defaultNode(node); + + @override + Future visitWhileStatementEnd(WhileStatementEnd node) => + defaultNode(node); + + @override + Future visitAsOperatorTypeBegin(AsOperatorTypeBegin node) => + defaultNode(node); + + @override + Future visitAsOperatorTypeEnd(AsOperatorTypeEnd node) => + defaultNode(node); + + @override + Future visitAsOperatorHandle(AsOperatorHandle node) => + defaultNode(node); + + @override + Future visitCastPatternHandle(CastPatternHandle node) => + defaultNode(node); + + @override + Future visitAssignmentExpressionHandle( + AssignmentExpressionHandle node) => + defaultNode(node); + + @override + Future visitBinaryExpressionBegin(BinaryExpressionBegin node) => + defaultNode(node); + + @override + Future visitBinaryExpressionEnd(BinaryExpressionEnd node) => + defaultNode(node); + + @override + Future visitBinaryPatternBegin(BinaryPatternBegin node) => + defaultNode(node); + + @override + Future visitBinaryPatternEnd(BinaryPatternEnd node) => + defaultNode(node); + + @override + Future visitEndingBinaryExpressionHandle( + EndingBinaryExpressionHandle node) => + defaultNode(node); + + @override + Future visitConditionalExpressionBegin( + ConditionalExpressionBegin node) => + defaultNode(node); + + @override + Future visitConditionalExpressionColonHandle( + ConditionalExpressionColonHandle node) => + defaultNode(node); + + @override + Future visitConditionalExpressionEnd(ConditionalExpressionEnd node) => + defaultNode(node); + + @override + Future visitConstExpressionBegin(ConstExpressionBegin node) => + defaultNode(node); + + @override + Future visitConstExpressionEnd(ConstExpressionEnd node) => + defaultNode(node); + + @override + Future visitConstFactoryHandle(ConstFactoryHandle node) => + defaultNode(node); + + @override + Future visitForControlFlowBegin(ForControlFlowBegin node) => + defaultNode(node); + + @override + Future visitForControlFlowEnd(ForControlFlowEnd node) => + defaultNode(node); + + @override + Future visitForInControlFlowEnd(ForInControlFlowEnd node) => + defaultNode(node); + + @override + Future visitIfControlFlowBegin(IfControlFlowBegin node) => + defaultNode(node); + + @override + Future visitThenControlFlowHandle(ThenControlFlowHandle node) => + defaultNode(node); + + @override + Future visitElseControlFlowHandle(ElseControlFlowHandle node) => + defaultNode(node); + + @override + Future visitIfControlFlowEnd(IfControlFlowEnd node) => + defaultNode(node); + + @override + Future visitIfElseControlFlowEnd(IfElseControlFlowEnd node) => + defaultNode(node); + + @override + Future visitSpreadExpressionHandle(SpreadExpressionHandle node) => + defaultNode(node); + + @override + Future visitNullAwareElementHandle(NullAwareElementHandle node) => + defaultNode(node); + + @override + Future visitRestPatternHandle(RestPatternHandle node) => + defaultNode(node); + + @override + Future visitFunctionTypedFormalParameterBegin( + FunctionTypedFormalParameterBegin node) => + defaultNode(node); + + @override + Future visitFunctionTypedFormalParameterEnd( + FunctionTypedFormalParameterEnd node) => + defaultNode(node); + + @override + Future visitIdentifierHandle(IdentifierHandle node) => + defaultNode(node); + + @override + Future visitIndexedExpressionHandle(IndexedExpressionHandle node) => + defaultNode(node); + + @override + Future visitIsOperatorTypeBegin(IsOperatorTypeBegin node) => + defaultNode(node); + + @override + Future visitIsOperatorTypeEnd(IsOperatorTypeEnd node) => + defaultNode(node); + + @override + Future visitIsOperatorHandle(IsOperatorHandle node) => + defaultNode(node); + + @override + Future visitLiteralBoolHandle(LiteralBoolHandle node) => + defaultNode(node); + + @override + Future visitBreakStatementHandle(BreakStatementHandle node) => + defaultNode(node); + + @override + Future visitContinueStatementHandle(ContinueStatementHandle node) => + defaultNode(node); + + @override + Future visitEmptyStatementHandle(EmptyStatementHandle node) => + defaultNode(node); + + @override + Future visitAssertBegin(AssertBegin node) => defaultNode(node); + + @override + Future visitAssertEnd(AssertEnd node) => defaultNode(node); + + @override + Future visitLiteralDoubleHandle(LiteralDoubleHandle node) => + defaultNode(node); + + @override + Future visitLiteralDoubleWithSeparatorsHandle( + LiteralDoubleWithSeparatorsHandle node) => + defaultNode(node); + + @override + Future visitLiteralIntHandle(LiteralIntHandle node) => + defaultNode(node); + + @override + Future visitLiteralIntWithSeparatorsHandle( + LiteralIntWithSeparatorsHandle node) => + defaultNode(node); + + @override + Future visitLiteralListHandle(LiteralListHandle node) => + defaultNode(node); + + @override + Future visitListPatternHandle(ListPatternHandle node) => + defaultNode(node); + + @override + Future visitLiteralSetOrMapHandle(LiteralSetOrMapHandle node) => + defaultNode(node); + + @override + Future visitMapPatternHandle(MapPatternHandle node) => + defaultNode(node); + + @override + Future visitLiteralNullHandle(LiteralNullHandle node) => + defaultNode(node); + + @override + Future visitNativeClauseHandle(NativeClauseHandle node) => + defaultNode(node); + + @override + Future visitNamedArgumentHandle(NamedArgumentHandle node) => + defaultNode(node); + + @override + Future visitPatternFieldHandle(PatternFieldHandle node) => + defaultNode(node); + + @override + Future visitNamedRecordFieldHandle(NamedRecordFieldHandle node) => + defaultNode(node); + + @override + Future visitNewExpressionBegin(NewExpressionBegin node) => + defaultNode(node); + + @override + Future visitNewExpressionEnd(NewExpressionEnd node) => + defaultNode(node); + + @override + Future visitNoArgumentsHandle(NoArgumentsHandle node) => + defaultNode(node); + + @override + Future visitNoConstructorReferenceContinuationAfterTypeArgumentsHandle( + NoConstructorReferenceContinuationAfterTypeArgumentsHandle node) => + defaultNode(node); + + @override + Future visitNoTypeNameInConstructorReferenceHandle( + NoTypeNameInConstructorReferenceHandle node) => + defaultNode(node); + + @override + Future visitNoTypeHandle(NoTypeHandle node) => defaultNode(node); + + @override + Future visitNoTypeVariablesHandle(NoTypeVariablesHandle node) => + defaultNode(node); + + @override + Future visitOperatorHandle(OperatorHandle node) => defaultNode(node); + + @override + Future visitSwitchCaseNoWhenClauseHandle( + SwitchCaseNoWhenClauseHandle node) => + defaultNode(node); + + @override + Future visitSwitchExpressionCasePatternHandle( + SwitchExpressionCasePatternHandle node) => + defaultNode(node); + + @override + Future visitSymbolVoidHandle(SymbolVoidHandle node) => + defaultNode(node); + + @override + Future visitOperatorNameHandle(OperatorNameHandle node) => + defaultNode(node); + + @override + Future visitInvalidOperatorNameHandle(InvalidOperatorNameHandle node) => + defaultNode(node); + + @override + Future visitParenthesizedConditionHandle( + ParenthesizedConditionHandle node) => + defaultNode(node); + + @override + Future visitPatternBegin(PatternBegin node) => defaultNode(node); + + @override + Future visitPatternGuardBegin(PatternGuardBegin node) => + defaultNode(node); + + @override + Future visitParenthesizedExpressionOrRecordLiteralBegin( + ParenthesizedExpressionOrRecordLiteralBegin node) => + defaultNode(node); + + @override + Future visitSwitchCaseWhenClauseBegin(SwitchCaseWhenClauseBegin node) => + defaultNode(node); + + @override + Future visitRecordLiteralEnd(RecordLiteralEnd node) => + defaultNode(node); + + @override + Future visitRecordPatternHandle(RecordPatternHandle node) => + defaultNode(node); + + @override + Future visitPatternEnd(PatternEnd node) => defaultNode(node); + + @override + Future visitPatternGuardEnd(PatternGuardEnd node) => defaultNode(node); + + @override + Future visitParenthesizedExpressionEnd( + ParenthesizedExpressionEnd node) => + defaultNode(node); + + @override + Future visitSwitchCaseWhenClauseEnd(SwitchCaseWhenClauseEnd node) => + defaultNode(node); + + @override + Future visitParenthesizedPatternHandle( + ParenthesizedPatternHandle node) => + defaultNode(node); + + @override + Future visitConstantPatternBegin(ConstantPatternBegin node) => + defaultNode(node); + + @override + Future visitConstantPatternEnd(ConstantPatternEnd node) => + defaultNode(node); + + @override + Future visitObjectPatternHandle(ObjectPatternHandle node) => + defaultNode(node); + + @override + Future visitQualifiedHandle(QualifiedHandle node) => defaultNode(node); + + @override + Future visitStringPartHandle(StringPartHandle node) => + defaultNode(node); + + @override + Future visitSuperExpressionHandle(SuperExpressionHandle node) => + defaultNode(node); + + @override + Future visitAugmentSuperExpressionHandle( + AugmentSuperExpressionHandle node) => + defaultNode(node); + + @override + Future visitSwitchCaseBegin(SwitchCaseBegin node) => defaultNode(node); + + @override + Future visitSwitchCaseEnd(SwitchCaseEnd node) => defaultNode(node); + + @override + Future visitSwitchExpressionCaseBegin(SwitchExpressionCaseBegin node) => + defaultNode(node); + + @override + Future visitSwitchExpressionCaseEnd(SwitchExpressionCaseEnd node) => + defaultNode(node); + + @override + Future visitThisExpressionHandle(ThisExpressionHandle node) => + defaultNode(node); + + @override + Future visitUnaryPostfixAssignmentExpressionHandle( + UnaryPostfixAssignmentExpressionHandle node) => + defaultNode(node); + + @override + Future visitUnaryPrefixExpressionHandle( + UnaryPrefixExpressionHandle node) => + defaultNode(node); + + @override + Future visitRelationalPatternHandle(RelationalPatternHandle node) => + defaultNode(node); + + @override + Future visitUnaryPrefixAssignmentExpressionHandle( + UnaryPrefixAssignmentExpressionHandle node) => + defaultNode(node); + + @override + Future visitFormalParameterDefaultValueExpressionBegin( + FormalParameterDefaultValueExpressionBegin node) => + defaultNode(node); + + @override + Future visitFormalParameterDefaultValueExpressionEnd( + FormalParameterDefaultValueExpressionEnd node) => + defaultNode(node); + + @override + Future visitValuedFormalParameterHandle( + ValuedFormalParameterHandle node) => + defaultNode(node); + + @override + Future visitFormalParameterWithoutValueHandle( + FormalParameterWithoutValueHandle node) => + defaultNode(node); + + @override + Future visitVoidKeywordHandle(VoidKeywordHandle node) => + defaultNode(node); + + @override + Future visitVoidKeywordWithTypeArgumentsHandle( + VoidKeywordWithTypeArgumentsHandle node) => + defaultNode(node); + + @override + Future visitYieldStatementBegin(YieldStatementBegin node) => + defaultNode(node); + + @override + Future visitYieldStatementEnd(YieldStatementEnd node) => + defaultNode(node); + + @override + Future visitInvalidYieldStatementEnd(InvalidYieldStatementEnd node) => + defaultNode(node); + + @override + Future visitRecoverableErrorHandle(RecoverableErrorHandle node) => + defaultNode(node); + + @override + Future visitExperimentNotEnabledHandle( + ExperimentNotEnabledHandle node) => + defaultNode(node); + + @override + Future visitErrorTokenHandle(ErrorTokenHandle node) => + defaultNode(node); + + @override + Future visitUnescapeErrorHandle(UnescapeErrorHandle node) => + defaultNode(node); + + @override + Future visitInvalidStatementHandle(InvalidStatementHandle node) => + defaultNode(node); + + @override + Future visitScriptHandle(ScriptHandle node) => defaultNode(node); + + @override + Future visitTypeArgumentApplicationHandle( + TypeArgumentApplicationHandle node) => + defaultNode(node); + + @override + Future visitNewAsIdentifierHandle(NewAsIdentifierHandle node) => + defaultNode(node); + + @override + Future visitPatternVariableDeclarationStatementHandle( + PatternVariableDeclarationStatementHandle node) => + defaultNode(node); + + @override + Future visitPatternAssignmentHandle(PatternAssignmentHandle node) => + defaultNode(node); +} diff --git a/pkg/front_end/test/crashing_test_case_minimizer_impl.dart b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart index a2e75d65d5f6..56f69b5095e3 100644 --- a/pkg/front_end/test/crashing_test_case_minimizer_impl.dart +++ b/pkg/front_end/test/crashing_test_case_minimizer_impl.dart @@ -1403,6 +1403,12 @@ worlds: decl.token.offset - 1, decl.token.offset + decl.token.length)); shouldCompile = true; what = "script"; + } else if (child.isExtensionType()) { + ExtensionTypeDeclarationEnd decl = child.asExtensionType(); + helper.replacements.add(new _Replacement( + decl.extensionKeyword.offset - 1, decl.endToken.charEnd)); + shouldCompile = true; + what = "extension type"; } if (shouldCompile) { @@ -1426,80 +1432,7 @@ worlds: if (!success) { // Also try to remove members one at a time. - for (ParserAstNode child in body.children!) { - shouldCompile = false; - if (child is MemberEnd) { - if (child.isClassConstructor()) { - ClassConstructorEnd memberDecl = - child.getClassConstructor(); - helper.replacements.add(new _Replacement( - memberDecl.beginToken.offset - 1, - memberDecl.endToken.offset + 1)); - what = "class constructor"; - shouldCompile = true; - } else if (child.isClassFields()) { - ClassFieldsEnd memberDecl = child.getClassFields(); - helper.replacements.add(new _Replacement( - memberDecl.beginToken.offset - 1, - memberDecl.endToken.offset + 1)); - what = "class fields"; - shouldCompile = true; - } else if (child.isClassMethod()) { - ClassMethodEnd memberDecl = child.getClassMethod(); - helper.replacements.add(new _Replacement( - memberDecl.beginToken.offset - 1, - memberDecl.endToken.offset + 1)); - what = "class method"; - shouldCompile = true; - } else if (child.isClassFactoryMethod()) { - ClassFactoryMethodEnd memberDecl = - child.getClassFactoryMethod(); - helper.replacements.add(new _Replacement( - memberDecl.beginToken.offset - 1, - memberDecl.endToken.offset + 1)); - what = "class factory method"; - shouldCompile = true; - } else { - // throw "$child --- ${child.children}"; - continue; - } - } else if (child.isMetadata()) { - MetadataStarEnd decl = child.asMetadata(); - List metadata = decl.getMetadataEntries(); - if (metadata.isNotEmpty) { - helper.replacements.add(new _Replacement( - metadata.first.beginToken.offset - 1, - metadata.last.endToken.charEnd)); - shouldCompile = true; - } - what = "metadata"; - } - if (shouldCompile) { - success = await _tryReplaceAndCompile( - helper, uri, initialComponent, what); - if (helper.shouldQuit) return; - if (!success) { - BlockFunctionBodyEnd? decl; - if (child is MemberEnd) { - if (child.isClassMethod()) { - decl = child.getClassMethod().getBlockFunctionBody(); - } else if (child.isClassConstructor()) { - decl = - child.getClassConstructor().getBlockFunctionBody(); - } - } - if (decl != null && - decl.beginToken.offset + 2 < decl.endToken.offset) { - helper.replacements.add(new _Replacement( - decl.beginToken.offset, decl.endToken.offset)); - what = "class member content"; - await _tryReplaceAndCompile( - helper, uri, initialComponent, what); - if (helper.shouldQuit) return; - } - } - } - } + await _deleteBlocksHelper(body, helper, uri, initialComponent); } // Try to remove "extends", "implements" etc. @@ -1549,80 +1482,38 @@ worlds: } if (!success) { - // Also try to remove members one at a time. - for (ParserAstNode child in body.children!) { - shouldCompile = false; - if (child is MemberEnd) { - if (child.isMixinConstructor()) { - MixinConstructorEnd memberDecl = - child.getMixinConstructor(); - helper.replacements.add(new _Replacement( - memberDecl.beginToken.offset - 1, - memberDecl.endToken.offset + 1)); - what = "mixin constructor"; - shouldCompile = true; - } else if (child.isMixinFields()) { - MixinFieldsEnd memberDecl = child.getMixinFields(); - helper.replacements.add(new _Replacement( - memberDecl.beginToken.offset - 1, - memberDecl.endToken.offset + 1)); - what = "mixin fields"; - shouldCompile = true; - } else if (child.isMixinMethod()) { - MixinMethodEnd memberDecl = child.getMixinMethod(); - helper.replacements.add(new _Replacement( - memberDecl.beginToken.offset - 1, - memberDecl.endToken.offset + 1)); - what = "mixin method"; - shouldCompile = true; - } else if (child.isMixinFactoryMethod()) { - MixinFactoryMethodEnd memberDecl = - child.getMixinFactoryMethod(); - helper.replacements.add(new _Replacement( - memberDecl.beginToken.offset - 1, - memberDecl.endToken.offset + 1)); - what = "mixin factory method"; - shouldCompile = true; - } else { - // throw "$child --- ${child.children}"; - continue; - } - } else if (child.isMetadata()) { - MetadataStarEnd decl = child.asMetadata(); - List metadata = decl.getMetadataEntries(); - if (metadata.isNotEmpty) { - helper.replacements.add(new _Replacement( - metadata.first.beginToken.offset - 1, - metadata.last.endToken.charEnd)); - shouldCompile = true; - } - what = "metadata"; - } - if (shouldCompile) { - success = await _tryReplaceAndCompile( - helper, uri, initialComponent, what); - if (helper.shouldQuit) return; - if (!success) { - BlockFunctionBodyEnd? decl; - if (child is MemberEnd) { - if (child.isClassMethod()) { - decl = child.getClassMethod().getBlockFunctionBody(); - } else if (child.isClassConstructor()) { - decl = - child.getClassConstructor().getBlockFunctionBody(); - } - } - if (decl != null && - decl.beginToken.offset + 2 < decl.endToken.offset) { - helper.replacements.add(new _Replacement( - decl.beginToken.offset, decl.endToken.offset)); - what = "class member content"; - await _tryReplaceAndCompile( - helper, uri, initialComponent, what); - if (helper.shouldQuit) return; - } - } - } + await _deleteBlocksHelper(body, helper, uri, initialComponent); + } + } else if (child.isExtensionType()) { + // Also try to remove all content of the extension type. + ExtensionTypeDeclarationEnd decl = child.asExtensionType(); + ClassOrMixinOrExtensionBodyEnd body = + decl.getClassOrMixinOrExtensionBody(); + if (body.beginToken.offset + 2 < body.endToken.offset) { + helper.replacements.add(new _Replacement( + body.beginToken.offset, body.endToken.offset)); + what = "extension type body"; + success = await _tryReplaceAndCompile( + helper, uri, initialComponent, what); + if (helper.shouldQuit) return; + } + + if (!success) { + await _deleteBlocksHelper(body, helper, uri, initialComponent); + } + } else if (child.isTopLevelMethod()) { + // Try to remove parameters. + TopLevelMethodEnd decl = child.asTopLevelMethod(); + FormalParametersEnd? formal = + decl.children?.whereType().firstOrNull; + if (formal != null) { + if (formal.beginToken.offset + 2 < formal.endToken.offset) { + helper.replacements.add(new _Replacement( + formal.beginToken.offset, formal.endToken.offset)); + what = "top level formals"; + success = await _tryReplaceAndCompile( + helper, uri, initialComponent, what); + if (helper.shouldQuit) return; } } } @@ -1631,6 +1522,171 @@ worlds: } } + Future _deleteBlocksHelper( + ClassOrMixinOrExtensionBodyEnd body, + _CompilationHelperClass helper, + final Uri uri, + Component initialComponent) async { + for (ParserAstNode child in body.children!) { + bool shouldCompile = false; + String what = ""; + if (child is MemberEnd) { + if (child.isClassConstructor()) { + ClassConstructorEnd memberDecl = child.getClassConstructor(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "class constructor"; + shouldCompile = true; + } else if (child.isClassFields()) { + ClassFieldsEnd memberDecl = child.getClassFields(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "class fields"; + shouldCompile = true; + } else if (child.isClassMethod()) { + ClassMethodEnd memberDecl = child.getClassMethod(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "class method"; + shouldCompile = true; + } else if (child.isClassFactoryMethod()) { + ClassFactoryMethodEnd memberDecl = child.getClassFactoryMethod(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "class factory method"; + shouldCompile = true; + } else if (child.isMixinConstructor()) { + MixinConstructorEnd memberDecl = child.getMixinConstructor(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "mixin constructor"; + shouldCompile = true; + } else if (child.isMixinFields()) { + MixinFieldsEnd memberDecl = child.getMixinFields(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "mixin fields"; + shouldCompile = true; + } else if (child.isMixinMethod()) { + MixinMethodEnd memberDecl = child.getMixinMethod(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "mixin method"; + shouldCompile = true; + } else if (child.isMixinFactoryMethod()) { + MixinFactoryMethodEnd memberDecl = child.getMixinFactoryMethod(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "mixin factory method"; + shouldCompile = true; + } else if (child.isExtensionTypeConstructor()) { + var memberDecl = child.getExtensionTypeConstructor(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "extension type constructor"; + shouldCompile = true; + } else if (child.isExtensionTypeFields()) { + ExtensionTypeFieldsEnd memberDecl = child.getExtensionTypeFields(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "extension type fields"; + shouldCompile = true; + } else if (child.isExtensionTypeMethod()) { + ExtensionTypeMethodEnd memberDecl = child.getExtensionTypeMethod(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "extension type method"; + shouldCompile = true; + } else if (child.isExtensionTypeFactoryMethod()) { + ExtensionTypeFactoryMethodEnd memberDecl = + child.getExtensionTypeFactoryMethod(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "extension type factory method"; + shouldCompile = true; + } else if (child.isExtensionConstructor()) { + var memberDecl = child.getExtensionConstructor(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "extension constructor"; + shouldCompile = true; + } else if (child.isExtensionFields()) { + ExtensionFieldsEnd memberDecl = child.getExtensionFields(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "extension fields"; + shouldCompile = true; + } else if (child.isExtensionMethod()) { + ExtensionMethodEnd memberDecl = child.getExtensionMethod(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "extension method"; + shouldCompile = true; + } else if (child.isExtensionFactoryMethod()) { + ExtensionFactoryMethodEnd memberDecl = + child.getExtensionFactoryMethod(); + helper.replacements.add(new _Replacement( + memberDecl.beginToken.offset - 1, + memberDecl.endToken.offset + 1)); + what = "extension factory method"; + shouldCompile = true; + } else { + // throw "$child --- ${child.children}"; + continue; + } + } else if (child.isMetadata()) { + MetadataStarEnd decl = child.asMetadata(); + List metadata = decl.getMetadataEntries(); + if (metadata.isNotEmpty) { + helper.replacements.add(new _Replacement( + metadata.first.beginToken.offset - 1, + metadata.last.endToken.charEnd)); + shouldCompile = true; + } + what = "metadata"; + } + if (shouldCompile) { + bool success = + await _tryReplaceAndCompile(helper, uri, initialComponent, what); + if (helper.shouldQuit) return; + if (!success) { + BlockFunctionBodyEnd? decl; + if (child is MemberEnd) { + if (child.isClassMethod()) { + decl = child.getClassMethod().getBlockFunctionBody(); + } else if (child.isClassConstructor()) { + decl = child.getClassConstructor().getBlockFunctionBody(); + } + // TODO(jensj): The other ones too maybe? + } + if (decl != null && + decl.beginToken.offset + 2 < decl.endToken.offset) { + helper.replacements.add( + new _Replacement(decl.beginToken.offset, decl.endToken.offset)); + what = "class member content"; + await _tryReplaceAndCompile(helper, uri, initialComponent, what); + if (helper.shouldQuit) return; + } + } + } + } + } + Future _tryReplaceAndCompile(_CompilationHelperClass data, Uri uri, Component initialComponent, String what) async { if (await _shouldQuit()) { @@ -1820,6 +1876,11 @@ worlds: } Version _getLanguageVersion(Uri uri, {bool crashOnFail = true}) { + if (_latestCrashingKnownInitialBuilders == null) { + // It crashed on the first compile so we have no builders. + // We'll just return something. + return ExperimentalFlag.nonNullable.enabledVersion; + } Uri asImportUri = _getImportUri(uri); LibraryBuilder? libraryBuilder = _latestCrashingKnownInitialBuilders![asImportUri]; diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt index 4709da315954..6113a0fbe9d4 100644 --- a/pkg/front_end/test/spell_checking_list_tests.txt +++ b/pkg/front_end/test/spell_checking_list_tests.txt @@ -38,6 +38,8 @@ amend amended amortized analyses +analytics +analyzerinput andahalf animal animated @@ -126,6 +128,7 @@ ca cafebabe calloc camel +cap capacity capitalized caption @@ -202,6 +205,7 @@ ctrl cumulative cursor cuts +cutting cx da dacoharkes @@ -246,6 +250,7 @@ di diagnosticable dictionaries dictionary +dicy differentiate dijkstra dijkstras @@ -314,6 +319,7 @@ exercised exercises exitcode exiting +exotic expanded expansive explainer @@ -349,6 +355,7 @@ foos forbidden forces foreground +forever forgot forrest forth @@ -367,6 +374,7 @@ futu futures fuzz fuzzed +fuzzer fuzzing fx gallery @@ -469,6 +477,7 @@ jc jk jlcontreras joo +jsonrpc jumped kernels kitty @@ -491,12 +500,14 @@ lints listening listing ln +locale locating logd logs loo lookahead loopback +lsp ma mac maker @@ -513,6 +524,7 @@ micro migrations mimicking minimize +minimized minimizer minimizing minitest @@ -559,6 +571,7 @@ nondefault nonexisting noo noted +notification noting nottest nq @@ -590,12 +603,14 @@ oracle ot outbound outliers +outstanding overflows overhead overlay overly ox pack +packet paging paint parallax @@ -678,6 +693,7 @@ refname refusing regards regenerate +regex regressions reify reject @@ -685,6 +701,7 @@ relinked remap remapping remaps +removeable rendition reorder reordering @@ -727,6 +744,7 @@ sdkroot sdks secondary secondtest +secure seemingly segment selection @@ -752,6 +770,7 @@ sigwinch silence simplistic slashes +sleep slight sliver smoke @@ -773,6 +792,7 @@ spurious sqrt squared ss +ssls sssp stacks stashed @@ -788,6 +808,7 @@ stringy strip strongest stub's +stuck stupid subcommand subdir @@ -813,6 +834,7 @@ tails taskset te templates +testfile theoretically there'll thereby @@ -889,6 +911,7 @@ vf virtually visualization vp +vscode vt vte waited @@ -906,6 +929,7 @@ wins wording workers workflow +workspace worlds worse wrongly diff --git a/pkg/front_end/tool/fuzz/Dart.g b/pkg/front_end/tool/fuzz/Dart.g new file mode 100644 index 000000000000..d4a7f9dfd0cb --- /dev/null +++ b/pkg/front_end/tool/fuzz/Dart.g @@ -0,0 +1,2288 @@ +// Copyright (c) 2017, 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. + +// CHANGES: +// +// v0.51 Support a `switchExpression` with no cases. +// +// v0.50 Add support for digit separators in numeric literals. +// +// v0.49 Add support for static and top-level members with no implementation. +// +// v0.48 Add support for enhanced parts. +// +// v0.47 Make `augment` a built-in identifier (this happened in the feature +// specification v1.10, but wasn't done here at the time). +// +// v0.46 Rename `libraryDefinition` to `libraryDeclaration`, as in the +// language specification. Add support for libraries with imports. +// +// v0.45 Update rule about augmenting extension type declaration to omit +// the primary constructor. +// +// v0.44 Support null-aware elements. +// +// v0.43 Change rule structure such that the association of metadata +// with non-terminals can be explained in a simple and consistent way. +// The derivable terms do not change. Remove `metadata` from the kind +// of `forLoopParts` where the iteration variable is an existing variable +// in scope (this is not implemented, is inconsistent anyway). +// +// v0.42 Support updated augmented `extensionDeclaration`. +// +// v0.41 Add missing `enumEntry` update for augmentations. +// +// v0.40 Include support for augmentation libraries. +// +// v0.39 Include latest changes to mixin related class modifiers. +// +// v0.38 Broaden `initializerExpression` to match implemented behavior. +// +// v0.37 Correct `libraryExport` to use `configurableUri`, not `uri`. +// +// v0.36 Update syntax from `inline class` to `extension type`, including +// a special case of primary constructors. +// +// v0.35 Change named optional parameter syntax to require '=', that is, +// remove the support for ':' as in `void f({int i: 1})`. +// +// v0.34 Add support for inline classes. +// +// v0.33 This commit does not change the derived language at all. It just +// changes several rules to use the regexp-like grammar operators to simplify +// onParts, recordLiteralNoConst, functionTypeTails, and functionType. +// +// v0.32 Remove unused non-terminal `patterns`. +// +// v0.31 Inline `identifierNotFUNCTION` into `identifier`. Replace all +// other references with `identifier` to match the spec. +// +// v0.30 Add support for the class modifiers `sealed`, `final`, `base`, +// `interface`, and for `mixin class` declarations. Also add support for +// unnamed libraries (`library;`). Introduce `otherIdentifier` to help +// maintaining consistency when the grammar is modified to mention any words +// that weren't previously mentioned, yet are not reserved or built-in. +// +// v0.29 Add an alternative in the `primary` rule to enable method invocations +// of the form `super(...)` and `super<...>(...)`. This was added to the +// language specification in May 21, b26e7287c318c0112610fe8b7e175289792dfde2, +// but the corresponding update here wasn't done here at the time. +// +// v0.28 Add support for `new` in `enumEntry`, e.g., `enum E { x.new(); }`. +// Add `identifierOrNew` non-terminal to simplify the grammar. +// +// v0.27 Remove unused non-terminals; make handling of interpolation in URIs +// consistent with the language specification. Make `partDeclaration` a +// start symbol in addition to `libraryDefinition` (such that no special +// precautions are needed in order to parse a part file). Corrected spacing +// in several rules. +// +// v0.26 Add missing `metadata` in `partDeclaration`. +// +// v0.25 Update pattern rules following changes to the patterns feature +// specification since v0.24. +// +// v0.24 Change constant pattern rules to allow Symbols and negative numbers. +// +// v0.23 Change logical pattern rules to || and &&. +// +// v0.22 Change pattern rules, following updated feature specification. +// +// v0.21 Add support for patterns. +// +// v0.20 Adjust record syntax such that () is allowed (denoting the empty +// record type and the empty record value). +// +// v0.19 Add support for super parameters, named arguments everywhere, and +// records. +// +// v0.18 Add support for enhanced `enum` declarations. +// +// v0.17 (58d917e7573c359580ade43845004dbbc62220d5) Correct `uri` to allow +// multi-line strings (raw and non-raw). +// +// v0.16 (284695f1937c262523a9a11b9084213f889c83e0) Correct instance variable +// declaration syntax such that `covariant late final` is allowed. +// +// v0.15 (6facd6dfdafa2953e8523348220d3129ea884678) Add support for +// constructor tearoffs and explicitly instantiated function tearoffs and +// type literals. +// +// v0.14 (f65c20124edd9e04f7b3a6f014f40c16f51052f6) Correct `partHeader` +// to allow uri syntax in a `PART OF` directive. +// +// v0.13 (bb5cb79a2fd57d6a480b922bc650d5cd15948753) Introduce non-terminals +// `builtinIdentifier` and `reservedWord`; update `typeAlias` to enable +// non-function type aliases; add missing `metadata` to formal parameter +// declarations; correct `symbolLiteral` to allow `VOID`; + +// v0.12 (82403371ac00ddf004be60fa7b705474d2864509) Cf. language issue #1341: +// correct `metadata`. Change `qualifiedName` such that it only includes the +// cases with a '.'; the remaining case is added where `qualifiedName` is used. +// +// v0.11 (67c703063d5b68c9e132edbaf34dfe375851f5a6) Corrections, mainly: +// `fieldFormalParameter` now allows `?` on the parameter type; cascade was +// reorganized in the spec, it is now reorganized similarly here; `?` was +// removed from argumentPart (null-aware invocation was never added). +// +// v0.10 (8ccdb9ae796d543e4ad8f339c847c02b09018d2d) Simplify grammar by making +// `constructorInvocation` an alternative in `primary`. +// +// v0.9 (f4d7951a88e1b738e22b768c3bc72bf1a1062365) Introduce abstract and +// external variables. +// +// v0.8 (a9ea9365ad8a3e3b59115bd889a55b6aa2c5a5fa) Change null-aware +// invocations of `operator []` and `operator []=` to not have a period. +// +// v0.7 (6826faf583f6a543b1a0e2e85bd6a8042607ce00) Introduce extension and +// mixin declarations. Revise rules about string literals and string +// interpolation. Reorganize "keywords" (built-in identifiers, reserved words, +// other words that are specified in the grammar and not parsed as IDENTIFIER) +// into explicitly marked groups. Change the cascade syntax to be +// compositional. +// +// v0.6 (a58052974ec2b4b334922c5227b043ed2b9c2cc5) Introduce syntax associated +// with null safety. +// +// v0.5 (56793b3d4714d4818d855a72074d5295489aef3f) Stop treating `ASYNC` as a +// conditional reserved word (only `AWAIT` and `YIELD` get this treatment). +// +// v0.4 Added support for 'unified collections' (spreads and control flow +// in collection literals). +// +// v0.3 Updated to use ANTLR v4 rather than antlr3. +// +// v0.2 Changed top level variable declarations to avoid redundant and +// misleading occurrence of (FINAL|CONST). +// +// v0.1 First version available in the SDK github repository. Covers the +// Dart language as specified in the language specification based on the +// many grammar rule snippets. That grammar was then adjusted to remove +// known issues (e.g., misplaced metadata) and to resolve ambiguities. + +grammar Dart; + +@parser::header{ +import java.util.Stack; +} + +@lexer::header{ +import java.util.Stack; +} + +@parser::members { + static String filePath = null; + static boolean errorHasOccurred = false; + + /// Must be invoked before the first error is reported for a library. + /// Will print the name of the library and indicate that it has errors. + static void prepareForErrors() { + errorHasOccurred = true; + System.err.println("Syntax error in " + filePath + ":"); + } + + /// Parse library, return true if success, false if errors occurred. + public boolean parseLibrary(String filePath) throws RecognitionException { + this.filePath = filePath; + errorHasOccurred = false; + startSymbol(); + return !errorHasOccurred; + } + + // Enable the parser to treat AWAIT/YIELD as keywords in the body of an + // `async`, `async*`, or `sync*` function. Access via methods below. + private Stack asyncEtcAreKeywords = new Stack(); + { asyncEtcAreKeywords.push(false); } + + // Use this to indicate that we are now entering an `async`, `async*`, + // or `sync*` function. + void startAsyncFunction() { asyncEtcAreKeywords.push(true); } + + // Use this to indicate that we are now entering a function which is + // neither `async`, `async*`, nor `sync*`. + void startNonAsyncFunction() { asyncEtcAreKeywords.push(false); } + + // Use this to indicate that we are now leaving any function. + void endFunction() { asyncEtcAreKeywords.pop(); } + + // Whether we can recognize AWAIT/YIELD as an identifier/typeIdentifier. + boolean asyncEtcPredicate(int tokenId) { + if (tokenId == AWAIT || tokenId == YIELD) { + return !asyncEtcAreKeywords.peek(); + } + return false; + } +} + +@lexer::members{ + public static final int BRACE_NORMAL = 1; + public static final int BRACE_SINGLE = 2; + public static final int BRACE_DOUBLE = 3; + public static final int BRACE_THREE_SINGLE = 4; + public static final int BRACE_THREE_DOUBLE = 5; + + // Enable the parser to handle string interpolations via brace matching. + // The top of the `braceLevels` stack describes the most recent unmatched + // '{'. This is needed in order to enable/disable certain lexer rules. + // + // NORMAL: Most recent unmatched '{' was not string literal related. + // SINGLE: Most recent unmatched '{' was `'...${`. + // DOUBLE: Most recent unmatched '{' was `"...${`. + // THREE_SINGLE: Most recent unmatched '{' was `'''...${`. + // THREE_DOUBLE: Most recent unmatched '{' was `"""...${`. + // + // Access via functions below. + private Stack braceLevels = new Stack(); + + // Whether we are currently in a string literal context, and which one. + boolean currentBraceLevel(int braceLevel) { + if (braceLevels.empty()) return false; + return braceLevels.peek() == braceLevel; + } + + // Use this to indicate that we are now entering a specific '{...}'. + // Call it after accepting the '{'. + void enterBrace() { + braceLevels.push(BRACE_NORMAL); + } + void enterBraceSingleQuote() { + braceLevels.push(BRACE_SINGLE); + } + void enterBraceDoubleQuote() { + braceLevels.push(BRACE_DOUBLE); + } + void enterBraceThreeSingleQuotes() { + braceLevels.push(BRACE_THREE_SINGLE); + } + void enterBraceThreeDoubleQuotes() { + braceLevels.push(BRACE_THREE_DOUBLE); + } + + // Use this to indicate that we are now exiting a specific '{...}', + // no matter which kind. Call it before accepting the '}'. + void exitBrace() { + // We might raise a parse error here if the stack is empty, but the + // parsing rules should ensure that we get a parse error anyway, and + // it is not a big problem for the spec parser even if it misinterprets + // the brace structure of some programs with syntax errors. + if (!braceLevels.empty()) braceLevels.pop(); + } +} + +// ---------------------------------------- Grammar rules. + +startSymbol + : libraryDeclaration + | partDeclaration + ; + +libraryDeclaration + : FEFF? SCRIPT_TAG? + libraryName? + importOrExport* + partDirective* + (metadata topLevelDefinition)* + EOF + ; + +topLevelDefinition + : classDeclaration + | mixinDeclaration + | extensionTypeDeclaration + | extensionDeclaration + | enumType + | typeAlias + | AUGMENT? EXTERNAL functionSignature ';' + | AUGMENT? EXTERNAL getterSignature ';' + | AUGMENT? EXTERNAL setterSignature ';' + | AUGMENT? EXTERNAL finalVarOrType identifierList ';' + | AUGMENT? getterSignature (functionBody | ';') + | AUGMENT? setterSignature (functionBody | ';') + | AUGMENT? functionSignature (functionBody | ';') + | AUGMENT? (FINAL | CONST) type? initializedIdentifierList ';' + | AUGMENT? LATE FINAL type? initializedIdentifierList ';' + | AUGMENT? LATE? varOrType initializedIdentifierList ';' + ; + +declaredIdentifier + : COVARIANT? finalConstVarOrType identifier + ; + +finalConstVarOrType + : LATE? FINAL type? + | CONST type? + | LATE? varOrType + ; + +finalVarOrType + : FINAL type? + | varOrType + ; + +varOrType + : VAR + | type + ; + +initializedIdentifier + : identifier ('=' expression)? + ; + +initializedIdentifierList + : initializedIdentifier (',' initializedIdentifier)* + ; + +functionSignature + : type? identifier formalParameterPart + ; + +functionBody + : '=>' { startNonAsyncFunction(); } expression { endFunction(); } ';' + | { startNonAsyncFunction(); } block { endFunction(); } + | ASYNC '=>' + { startAsyncFunction(); } expression { endFunction(); } ';' + | (ASYNC | ASYNC '*' | SYNC '*') + { startAsyncFunction(); } block { endFunction(); } + ; + +block + : LBRACE statements RBRACE + ; + +formalParameterPart + : typeParameters? formalParameterList + ; + +formalParameterList + : '(' ')' + | '(' normalFormalParameters ','? ')' + | '(' normalFormalParameters ',' optionalOrNamedFormalParameters ')' + | '(' optionalOrNamedFormalParameters ')' + ; + +normalFormalParameters + : normalFormalParameter (',' normalFormalParameter)* + ; + +optionalOrNamedFormalParameters + : optionalPositionalFormalParameters + | namedFormalParameters + ; + +optionalPositionalFormalParameters + : '[' defaultFormalParameter (',' defaultFormalParameter)* ','? ']' + ; + +namedFormalParameters + : LBRACE defaultNamedParameter (',' defaultNamedParameter)* ','? RBRACE + ; + +normalFormalParameter + : metadata normalFormalParameterNoMetadata + ; + +normalFormalParameterNoMetadata + : functionFormalParameter + | fieldFormalParameter + | simpleFormalParameter + | superFormalParameter + ; + +// NB: It is an anomaly that a functionFormalParameter cannot be FINAL. +functionFormalParameter + : COVARIANT? type? identifier formalParameterPart '?'? + ; + +simpleFormalParameter + : declaredIdentifier + | COVARIANT? identifier + ; + +// NB: It is an anomaly that VAR can be a return type (`var this.x()`). +fieldFormalParameter + : finalConstVarOrType? THIS '.' identifier (formalParameterPart '?'?)? + ; + +superFormalParameter + : type? SUPER '.' identifier (formalParameterPart '?'?)? + ; + +defaultFormalParameter + : normalFormalParameter ('=' expression)? + ; + +defaultNamedParameter + : metadata REQUIRED? normalFormalParameterNoMetadata ('=' expression)? + ; + +typeWithParameters + : typeIdentifier typeParameters? + ; + +classDeclaration + : AUGMENT? (classModifiers | mixinClassModifiers) + CLASS typeWithParameters superclass? interfaces? + LBRACE (metadata classMemberDeclaration)* RBRACE + | classModifiers MIXIN? CLASS mixinApplicationClass + ; + +classModifiers + : SEALED + | ABSTRACT? (BASE | INTERFACE | FINAL)? + ; + +mixinClassModifiers + : ABSTRACT? BASE? MIXIN + ; + +superclass + : EXTENDS typeNotVoidNotFunction mixins? + | mixins + ; + +mixins + : WITH typeNotVoidNotFunctionList + ; + +interfaces + : IMPLEMENTS typeNotVoidNotFunctionList + ; + +classMemberDeclaration + : AUGMENT? methodSignature functionBody + | AUGMENT? declaration ';' + ; + +mixinApplicationClass + : typeWithParameters '=' mixinApplication ';' + ; + +mixinDeclaration + : AUGMENT? BASE? MIXIN typeWithParameters + (ON typeNotVoidNotFunctionList)? interfaces? + LBRACE (metadata mixinMemberDeclaration)* RBRACE + ; + +// TODO: We might want to make this more strict. +mixinMemberDeclaration + : classMemberDeclaration + ; + +extensionTypeDeclaration + : EXTENSION TYPE CONST? typeWithParameters + representationDeclaration interfaces? + LBRACE (metadata extensionTypeMemberDeclaration)* RBRACE + | AUGMENT EXTENSION TYPE typeWithParameters interfaces? + LBRACE (metadata extensionTypeMemberDeclaration)* RBRACE + ; + +representationDeclaration + : ('.' identifierOrNew)? '(' metadata typedIdentifier ')' + ; + + +// TODO: We might want to make this more strict. +extensionTypeMemberDeclaration + : classMemberDeclaration + ; + +extensionDeclaration + : EXTENSION typeIdentifierNotType? typeParameters? ON type extensionBody + | AUGMENT EXTENSION typeIdentifierNotType typeParameters? extensionBody + ; + +extensionBody + : LBRACE (metadata extensionMemberDeclaration)* RBRACE + ; + +// TODO: We might want to make this more strict. +extensionMemberDeclaration + : classMemberDeclaration + ; + +methodSignature + : constructorSignature initializers + | factoryConstructorSignature + | STATIC? functionSignature + | STATIC? getterSignature + | STATIC? setterSignature + | operatorSignature + | constructorSignature + ; + +declaration + : EXTERNAL? factoryConstructorSignature + | EXTERNAL constantConstructorSignature + | EXTERNAL constructorSignature + | EXTERNAL? STATIC? getterSignature + | EXTERNAL? STATIC? setterSignature + | EXTERNAL? STATIC? functionSignature + | EXTERNAL (STATIC? finalVarOrType | COVARIANT varOrType) identifierList + | EXTERNAL? operatorSignature + | ABSTRACT (finalVarOrType | COVARIANT varOrType) identifierList + | STATIC (FINAL | CONST) type? initializedIdentifierList + | STATIC LATE FINAL type? initializedIdentifierList + | STATIC LATE? varOrType initializedIdentifierList + | COVARIANT LATE FINAL type? identifierList + | COVARIANT LATE? varOrType initializedIdentifierList + | LATE? (FINAL type? | varOrType) initializedIdentifierList + | redirectingFactoryConstructorSignature + | constantConstructorSignature (redirection | initializers)? + | constructorSignature (redirection | initializers)? + ; + +operatorSignature + : type? OPERATOR operator formalParameterList + ; + +operator + : '~' + | binaryOperator + | '[' ']' + | '[' ']' '=' + ; + +binaryOperator + : multiplicativeOperator + | additiveOperator + | shiftOperator + | relationalOperator + | '==' + | bitwiseOperator + ; + +getterSignature + : type? GET identifier + ; + +setterSignature + : type? SET identifier formalParameterList + ; + +constructorSignature + : constructorName formalParameterList + ; + +constructorName + : typeIdentifier ('.' identifierOrNew)? + ; + +// TODO: Add this in the language specification, use it in grammar rules. +identifierOrNew + : identifier + | NEW + ; + +redirection + : ':' THIS ('.' identifierOrNew)? arguments + ; + +initializers + : ':' initializerListEntry (',' initializerListEntry)* + ; + +initializerListEntry + : SUPER arguments + | SUPER '.' identifierOrNew arguments + | fieldInitializer + | assertion + ; + +fieldInitializer + : (THIS '.')? identifier '=' initializerExpression + ; + +initializerExpression + : throwExpression + | assignableExpression assignmentOperator expression + | conditionalExpression + | cascade + ; + +factoryConstructorSignature + : CONST? FACTORY constructorName formalParameterList + ; + +redirectingFactoryConstructorSignature + : CONST? FACTORY constructorName formalParameterList '=' + constructorDesignation + ; + +constantConstructorSignature + : CONST constructorName formalParameterList + ; + +mixinApplication + : typeNotVoidNotFunction mixins interfaces? + ; + +enumType + : AUGMENT? ENUM typeWithParameters mixins? interfaces? LBRACE + enumEntry (',' enumEntry)* (',')? + (';' (metadata classMemberDeclaration)*)? + RBRACE + ; + +enumEntry + : metadata AUGMENT? identifier argumentPart? + | metadata AUGMENT? identifier typeArguments? + '.' identifierOrNew arguments + ; + +typeParameter + : metadata typeIdentifier (EXTENDS typeNotVoid)? + ; + +typeParameters + : '<' typeParameter (',' typeParameter)* '>' + ; + +metadata + : ('@' metadatum)* + ; + +metadatum + : constructorDesignation arguments + | identifier + | qualifiedName + ; + +expression + : patternAssignment + | functionExpression + | throwExpression + | assignableExpression assignmentOperator expression + | conditionalExpression + | cascade + ; + +expressionWithoutCascade + : functionExpressionWithoutCascade + | throwExpressionWithoutCascade + | assignableExpression assignmentOperator expressionWithoutCascade + | conditionalExpression + ; + +expressionList + : expression (',' expression)* + ; + +primary + : thisExpression + | SUPER unconditionalAssignableSelector + | SUPER argumentPart + | functionPrimary + | literal + | identifier + | newExpression + | constObjectExpression + | constructorInvocation + | '(' expression ')' + | constructorTearoff + | switchExpression + ; + +constructorInvocation + : typeName typeArguments '.' NEW arguments + | typeName '.' NEW arguments + ; + +literal + : nullLiteral + | booleanLiteral + | numericLiteral + | stringLiteral + | symbolLiteral + | setOrMapLiteral + | listLiteral + | recordLiteral + ; + +nullLiteral + : NULL + ; + +numericLiteral + : NUMBER + | HEX_NUMBER + ; + +booleanLiteral + : TRUE + | FALSE + ; + +stringLiteral + : (multiLineString | singleLineString)+ + ; + +setOrMapLiteral + : CONST? typeArguments? LBRACE elements? RBRACE + ; + +listLiteral + : CONST? typeArguments? '[' elements? ']' + ; + +recordLiteral + : CONST? recordLiteralNoConst + ; + +recordLiteralNoConst + : '(' ')' + | '(' expression ',' ')' + | '(' label expression ','? ')' + | '(' recordField (',' recordField)+ ','? ')' + ; + +recordField + : label? expression + ; + +elements + : element (',' element)* ','? + ; + +element + : nullAwareExpressionElement + | nullAwareMapElement + | expressionElement + | mapElement + | spreadElement + | ifElement + | forElement + ; + +nullAwareExpressionElement + : '?' expression + ; + +nullAwareMapElement + : '?' expression ':' '?'? expression + | expression ':' '?' expression + ; + +expressionElement + : expression + ; + +mapElement + : expression ':' expression + ; + +spreadElement + : ('...' | '...?') expression + ; + +ifElement + : ifCondition element (ELSE element)? + ; + +forElement + : AWAIT? FOR '(' forLoopParts ')' element + ; + +constructorTearoff + : typeName typeArguments? '.' NEW + ; + +switchExpression + : SWITCH '(' expression ')' + LBRACE (switchExpressionCase (',' switchExpressionCase)* ','?)? RBRACE + ; + +switchExpressionCase + : guardedPattern '=>' expression + ; + +throwExpression + : THROW expression + ; + +throwExpressionWithoutCascade + : THROW expressionWithoutCascade + ; + +functionExpression + : formalParameterPart functionExpressionBody + ; + +functionExpressionBody + : '=>' { startNonAsyncFunction(); } expression { endFunction(); } + | ASYNC '=>' { startAsyncFunction(); } expression { endFunction(); } + ; + +functionExpressionWithoutCascade + : formalParameterPart functionExpressionWithoutCascadeBody + ; + +functionExpressionWithoutCascadeBody + : '=>' { startNonAsyncFunction(); } + expressionWithoutCascade { endFunction(); } + | ASYNC '=>' { startAsyncFunction(); } + expressionWithoutCascade { endFunction(); } + ; + +functionPrimary + : formalParameterPart functionPrimaryBody + ; + +functionPrimaryBody + : { startNonAsyncFunction(); } block { endFunction(); } + | (ASYNC | ASYNC '*' | SYNC '*') + { startAsyncFunction(); } block { endFunction(); } + ; + +thisExpression + : THIS + ; + +newExpression + : NEW constructorDesignation arguments + ; + +constObjectExpression + : CONST constructorDesignation arguments + ; + +arguments + : '(' (argumentList ','?)? ')' + ; + +argumentList + : argument (',' argument)* + ; + +argument + : label? expression + ; + +cascade + : cascade '..' cascadeSection + | conditionalExpression ('?..' | '..') cascadeSection + ; + +cascadeSection + : cascadeSelector cascadeSectionTail + ; + +cascadeSelector + : '[' expression ']' + | identifier + ; + +cascadeSectionTail + : cascadeAssignment + | selector* (assignableSelector cascadeAssignment)? + ; + +cascadeAssignment + : assignmentOperator expressionWithoutCascade + ; + +assignmentOperator + : '=' + | compoundAssignmentOperator + ; + +compoundAssignmentOperator + : '*=' + | '/=' + | '~/=' + | '%=' + | '+=' + | '-=' + | '<<=' + | '>' '>' '>' '=' + | '>' '>' '=' + | '&=' + | '^=' + | '|=' + | '??=' + ; + +conditionalExpression + : ifNullExpression + ('?' expressionWithoutCascade ':' expressionWithoutCascade)? + ; + +ifNullExpression + : logicalOrExpression ('??' logicalOrExpression)* + ; + +logicalOrExpression + : logicalAndExpression ('||' logicalAndExpression)* + ; + +logicalAndExpression + : equalityExpression ('&&' equalityExpression)* + ; + +equalityExpression + : relationalExpression (equalityOperator relationalExpression)? + | SUPER equalityOperator relationalExpression + ; + +equalityOperator + : '==' + | '!=' + ; + +relationalExpression + : bitwiseOrExpression + (typeTest | typeCast | relationalOperator bitwiseOrExpression)? + | SUPER relationalOperator bitwiseOrExpression + ; + +relationalOperator + : '>' '=' + | '>' + | '<=' + | '<' + ; + +bitwiseOrExpression + : bitwiseXorExpression ('|' bitwiseXorExpression)* + | SUPER ('|' bitwiseXorExpression)+ + ; + +bitwiseXorExpression + : bitwiseAndExpression ('^' bitwiseAndExpression)* + | SUPER ('^' bitwiseAndExpression)+ + ; + +bitwiseAndExpression + : shiftExpression ('&' shiftExpression)* + | SUPER ('&' shiftExpression)+ + ; + +bitwiseOperator + : '&' + | '^' + | '|' + ; + +shiftExpression + : additiveExpression (shiftOperator additiveExpression)* + | SUPER (shiftOperator additiveExpression)+ + ; + +shiftOperator + : '<<' + | '>' '>' '>' + | '>' '>' + ; + +additiveExpression + : multiplicativeExpression (additiveOperator multiplicativeExpression)* + | SUPER (additiveOperator multiplicativeExpression)+ + ; + +additiveOperator + : '+' + | '-' + ; + +multiplicativeExpression + : unaryExpression (multiplicativeOperator unaryExpression)* + | SUPER (multiplicativeOperator unaryExpression)+ + ; + +multiplicativeOperator + : '*' + | '/' + | '%' + | '~/' + ; + +unaryExpression + : prefixOperator unaryExpression + | awaitExpression + | postfixExpression + | (minusOperator | tildeOperator) SUPER + | incrementOperator assignableExpression + ; + +prefixOperator + : minusOperator + | negationOperator + | tildeOperator + ; + +minusOperator + : '-' + ; + +negationOperator + : '!' + ; + +tildeOperator + : '~' + ; + +awaitExpression + : AWAIT unaryExpression + ; + +postfixExpression + : assignableExpression postfixOperator + | primary selector* + ; + +postfixOperator + : incrementOperator + ; + +selector + : '!' + | assignableSelector + | argumentPart + | typeArguments + ; + +argumentPart + : typeArguments? arguments + ; + +incrementOperator + : '++' + | '--' + ; + +assignableExpression + : SUPER unconditionalAssignableSelector + | primary assignableSelectorPart + | identifier + ; + +assignableSelectorPart + : selector* assignableSelector + ; + +unconditionalAssignableSelector + : '[' expression ']' + | '.' identifier + ; + +assignableSelector + : unconditionalAssignableSelector + | '?.' identifier + | '?' '[' expression ']' + ; + +identifier + : IDENTIFIER + | builtInIdentifier + | otherIdentifier + | { asyncEtcPredicate(getCurrentToken().getType()) }? (AWAIT|YIELD) + ; + +qualifiedName + : typeIdentifier '.' identifierOrNew + | typeIdentifier '.' typeIdentifier '.' identifierOrNew + ; + +typeIdentifierNotType + : IDENTIFIER + | DYNAMIC // Built-in identifier that can be used as a type. + | otherIdentifierNotType // Occur in grammar rules, are not built-in. + | { asyncEtcPredicate(getCurrentToken().getType()) }? (AWAIT|YIELD) + ; + +typeIdentifier + : typeIdentifierNotType + | TYPE + ; + +typeTest + : isOperator typeNotVoid + ; + +isOperator + : IS '!'? + ; + +typeCast + : asOperator typeNotVoid + ; + +asOperator + : AS + ; + +pattern + : logicalOrPattern + ; + +logicalOrPattern + : logicalAndPattern ('||' logicalAndPattern)* + ; + +logicalAndPattern + : relationalPattern ('&&' relationalPattern)* + ; + +relationalPattern + : (equalityOperator | relationalOperator) bitwiseOrExpression + | unaryPattern + ; + +unaryPattern + : castPattern + | nullCheckPattern + | nullAssertPattern + | primaryPattern + ; + +primaryPattern + : constantPattern + | variablePattern + | parenthesizedPattern + | listPattern + | mapPattern + | recordPattern + | objectPattern + ; + +castPattern + : primaryPattern AS type + ; + +nullCheckPattern + : primaryPattern '?' + ; + +nullAssertPattern + : primaryPattern '!' + ; + +constantPattern + : booleanLiteral + | nullLiteral + | '-'? numericLiteral + | stringLiteral + | symbolLiteral + | identifier + | qualifiedName + | constObjectExpression + | CONST typeArguments? '[' elements? ']' + | CONST typeArguments? LBRACE elements? RBRACE + | CONST '(' expression ')' + ; + +variablePattern + : (VAR | FINAL | FINAL? type)? identifier + ; + +parenthesizedPattern + : '(' pattern ')' + ; + +listPattern + : typeArguments? '[' listPatternElements? ']' + ; + +listPatternElements + : listPatternElement (',' listPatternElement)* ','? + ; + +listPatternElement + : pattern + | restPattern + ; + +restPattern + : '...' pattern? + ; + +mapPattern + : typeArguments? LBRACE mapPatternEntries? RBRACE + ; + +mapPatternEntries + : mapPatternEntry (',' mapPatternEntry)* ','? + ; + +mapPatternEntry + : expression ':' pattern + | '...' + ; + +recordPattern + : '(' patternFields? ')' + ; + +patternFields + : patternField (',' patternField)* ','? + ; + +patternField + : (identifier? ':')? pattern + ; + +objectPattern + : (typeName typeArguments? | typeNamedFunction) '(' patternFields? ')' + ; + +patternVariableDeclaration + : outerPatternDeclarationPrefix '=' expression + ; + +outerPattern + : parenthesizedPattern + | listPattern + | mapPattern + | recordPattern + | objectPattern + ; + +outerPatternDeclarationPrefix + : (FINAL | VAR) outerPattern + ; + +patternAssignment + : outerPattern '=' expression + ; + +statements + : statement* + ; + +statement + : label* nonLabelledStatement + ; + +// Exception in the language specification: An expressionStatement cannot +// start with LBRACE. We force anything that starts with LBRACE to be a block, +// which will prevent an expressionStatement from starting with LBRACE, and +// which will not interfere with the recognition of any other case. If we +// add another statement which can start with LBRACE we must adjust this +// check. +nonLabelledStatement + : block + | localVariableDeclaration + | forStatement + | whileStatement + | doStatement + | switchStatement + | ifStatement + | rethrowStatement + | tryStatement + | breakStatement + | continueStatement + | returnStatement + | localFunctionDeclaration + | assertStatement + | yieldStatement + | yieldEachStatement + | expressionStatement + ; + +expressionStatement + : expression? ';' + ; + +localVariableDeclaration + : metadata initializedVariableDeclaration ';' + | metadata patternVariableDeclaration ';' + ; + +initializedVariableDeclaration + : declaredIdentifier ('=' expression)? (',' initializedIdentifier)* + ; + +localFunctionDeclaration + : metadata functionSignature functionBody + ; + +ifStatement + : ifCondition statement (ELSE statement)? + ; + +ifCondition + : IF '(' expression (CASE guardedPattern)? ')' + ; + +forStatement + : AWAIT? FOR '(' forLoopParts ')' statement + ; + +forLoopParts + : forInLoopPrefix IN expression + | forInitializerStatement expression? ';' expressionList? + ; + +forInLoopPrefix + : metadata declaredIdentifier + | metadata outerPatternDeclarationPrefix + | identifier + ; + +// The localVariableDeclaration cannot be CONST, but that can +// be enforced in a later phase, and the grammar allows it. +forInitializerStatement + : localVariableDeclaration + | expression? ';' + ; + +whileStatement + : WHILE '(' expression ')' statement + ; + +doStatement + : DO statement WHILE '(' expression ')' ';' + ; + +switchStatement + : SWITCH '(' expression ')' + LBRACE switchStatementCase* switchStatementDefault? RBRACE + ; + +switchStatementCase + : label* CASE guardedPattern ':' statements + ; + +guardedPattern + : pattern (WHEN expression)? + ; + +switchStatementDefault + : label* DEFAULT ':' statements + ; + +rethrowStatement + : RETHROW ';' + ; + +tryStatement + : TRY block (onPart+ finallyPart? | finallyPart) + ; + +onPart + : catchPart block + | ON typeNotVoid catchPart? block + ; + +catchPart + : CATCH '(' identifier (',' identifier)? ')' + ; + +finallyPart + : FINALLY block + ; + +returnStatement + : RETURN expression? ';' + ; + +label + : identifier ':' + ; + +breakStatement + : BREAK identifier? ';' + ; + +continueStatement + : CONTINUE identifier? ';' + ; + +yieldStatement + : YIELD expression ';' + ; + +yieldEachStatement + : YIELD '*' expression ';' + ; + +assertStatement + : assertion ';' + ; + +assertion + : ASSERT '(' expression (',' expression)? ','? ')' + ; + +libraryName + : metadata libraryNameBody ';' + ; + +libraryNameBody + : LIBRARY dottedIdentifierList? + | AUGMENT LIBRARY uri + ; + +dottedIdentifierList + : identifier ('.' identifier)* + ; + +importOrExport + : libraryImport + | libraryAugmentImport + | libraryExport + ; + +libraryImport + : metadata importSpecification + ; + +libraryAugmentImport + : metadata IMPORT AUGMENT uri ';' + ; + +importSpecification + : IMPORT configurableUri (DEFERRED? AS typeIdentifier)? combinator* ';' + ; + +combinator + : SHOW identifierList + | HIDE identifierList + ; + +identifierList + : identifier (',' identifier)* + ; + +libraryExport + : metadata EXPORT configurableUri combinator* ';' + ; + +partDirective + : metadata PART configurableUri ';' + ; + +partHeader + : metadata PART OF uri ';' + ; + +partDeclaration + : FEFF? partHeader + importOrExport* + partDirective* + (metadata topLevelDefinition)* + EOF + ; + +uri + : stringLiteral + ; + +configurableUri + : uri configurationUri* + ; + +configurationUri + : IF '(' uriTest ')' uri + ; + +uriTest + : dottedIdentifierList ('==' stringLiteral)? + ; + +type + : functionType '?'? + | typeNotFunction + ; + +typeNotVoid + : functionType '?'? + | recordType '?'? + | typeNotVoidNotFunction '?'? + ; + +typeNotFunction + : typeNotVoidNotFunction '?'? + | recordType '?'? + | VOID + ; + +typeNamedFunction + : (typeIdentifier '.')? FUNCTION + ; + +typeNotVoidNotFunction + : typeName typeArguments? + | typeNamedFunction + ; + +typeName + : typeIdentifier ('.' typeIdentifier)? + ; + +typeArguments + : '<' typeList '>' + ; + +typeList + : type (',' type)* + ; + +recordType + : '(' ')' + | '(' recordTypeFields ',' recordTypeNamedFields ')' + | '(' recordTypeFields ','? ')' + | '(' recordTypeNamedFields ')' + ; + +recordTypeFields + : recordTypeField (',' recordTypeField)* + ; + +recordTypeField + : metadata type identifier? + ; + +recordTypeNamedFields + : LBRACE recordTypeNamedField (',' recordTypeNamedField)* ','? RBRACE + ; + +recordTypeNamedField + : metadata typedIdentifier + ; + +typeNotVoidNotFunctionList + : typeNotVoidNotFunction (',' typeNotVoidNotFunction)* + ; + +typeAlias + : AUGMENT? TYPEDEF typeWithParameters '=' type ';' + | AUGMENT? TYPEDEF functionTypeAlias + ; + +functionTypeAlias + : functionPrefix formalParameterPart ';' + ; + +functionPrefix + : type identifier + | identifier + ; + +functionTypeTail + : FUNCTION typeParameters? parameterTypeList + ; + +functionTypeTails + : (functionTypeTail '?'?)* functionTypeTail + ; + +functionType + : typeNotFunction? functionTypeTails + ; + +parameterTypeList + : '(' ')' + | '(' normalParameterTypes ',' optionalParameterTypes ')' + | '(' normalParameterTypes ','? ')' + | '(' optionalParameterTypes ')' + ; + +normalParameterTypes + : normalParameterType (',' normalParameterType)* + ; + +normalParameterType + : metadata typedIdentifier + | metadata type + ; + +optionalParameterTypes + : optionalPositionalParameterTypes + | namedParameterTypes + ; + +optionalPositionalParameterTypes + : '[' normalParameterTypes ','? ']' + ; + +namedParameterTypes + : LBRACE namedParameterType (',' namedParameterType)* ','? RBRACE + ; + +namedParameterType + : metadata REQUIRED? typedIdentifier + ; + +typedIdentifier + : type identifier + ; + +constructorDesignation + : typeIdentifier + | qualifiedName + | typeName typeArguments ('.' identifierOrNew)? + ; + +symbolLiteral + : '#' (operator | (identifier ('.' identifier)*) | VOID) + ; + +singleLineString + : RAW_SINGLE_LINE_STRING + | SINGLE_LINE_STRING_SQ_BEGIN_END + | SINGLE_LINE_STRING_SQ_BEGIN_MID expression + (SINGLE_LINE_STRING_SQ_MID_MID expression)* + SINGLE_LINE_STRING_SQ_MID_END + | SINGLE_LINE_STRING_DQ_BEGIN_END + | SINGLE_LINE_STRING_DQ_BEGIN_MID expression + (SINGLE_LINE_STRING_DQ_MID_MID expression)* + SINGLE_LINE_STRING_DQ_MID_END + ; + +multiLineString + : RAW_MULTI_LINE_STRING + | MULTI_LINE_STRING_SQ_BEGIN_END + | MULTI_LINE_STRING_SQ_BEGIN_MID expression + (MULTI_LINE_STRING_SQ_MID_MID expression)* + MULTI_LINE_STRING_SQ_MID_END + | MULTI_LINE_STRING_DQ_BEGIN_END + | MULTI_LINE_STRING_DQ_BEGIN_MID expression + (MULTI_LINE_STRING_DQ_MID_MID expression)* + MULTI_LINE_STRING_DQ_MID_END + ; + +reservedWord + : ASSERT + | BREAK + | CASE + | CATCH + | CLASS + | CONST + | CONTINUE + | DEFAULT + | DO + | ELSE + | ENUM + | EXTENDS + | FALSE + | FINAL + | FINALLY + | FOR + | IF + | IN + | IS + | NEW + | NULL + | RETHROW + | RETURN + | SUPER + | SWITCH + | THIS + | THROW + | TRUE + | TRY + | VAR + | VOID + | WHILE + | WITH + ; + +builtInIdentifier + : ABSTRACT + | AS + | AUGMENT + | COVARIANT + | DEFERRED + | DYNAMIC + | EXPORT + | EXTENSION + | EXTERNAL + | FACTORY + | FUNCTION + | GET + | IMPLEMENTS + | IMPORT + | INTERFACE + | LATE + | LIBRARY + | OPERATOR + | MIXIN + | PART + | REQUIRED + | SET + | STATIC + | TYPEDEF + ; + +otherIdentifierNotType + : ASYNC + | BASE + | HIDE + | OF + | ON + | SEALED + | SHOW + | SYNC + | WHEN + ; + +otherIdentifier + : otherIdentifierNotType + | TYPE + ; + +// ---------------------------------------- Lexer rules. + +fragment +LETTER + : 'a' .. 'z' + | 'A' .. 'Z' + ; + +fragment +DIGIT + : '0' .. '9' + ; + +fragment +EXPONENT + : ('e' | 'E') ('+' | '-')? DIGITS + ; + +fragment +DIGITS + : DIGIT ('_'* DIGIT)* + ; + +fragment +HEX_DIGIT + : ('a' | 'b' | 'c' | 'd' | 'e' | 'f') + | ('A' | 'B' | 'C' | 'D' | 'E' | 'F') + | DIGIT + ; + +fragment +HEX_DIGITS + : HEX_DIGIT ('_'* HEX_DIGIT)* + ; + +// Reserved words (if updated, update `reservedWord` as well). + +ASSERT + : 'assert' + ; + +BREAK + : 'break' + ; + +CASE + : 'case' + ; + +CATCH + : 'catch' + ; + +CLASS + : 'class' + ; + +CONST + : 'const' + ; + +CONTINUE + : 'continue' + ; + +DEFAULT + : 'default' + ; + +DO + : 'do' + ; + +ELSE + : 'else' + ; + +ENUM + : 'enum' + ; + +EXTENDS + : 'extends' + ; + +FALSE + : 'false' + ; + +FINAL + : 'final' + ; + +FINALLY + : 'finally' + ; + +FOR + : 'for' + ; + +IF + : 'if' + ; + +IN + : 'in' + ; + +IS + : 'is' + ; + +NEW + : 'new' + ; + +NULL + : 'null' + ; + +RETHROW + : 'rethrow' + ; + +RETURN + : 'return' + ; + +SUPER + : 'super' + ; + +SWITCH + : 'switch' + ; + +THIS + : 'this' + ; + +THROW + : 'throw' + ; + +TRUE + : 'true' + ; + +TRY + : 'try' + ; + +VAR + : 'var' + ; + +VOID + : 'void' + ; + +WHILE + : 'while' + ; + +WITH + : 'with' + ; + +// Built-in identifiers (if updated, update `builtInIdentifier` as well). + +ABSTRACT + : 'abstract' + ; + +AS + : 'as' + ; + +AUGMENT + : 'augment' + ; + +COVARIANT + : 'covariant' + ; + +DEFERRED + : 'deferred' + ; + +DYNAMIC + : 'dynamic' + ; + +EXPORT + : 'export' + ; + +EXTENSION + : 'extension' + ; + +EXTERNAL + : 'external' + ; + +FACTORY + : 'factory' + ; + +FUNCTION + : 'Function' + ; + +GET + : 'get' + ; + +IMPLEMENTS + : 'implements' + ; + +IMPORT + : 'import' + ; + +INTERFACE + : 'interface' + ; + +LATE + : 'late' + ; + +LIBRARY + : 'library' + ; + +OPERATOR + : 'operator' + ; + +MIXIN + : 'mixin' + ; + +PART + : 'part' + ; + +REQUIRED + : 'required' + ; + +SET + : 'set' + ; + +STATIC + : 'static' + ; + +TYPEDEF + : 'typedef' + ; + +// "Contextual keywords". + +AWAIT + : 'await' + ; + +YIELD + : 'yield' + ; + +// Other words used in the grammar (if updated, update `otherIdentifier`, too). + +ASYNC + : 'async' + ; + +BASE + : 'base' + ; + +HIDE + : 'hide' + ; + +OF + : 'of' + ; + +ON + : 'on' + ; + +SEALED + : 'sealed' + ; + +SHOW + : 'show' + ; + +SYNC + : 'sync' + ; + +TYPE + : 'type' + ; + +WHEN + : 'when' + ; + +// Lexical tokens that are not words. + +NUMBER + : DIGITS ('.' DIGITS)? EXPONENT? + | '.' DIGITS EXPONENT? + ; + +HEX_NUMBER + : '0x' HEX_DIGITS + | '0X' HEX_DIGITS + ; + +RAW_SINGLE_LINE_STRING + : 'r' '\'' (~('\'' | '\r' | '\n'))* '\'' + | 'r' '"' (~('"' | '\r' | '\n'))* '"' + ; + +RAW_MULTI_LINE_STRING + : 'r' '"""' (.)*? '"""' + | 'r' '\'\'\'' (.)*? '\'\'\'' + ; + +fragment +SIMPLE_STRING_INTERPOLATION + : '$' IDENTIFIER_NO_DOLLAR + ; + +fragment +ESCAPE_SEQUENCE + : '\\n' + | '\\r' + | '\\b' + | '\\t' + | '\\v' + | '\\x' HEX_DIGIT HEX_DIGIT + | '\\u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT + | '\\u{' HEX_DIGIT_SEQUENCE '}' + ; + +fragment +HEX_DIGIT_SEQUENCE + : HEX_DIGIT HEX_DIGIT? HEX_DIGIT? + HEX_DIGIT? HEX_DIGIT? HEX_DIGIT? + ; + +fragment +STRING_CONTENT_COMMON + : ~('\\' | '\'' | '"' | '$' | '\r' | '\n') + | ESCAPE_SEQUENCE + | '\\' ~('n' | 'r' | 'b' | 't' | 'v' | 'x' | 'u' | '\r' | '\n') + | SIMPLE_STRING_INTERPOLATION + ; + +fragment +STRING_CONTENT_SQ + : STRING_CONTENT_COMMON + | '"' + ; + +SINGLE_LINE_STRING_SQ_BEGIN_END + : '\'' STRING_CONTENT_SQ* '\'' + ; + +SINGLE_LINE_STRING_SQ_BEGIN_MID + : '\'' STRING_CONTENT_SQ* '${' { enterBraceSingleQuote(); } + ; + +SINGLE_LINE_STRING_SQ_MID_MID + : { currentBraceLevel(BRACE_SINGLE) }? + { exitBrace(); } '}' STRING_CONTENT_SQ* '${' + { enterBraceSingleQuote(); } + ; + +SINGLE_LINE_STRING_SQ_MID_END + : { currentBraceLevel(BRACE_SINGLE) }? + { exitBrace(); } '}' STRING_CONTENT_SQ* '\'' + ; + +fragment +STRING_CONTENT_DQ + : STRING_CONTENT_COMMON + | '\'' + ; + +SINGLE_LINE_STRING_DQ_BEGIN_END + : '"' STRING_CONTENT_DQ* '"' + ; + +SINGLE_LINE_STRING_DQ_BEGIN_MID + : '"' STRING_CONTENT_DQ* '${' { enterBraceDoubleQuote(); } + ; + +SINGLE_LINE_STRING_DQ_MID_MID + : { currentBraceLevel(BRACE_DOUBLE) }? + { exitBrace(); } '}' STRING_CONTENT_DQ* '${' + { enterBraceDoubleQuote(); } + ; + +SINGLE_LINE_STRING_DQ_MID_END + : { currentBraceLevel(BRACE_DOUBLE) }? + { exitBrace(); } '}' STRING_CONTENT_DQ* '"' + ; + +fragment +QUOTES_SQ + : + | '\'' + | '\'\'' + ; + +// Read string contents, which may be almost anything, but stop when seeing +// '\'\'\'' and when seeing '${'. We do this by allowing all other +// possibilities including escapes, simple interpolation, and fewer than +// three '\''. +fragment +STRING_CONTENT_TSQ + : QUOTES_SQ + (STRING_CONTENT_COMMON | '"' | '\r' | '\n' | '\\\r' | '\\\n') + ; + +MULTI_LINE_STRING_SQ_BEGIN_END + : '\'\'\'' STRING_CONTENT_TSQ* '\'\'\'' + ; + +MULTI_LINE_STRING_SQ_BEGIN_MID + : '\'\'\'' STRING_CONTENT_TSQ* QUOTES_SQ '${' + { enterBraceThreeSingleQuotes(); } + ; + +MULTI_LINE_STRING_SQ_MID_MID + : { currentBraceLevel(BRACE_THREE_SINGLE) }? + { exitBrace(); } '}' STRING_CONTENT_TSQ* QUOTES_SQ '${' + { enterBraceThreeSingleQuotes(); } + ; + +MULTI_LINE_STRING_SQ_MID_END + : { currentBraceLevel(BRACE_THREE_SINGLE) }? + { exitBrace(); } '}' STRING_CONTENT_TSQ* '\'\'\'' + ; + +fragment +QUOTES_DQ + : + | '"' + | '""' + ; + +// Read string contents, which may be almost anything, but stop when seeing +// '"""' and when seeing '${'. We do this by allowing all other possibilities +// including escapes, simple interpolation, and fewer-than-three '"'. +fragment +STRING_CONTENT_TDQ + : QUOTES_DQ + (STRING_CONTENT_COMMON | '\'' | '\r' | '\n' | '\\\r' | '\\\n') + ; + +MULTI_LINE_STRING_DQ_BEGIN_END + : '"""' STRING_CONTENT_TDQ* '"""' + ; + +MULTI_LINE_STRING_DQ_BEGIN_MID + : '"""' STRING_CONTENT_TDQ* QUOTES_DQ '${' + { enterBraceThreeDoubleQuotes(); } + ; + +MULTI_LINE_STRING_DQ_MID_MID + : { currentBraceLevel(BRACE_THREE_DOUBLE) }? + { exitBrace(); } '}' STRING_CONTENT_TDQ* QUOTES_DQ '${' + { enterBraceThreeDoubleQuotes(); } + ; + +MULTI_LINE_STRING_DQ_MID_END + : { currentBraceLevel(BRACE_THREE_DOUBLE) }? + { exitBrace(); } '}' STRING_CONTENT_TDQ* '"""' + ; + +LBRACE + : '{' { enterBrace(); } + ; + +RBRACE + : { currentBraceLevel(BRACE_NORMAL) }? { exitBrace(); } '}' + ; + +fragment +IDENTIFIER_START_NO_DOLLAR + : LETTER + | '_' + ; + +fragment +IDENTIFIER_PART_NO_DOLLAR + : IDENTIFIER_START_NO_DOLLAR + | DIGIT + ; + +fragment +IDENTIFIER_NO_DOLLAR + : IDENTIFIER_START_NO_DOLLAR IDENTIFIER_PART_NO_DOLLAR* + ; + +fragment +IDENTIFIER_START + : IDENTIFIER_START_NO_DOLLAR + | '$' + ; + +fragment +IDENTIFIER_PART + : IDENTIFIER_START + | DIGIT + ; + +SCRIPT_TAG + : '#!' (~('\r' | '\n'))* NEWLINE + ; + +IDENTIFIER + : IDENTIFIER_START IDENTIFIER_PART* + ; + +SINGLE_LINE_COMMENT + : '//' (~('\r' | '\n'))* NEWLINE? + { skip(); } + ; + +MULTI_LINE_COMMENT + : '/*' (MULTI_LINE_COMMENT | .)*? '*/' + { skip(); } + ; + +fragment +NEWLINE + : ('\r' | '\n' | '\r\n') + ; + +FEFF + : '\uFEFF' + ; + +WS + : (' ' | '\t' | '\r' | '\n')+ + { skip(); } + ; diff --git a/pkg/front_end/tool/fuzz/analyzer_helper.dart b/pkg/front_end/tool/fuzz/analyzer_helper.dart new file mode 100644 index 000000000000..bdd4419b060d --- /dev/null +++ b/pkg/front_end/tool/fuzz/analyzer_helper.dart @@ -0,0 +1,489 @@ +// Copyright (c) 2025, 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:async"; +import "dart:convert"; +import "dart:io"; +import "dart:typed_data"; + +Future<(Object, StackTrace)?> compileWithAnalyzer(AnalyzerHelper analyzerHelper, + String program, Uri fileUri, int version) async { + try { + ZoneSpecification specification = + new ZoneSpecification(print: (_1, _2, _3, String line) { + // Swallow! + }); + await runZoned(() async { + Stopwatch stopwatch = new Stopwatch()..start(); + await analyzerHelper.changeFileContent(fileUri, program, version); + print("Analyzer compile took ${stopwatch.elapsedMilliseconds} ms."); + }, zoneSpecification: specification); + return null; + } catch (e, st) { + print("Analyzer crashed on input."); + return (e, st); + } +} + +Future main() async { + AnalyzerHelper analyzerHelper = new AnalyzerHelper(); + Directory root = Directory.systemTemp.createTempSync("fuzzer"); + File f = new File.fromUri(root.uri.resolve("testfile.dart")); + f.writeAsStringSync(""); + int version = 1; + + await analyzerHelper.setup(root.uri); + + await analyzerHelper.changeFileContent( + f.uri, "void main() { foo; }", version++); + + await analyzerHelper.changeFileContent(f.uri, "void main() { }", version++); + + await analyzerHelper.changeFileContent( + f.uri, "void main() { foo; } get foo => 42; ", version++); + + analyzerHelper.shutdown(); + root.deleteSync(recursive: true); +} + +class AnalyzerHelper { + static const bool debug = false; + static const int _printSizeCap = 300; + late final Process _p; + late final Timer _periodicTimer; + bool _processShutDown = false; + bool _processExited = false; + + Future setup(Uri rootUri) async { + _checkCorrectDart(); + Uri sdkUri = Uri.base + .resolveUri(Uri.file(Platform.resolvedExecutable)) + .resolve(".."); + if (!Directory.fromUri(rootUri).existsSync()) { + throw "Directory $rootUri doesn't exist. " + "Specify existing directory with --root="; + } + + if (!Directory.fromUri(sdkUri).existsSync()) { + throw "Directory $sdkUri doesn't exist. " + "Specify existing directory with --sdk="; + } + + if (!debug) { + _p = await Process.start(Platform.resolvedExecutable, [ + "language-server", + "--lsp", + ]); + } else { + _p = await Process.start(Platform.resolvedExecutable, [ + "--enable-vm-service", + "--profiler", + "pkg/analysis_server/bin/server.dart", + "--lsp", + "--port=9101" + ]); + } + // ignore: unawaited_futures + _p.exitCode.then((_) { + _processExited = true; + if (!_processShutDown) { + shutdown(); + // Make sure we don't wait forever. + _setAnalyzing(false); + } + }); + _p.stdout.listen(_listenToStdout); + _periodicTimer = + Timer.periodic(const Duration(seconds: 1), _checkLongRunningRequests); + await _initialize(sdkUri, rootUri, []); + } + + void shutdown() { + _processShutDown = true; + _periodicTimer.cancel(); + _p.kill(); + } + + Future _initialize( + Uri sdkUri, Uri rootUri, List additionalWorkspaceUris) async { + OutstandingRequest? request = await _send( + Messages.initMessage(pid, rootUri, additionalWorkspaceUris)); + await request?.completer.future; + _resetAnalyzingBool(); + await _send(Messages.initNotification); + await _send(Messages.initMore(sdkUri)); + await _waitForAnalysisToComplete(); + } + + Set _openFiles = {}; + + Future changeFileContent( + Uri file, String newContent, int fileVersion) async { + _resetAnalyzingBool(); + if (_openFiles.add(file)) { + await _send(Messages.openFile(file, fileVersion, newContent)); + } else { + await _send(Messages.changeFileContent(file, newContent, fileVersion)); + } + + await _waitForAnalysisToComplete(); + } + + void _checkCorrectDart() { + Uri exe = Uri.base.resolveUri(Uri.file(Platform.resolvedExecutable)); + Uri librariesDart = + exe.resolve("../lib/_internal/sdk_library_metadata/lib/libraries.dart"); + if (!File.fromUri(librariesDart).existsSync()) { + throw "Execute with a dart that has " + "'../lib/_internal/sdk_library_metadata/lib/libraries.dart' " + "available (e.g. out/ReleaseX64/dart-sdk/bin/dart)"; + } + } + + void _checkLongRunningRequests(timer) { + bool reportedSomething = false; + for (MapEntry waitingFor + in _outstandingRequestsWithId.entries) { + if (waitingFor.value.stopwatch.elapsed > const Duration(seconds: 1)) { + if (!reportedSomething) { + print("----"); + reportedSomething = true; + } + print("==> Has been waiting for ${waitingFor.key} for " + "${waitingFor.value.stopwatch.elapsed}"); + } + } + if (reportedSomething) { + print("----"); + } + } + + int? _headerContentLength; + + bool? _currentlyAnalyzing; + Completer _analyzingCompleter = Completer(); + + final _buffer = []; + + /// There's something weird about getting (several) id 3's that wasn't + /// requested... + int _largestIdSeen = 3; + + RegExp _newLineRegExp = RegExp("\r?\n"); + + Map _outstandingRequestsWithId = {}; + + bool _printedVmServiceStuff = false; + + int _verbosity = 0; + + void _setAnalyzing(bool b) { + _analyzingCompleter.complete(b); + _currentlyAnalyzing = b; + _analyzingCompleter = Completer(); + } + + void _resetAnalyzingBool() { + _currentlyAnalyzing = null; + } + + Future _waitForAnalysisToComplete() async { + // Wait until it's done analyzing. + if (_currentlyAnalyzing == null) { + await _analyzingCompleter.future; + } + Stopwatch stopwatch = Stopwatch()..start(); + while (_currentlyAnalyzing == true) { + await _analyzingCompleter.future; + } + print("isAnalyzing is now done after ${stopwatch.elapsed}"); + if (_processExited) { + // TODO(jensj): We should extract the correct stacktrace somehow. + throw "Process exited."; + } + } + + void _listenToStdout(List event) { + // General idea taken from + // pkg/analysis_server/lib/src/lsp/lsp_packet_transformer.dart + for (int element in event) { + _buffer.add(element); + if (_verbosity > 3 && + _buffer.length >= 1000 && + _buffer.length % 1000 == 0) { + print("DEBUG MESSAGE: Stdout buffer with length " + "${_buffer.length} so far: " + "${utf8.decode(_buffer)}"); + } + if (_headerContentLength == null && _endsWithCrLfCrLf()) { + String headerRaw = utf8.decode(_buffer); + _buffer.clear(); + // Use a regex that makes the "\r" optional to handle "The Dart VM + // service is listening on [..." message - at least on linux - being \n + // terminated which would otherwise mean that we'd be stuck because no + // message would start with "Content-Length:". + List headers = headerRaw.split(_newLineRegExp); + for (String header in headers) { + if (!_printedVmServiceStuff && + header.startsWith("The Dart VM service")) { + print("\n\n$header\n\n"); + _printedVmServiceStuff = true; + } + if (header.startsWith("Content-Length:")) { + String contentLength = + header.substring("Content-Length:".length).trim(); + _headerContentLength = int.parse(contentLength); + break; + } + } + } else if (_headerContentLength != null && + _buffer.length == _headerContentLength!) { + String messageString = utf8.decode(_buffer); + _buffer.clear(); + _headerContentLength = null; + Map message = + json.decode(messageString) as Map; + + // {"jsonrpc":"2.0","method":"$/analyzerStatus","params":{"isAnalyzing":false}} + dynamic method = message["method"]; + if (method == r"$/analyzerStatus") { + dynamic params = message["params"]; + if (params is Map) { + dynamic isAnalyzing = params["isAnalyzing"]; + if (isAnalyzing is bool) { + _setAnalyzing(isAnalyzing); + if (_verbosity > 0) { + print("Got analyzerStatus isAnalyzing = $isAnalyzing"); + } + } + } + } + dynamic possibleId = message["id"]; + if (possibleId is int) { + if (possibleId > _largestIdSeen) { + _largestIdSeen = possibleId; + } + + if (_verbosity > 0) { + if (messageString.length > _printSizeCap) { + print("Got message " + "${messageString.substring(0, _printSizeCap)}..."); + } else { + print("Got message $messageString"); + } + } + + OutstandingRequest? outstandingRequest = + _outstandingRequestsWithId.remove(possibleId); + if (outstandingRequest != null) { + outstandingRequest.stopwatch.stop(); + outstandingRequest.completer.complete(message); + if (_verbosity > 2) { + print(" => Got response for $possibleId in " + "${outstandingRequest.stopwatch.elapsed}"); + } + } + } else if (_verbosity > 1) { + if (messageString.length > _printSizeCap) { + print("Got message " + "${messageString.substring(0, _printSizeCap)}..."); + } else { + print("Got message $messageString"); + } + } + } + } + } + + Future _send(Map json) async { + if (_processExited) throw "Process is gone."; + // Mostly copied from + // pkg/analysis_server/lib/src/lsp/channel/lsp_byte_stream_channel.dart + String jsonEncodedBody = jsonEncode(json); + Uint8List utf8EncodedBody = utf8.encode(jsonEncodedBody); + String header = "Content-Length: ${utf8EncodedBody.length}\r\n" + "Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n\r\n"; + Uint8List asciiEncodedHeader = ascii.encode(header); + + OutstandingRequest? result; + + dynamic possibleId = json["id"]; + if (possibleId is int) { + if (possibleId > _largestIdSeen) { + _largestIdSeen = possibleId; + } + result = OutstandingRequest(); + _outstandingRequestsWithId[possibleId] = result; + if (_verbosity > 2) { + print("Sending message with id $possibleId"); + } + } + + // Header is always ascii, body is always utf8! + _p.stdin.add(asciiEncodedHeader); + _p.stdin.add(utf8EncodedBody); + await _p.stdin.flush(); + if (_verbosity > 2) { + print("\n\nMessage sent...\n\n"); + print("jsonEncodedBody: $jsonEncodedBody"); + } + return result; + } + + /// Copied from pkg/analysis_server/lib/src/lsp/lsp_packet_transformer.dart. + bool _endsWithCrLfCrLf() { + int l = _buffer.length; + return l > 4 && + _buffer[l - 1] == 10 && + _buffer[l - 2] == 13 && + _buffer[l - 3] == 10 && + _buffer[l - 4] == 13; + } +} + +class Location { + final Uri uri; + final int line; + final int column; + + Location(this.uri, this.line, this.column); + + @override + String toString() => "Location[$uri:$line:$column]"; +} + +// Messages taken from what VSCode sent. +class Messages { + static Map initNotification = { + "jsonrpc": "2.0", + "method": "initialized", + "params": {} + }; + + static Map gotoDef(int id, Location location) { + return { + "jsonrpc": "2.0", + "id": id, + "method": "textDocument/definition", + "params": { + "textDocument": {"uri": "${location.uri}"}, + "position": {"line": location.line, "character": location.column} + } + }; + } + + static Map implementation(int id, Location location) { + return { + "jsonrpc": "2.0", + "id": id, + "method": "textDocument/implementation", + "params": { + "textDocument": {"uri": "${location.uri}"}, + "position": {"line": location.line, "character": location.column} + } + }; + } + + static Map initMessage( + int processId, Uri rootUri, List additionalWorkspaceUris) { + String rootPath = rootUri.toFilePath(); + String name = rootUri.pathSegments.last; + if (name.isEmpty) { + name = rootUri.pathSegments[rootUri.pathSegments.length - 2]; + } + return { + "id": 0, + "jsonrpc": "2.0", + "method": "initialize", + "params": { + "processId": processId, + "clientInfo": {"name": "lspTestScript", "version": "0.0.1"}, + "locale": "en", + "rootPath": rootPath, + "rootUri": "$rootUri", + "capabilities": {}, + "initializationOptions": {}, + "workspaceFolders": [ + {"uri": "$rootUri", "name": name}, + ...additionalWorkspaceUris.map((uri) { + String name = uri.pathSegments.last; + if (name.isEmpty) { + name = uri.pathSegments[uri.pathSegments.length - 2]; + } + return { + "uri": "$uri", + "name": name, + }; + }) + ] + } + }; + } + + static Map initMore(Uri sdkUri) { + String sdkPath = sdkUri.toFilePath(); + return { + // "id": 1, + "jsonrpc": "2.0", + "result": [ + { + "useLsp": true, + "sdkPath": sdkPath, + "allowAnalytics": false, + } + ] + }; + } + + static Map references(int id, Location location) { + return { + "jsonrpc": "2.0", + "id": id, + "method": "textDocument/references", + "params": { + "textDocument": {"uri": "${location.uri}"}, + "position": {"line": location.line, "character": location.column}, + "context": {"includeDeclaration": true} + } + }; + } + + static Map openFile( + Uri file, int fileVersion, String content) { + return { + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "$file", + "languageId": "dart", + "version": fileVersion, + "text": content + }, + } + }; + } + + static Map changeFileContent( + Uri file, String newContent, int fileVersion) { + return { + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": {"uri": "$file", "version": fileVersion}, + "contentChanges": [ + {"text": newContent} + ] + } + }; + } +} + +class OutstandingRequest { + final Stopwatch stopwatch = Stopwatch(); + final Completer> completer = Completer(); + OutstandingRequest() { + stopwatch.start(); + } +} diff --git a/pkg/front_end/tool/fuzz/compile_helper.dart b/pkg/front_end/tool/fuzz/compile_helper.dart new file mode 100644 index 000000000000..efec0437e683 --- /dev/null +++ b/pkg/front_end/tool/fuzz/compile_helper.dart @@ -0,0 +1,94 @@ +// Copyright (c) 2025, 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:async' show ZoneSpecification, runZoned; + +import 'dart:io' show File; + +import 'package:front_end/src/api_prototype/compiler_options.dart' + show CompilerOptions; + +import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart' + show DiagnosticMessage; +import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart'; + +import "package:front_end/src/api_prototype/memory_file_system.dart" + show MemoryFileSystem; + +import 'package:front_end/src/compute_platform_binaries_location.dart' + show computePlatformBinariesLocation; + +import 'package:kernel/kernel.dart' show Component; + +import "../../test/incremental_utils.dart" as util; + +import "../../test/incremental_suite.dart" as incrementalTest; + +import '../../test/utils/io_utils.dart' show computeRepoDirUri; + +final Uri repoDir = computeRepoDirUri(); + +class Helper { + MemoryFileSystem? fs; + CompilerOptions? options; + incrementalTest.TestIncrementalCompiler? compiler; + late final Uri base; + late final Uri sdkSummary; + late final List sdkSummaryData; + + Future setup() async { + final Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true); + base = Uri.parse("org-dartlang-test:///"); + sdkSummary = base.resolve("vm_platform_strong.dill"); + Uri platformUri = sdkRoot.resolve("vm_platform_strong.dill"); + sdkSummaryData = await new File.fromUri(platformUri).readAsBytes(); + } + + Future<(Object, StackTrace)?> compile(String program) async { + if (fs == null) { + fs = new MemoryFileSystem(base); + fs!.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData); + } + + Uri uri = base.resolve("main.dart"); + fs!.entityForUri(uri).writeAsStringSync(program); + Map diagnostics = {}; + options ??= incrementalTest.getOptions(); + options!.fileSystem = fs!; + options!.sdkRoot = null; + options!.sdkSummary = sdkSummary; + options!.omitPlatform = true; + options!.onDiagnostic = (DiagnosticMessage message) { + diagnostics[message.severity.toString()] = + (diagnostics[message.severity.toString()] ?? 0) + 1; + }; + + compiler ??= new incrementalTest.TestIncrementalCompiler(options!, uri); + compiler!.invalidate(uri); + + try { + ZoneSpecification specification = + new ZoneSpecification(print: (_1, _2, _3, String line) { + // Swallow! + }); + await runZoned(() async { + Stopwatch stopwatch = new Stopwatch()..start(); + IncrementalCompilerResult result = await compiler! + .computeDelta(entryPoints: [uri], fullComponent: true); + Component component = result.component; + + util.throwOnEmptyMixinBodies(component); + await util.throwOnInsufficientUriToSource(component); + print("Compile took ${stopwatch.elapsedMilliseconds} ms. " + "Got $diagnostics"); + }, zoneSpecification: specification); + return null; + } catch (e, st) { + print("Crashed on input."); + options = null; + compiler = null; + return (e, st); + } + } +} diff --git a/pkg/front_end/tool/fuzz/fuzz.dart b/pkg/front_end/tool/fuzz/fuzz.dart new file mode 100644 index 000000000000..01a0c6f35a4e --- /dev/null +++ b/pkg/front_end/tool/fuzz/fuzz.dart @@ -0,0 +1,142 @@ +// Copyright (c) 2025, 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:async'; +import 'dart:io' show Directory, File, sleep, stdin; + +import '../../test/utils/io_utils.dart' show computeRepoDirUri; + +import 'analyzer_helper.dart'; +import 'compile_helper.dart'; +import "gReader.dart" as gReader; +import 'stacktrace_utils.dart'; + +final Uri repoDir = computeRepoDirUri(); +int _worldNum = 0; +int _cfeCrashes = 0; +int _analyzerCrashes = 0; +Stopwatch _stopwatch = new Stopwatch(); +Stopwatch _createStopwatch = new Stopwatch(); +Stopwatch _cfeStopwatch = new Stopwatch(); +Stopwatch _analyzerStopwatch = new Stopwatch(); + +Future main() async { + _setupStdin(); + try { + _stopwatch.start(); + Helper helper = new Helper(); + await helper.setup(); + AnalyzerHelper analyzerHelper = new AnalyzerHelper(); + Directory root = Directory.systemTemp.createTempSync("fuzzer"); + File analyzerFileHelper = + new File.fromUri(root.uri.resolve("testfile.dart")); + analyzerFileHelper.writeAsStringSync(""); + await analyzerHelper.setup(root.uri); + gReader.initialize(repoDir.resolve("pkg/front_end/tool/fuzz/Dart.g")); + while (!_quit) { + _worldNum++; + if (_worldNum > 100000) break; + print("----------------"); + print("World #$_worldNum"); + print("----------------"); + + _createStopwatch.start(); + String data = gReader.createRandomProgram(includeWhy: false); + _createStopwatch.stop(); + + _cfeStopwatch.start(); + (Object, StackTrace)? result = await helper.compile(data); + _cfeStopwatch.stop(); + if (result != null) { + _cfeCrashes++; + var (Object e, StackTrace st) = result; + String category = categorize(st); + Directory d = + new Directory.fromUri(repoDir.resolve("fuzzDumps/$category/")); + d.createSync(recursive: true); + File f = new File.fromUri( + repoDir.resolve("fuzzDumps/$category/$_worldNum.input")); + f.writeAsStringSync(data); + print("Crashed on input. Input dumped into $f"); + } + + _analyzerStopwatch.start(); + (Object, StackTrace)? resultAnalyzer = await compileWithAnalyzer( + analyzerHelper, data, analyzerFileHelper.uri, _worldNum); + _analyzerStopwatch.stop(); + if (resultAnalyzer != null) { + _analyzerCrashes++; + var (Object e, StackTrace st) = resultAnalyzer; + String category = categorize(st); + Directory d = + new Directory.fromUri(repoDir.resolve("fuzzDumps/$category/")); + d.createSync(recursive: true); + File f = new File.fromUri( + repoDir.resolve("fuzzDumps/$category/$_worldNum.analyzerinput")); + f.writeAsStringSync(data); + print("Analyzer crashed on input. Input dumped into $f"); + + analyzerHelper = new AnalyzerHelper(); + await analyzerHelper.setup(root.uri); + } + } + print("Done."); + analyzerHelper.shutdown(); + printInfo(false); + } finally { + await _resetStdin(); + } +} + +void printInfo(bool addSleep) { + int countWorlds = _worldNum - 1; + print("Processed $countWorlds random programs in ${_stopwatch.elapsed}."); + if (countWorlds > 0) { + print("CFE crashes: $_cfeCrashes " + "(${((_cfeCrashes * 100) / countWorlds).toStringAsFixed(2)}%)"); + print("Analyzer crashes: $_analyzerCrashes " + "(${((_analyzerCrashes * 100) / countWorlds).toStringAsFixed(2)}%)"); + } + print("Spend ${_createStopwatch.elapsed} creating random programs."); + print("Spend ${_cfeStopwatch.elapsed} compiling with the CFE."); + print("Spend ${_analyzerStopwatch.elapsed} compiling with the Analyzer."); + if (addSleep) sleep(const Duration(seconds: 3)); +} + +bool? _oldEchoMode; +bool? _oldLineMode; +StreamSubscription>? _stdinSubscription; +bool _quit = false; + +Future _resetStdin() async { + try { + stdin.echoMode = _oldEchoMode!; + } catch (e) {} + try { + stdin.lineMode = _oldLineMode!; + } catch (e) {} + await _stdinSubscription!.cancel(); +} + +void _setupStdin() { + try { + _oldEchoMode = stdin.echoMode; + _oldLineMode = stdin.lineMode; + stdin.echoMode = false; + stdin.lineMode = false; + } catch (e) { + print("Trying to setup 'stdin' failed. Continuing anyway, " + "but 'q' and 'i' might not work."); + } + _stdinSubscription = stdin.listen((List event) { + if (event.length == 1 && event.single == "q".codeUnits.single) { + print("\n\nGot told to quit!\n\n"); + _quit = true; + } else if (event.length == 1 && event.single == "i".codeUnits.single) { + printInfo(true); + } else { + print("\n\nGot stdin input: $event\n\n"); + } + }); +} diff --git a/pkg/front_end/tool/fuzz/gReader.dart b/pkg/front_end/tool/fuzz/gReader.dart new file mode 100644 index 000000000000..308be95f6d10 --- /dev/null +++ b/pkg/front_end/tool/fuzz/gReader.dart @@ -0,0 +1,1140 @@ +// Copyright (c) 2025, 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' show utf8; +import 'dart:io' show File, Platform, stderr; +import 'dart:math' show Random; + +import 'package:_fe_analyzer_shared/src/scanner/characters.dart'; + +List stack = []; +Map data = {}; + +void main() { + initialize(Platform.script.resolve("Dart.g")); + print(createRandomProgram(includeWhy: true)); +} + +void initialize(Uri dartG) { + parseDartG(dartG); + postParse(); +} + +String createRandomProgram({required bool includeWhy}) { + ProgramCreator programCreator = new ProgramCreator(createWhy: includeWhy); + programCreator.path.add("startSymbol"); + data["startSymbol"]!.accept(programCreator); + if (includeWhy) { + return "${programCreator.sb.toString()}" + "\n\n\n\n" + "${programCreator.sb.toStringWhy()}"; + } else { + return programCreator.sb.toString(); + } +} + +void postParse() { + EntryChecker entryChecker = new EntryChecker(); + for (MapEntry entry in data.entries) { + entryChecker.parent = entry.key; + entry.value.accept(entryChecker); + } + DepthMarker depthMarker = new DepthMarker(); + for (int i = 0; i < 2; i++) { + for (MapEntry entry in data.entries) { + entry.value.accept(depthMarker); + } + depthMarker.recurse = true; + } + SanityChecker sanityChecker = new SanityChecker(); + for (MapEntry entry in data.entries) { + entry.value.accept(sanityChecker); + } +} + +void parseDartG(Uri dartG) { + File f = new File.fromUri(dartG); + List input = f.readAsBytesSync(); + List tokens = tokenize(input); + + Token? currentToken = tokens.first; + while (currentToken != null) { + currentToken = parseDef(currentToken); + } + // Attempt to have less exotic identifiers and types etc. + data["identifier"] = new Literal("foo"); + data["typeIdentifier"] = new Literal("foo"); + data["typeNotVoid"] = new Literal("foo"); + data["singleLineString"] = new Literal("'ssls'"); // small single line string + data["argument"] = new Literal("42"); +} + +class StringBufferWrapper { + final bool createWhy; + final StringBuffer sb = new StringBuffer(); + final StringBuffer sbWhy = new StringBuffer(); + int get length => sb.length; + final ProgramCreator parent; + int currentNewlines = 1; + String prev = ""; + + StringBufferWrapper(this.createWhy, this.parent); + + void addNewline() { + if (parent.singleLineStringDepth > 0) return; + if (parent.numericLiteralDepth > 0) return; + if (currentNewlines >= 2) return; + sb.writeln(); + if (createWhy) { + sbWhy.writeln(); + } + currentNewlines++; + } + + void write(String s, String why) { + if (currentNewlines > 0 || parent.numericLiteralDepth > 0) { + // No space + } else if (prev == "" || prev == "@" || prev == "." || prev == "(") { + // No space. + } else if (s == "." || s == "," || s == "(" || s == ")" || s == ";") { + // No space. + } else { + sb.write(" "); + } + currentNewlines = 0; + prev = s; + sb.write(s); + if (createWhy) { + sbWhy.writeln("$s: ${parent.path.join("/")}"); + } + } + + void writeCharCode(int charCode, String why) { + write(new String.fromCharCode(charCode), why); + } + + @override + String toString() => sb.toString(); + String toStringWhy() => sbWhy.toString(); +} + +class WrappedRandom { + final Random random = new Random.secure(); + + int nextInt(int max) { + int result = random.nextInt(max); + return result; + } + + bool nextBool() { + bool result = random.nextBool(); + return result; + } +} + +class ProgramCreator implements Visitor { + final WrappedRandom random = new WrappedRandom(); + late final StringBufferWrapper sb; + int depth = 0; + int? currentExpressionStart; + int? currentTopLevelStart; + int singleLineStringDepth = 0; + int numericLiteralDepth = 0; + final List path = []; + + bool isDepthHigh() => depth > 1000; + bool isOutputLarge() => + sb.length > 14000 || + (currentTopLevelStart != null && + (sb.length - currentTopLevelStart!) > 700); + bool isBigExpression() => + currentExpressionStart != null && + (sb.length - currentExpressionStart!) >= 20; + + ProgramCreator({required bool createWhy}) { + sb = new StringBufferWrapper(createWhy, this); + } + + @override + void defaultNode(Node node) { + throw "Should never get to defaultNode"; + } + + @override + void visitAnyCharNode(AnyCharNode node) { + int from = $SPACE; + int to = $z; + sb.writeCharCode(random.nextInt(to - from + 1) + from, "visitAnyCharNode"); + } + + @override + void visitDummyNode(DummyNode node) { + throw "Should never get to visitDummyNode"; + } + + @override + void visitEmpty(Empty node) { + // do nothing + } + + @override + void visitEndOfFileNode(EndOfFileNode node) { + // do nothing + } + + @override + void visitLeaf(Leaf node) { + depth++; + final String what = node.token.value; + path.add(what); + if (what == "singleLineString") { + singleLineStringDepth++; + } else if (what == "numericLiteral") { + numericLiteralDepth++; + } + int? oldExpressionStart = currentExpressionStart; + if (what == "expression" && oldExpressionStart == null) { + // Only write if it's not already set. + currentExpressionStart = sb.length; + } + int? oldTopLevelStart = currentTopLevelStart; + if (what == "topLevelDefinition" && oldTopLevelStart == null) { + // Only write if it's not already set. + currentTopLevelStart = sb.length; + } + + const newLinesAround = { + "topLevelDefinition", + "libraryName", + "partDirective" + }; + const newLinesAfter = {"metadatum"}; + bool addNewlinesAround = newLinesAround.contains(what); + bool addNewlinesAfter = newLinesAfter.contains(what); + + if (addNewlinesAround) { + sb.addNewline(); + } + + Node linked = data[what]!; + linked.accept(this); + + if (addNewlinesAround || addNewlinesAfter) { + sb.addNewline(); + } + + currentExpressionStart = oldExpressionStart; + currentTopLevelStart = oldTopLevelStart; + + if (what == "singleLineString") { + singleLineStringDepth--; + } else if (what == "numericLiteral") { + numericLiteralDepth--; + } + + if (path.removeLast() != what) throw "Bad remove!"; + depth--; + } + + @override + void visitLiteral(Literal node) { + sb.write(node.content, "visitLiteral"); + } + + @override + void visitOneOrMore(OneOrMore node) { + depth++; + int count = random.nextInt(3) + 1; + if (isDepthHigh() || isOutputLarge() || isBigExpression()) count = 1; + for (int i = 0; i < count; i++) { + node.visitChildren(this); + } + depth--; + } + + @override + void visitOptional(Optional node) { + if (isDepthHigh() || isOutputLarge() || isBigExpression()) return; + + depth++; + if (random.nextInt(10) < 2) { + node.visitChildren(this); + } + depth--; + } + + @override + void visitOrNode(OrNode node) { + depth++; + List nodes = node.nodesFiltered; + if (nodes.length == 1) { + nodes[0].accept(this); + } else { + // In practise some should be more likely than others. + if (nodes.length == 2 && + nodes[0] is Leaf && + (nodes[0] as Leaf).token.value == "libraryDeclaration" && + nodes[1] is Leaf && + (nodes[1] as Leaf).token.value == "partDeclaration") { + // libraryDeclaration should be more likely. + if (random.nextInt(100) < 95) { + nodes[0].accept(this); + } else { + nodes[1].accept(this); + } + } else { + Node? chosen; + bool pickSmallestDepth = false; + if (isBigExpression() || isDepthHigh() || isOutputLarge()) { + // Choose the one with the smallest depth. + pickSmallestDepth = true; + } else if (random.nextBool() && + 1 + 1 == 3 /* i.e. currently disabled */) { + // If depth and length is not too big yet, and if several nodes has + // depth <= 10, choose randomly among those. + List smallDepthNodes = []; + for (Node childNode in nodes) { + if (childNode.depth! > 0 && (childNode.depth! <= 10)) { + smallDepthNodes.add(childNode); + } + } + if (smallDepthNodes.isNotEmpty) { + chosen = smallDepthNodes[random.nextInt(smallDepthNodes.length)]; + } + } + + if (pickSmallestDepth) { + chosen = nodes[0]; + for (Node childNode in nodes) { + if (childNode.depth! > 0 && + (childNode.depth! < chosen!.depth! || chosen.depth! < 0)) { + chosen = childNode; + } + } + if (chosen!.depth! < 0) { + throw "Not good"; + } + } + if (chosen == null) { + chosen = nodes[random.nextInt(nodes.length)]; + } + + chosen.accept(this); + } + } + depth--; + } + + @override + void visitRangeNode(RangeNode node) { + String a = node.a.content; + String b = node.b.content; + if (a.length != 1 || b.length != 1) { + throw "Unexpected range: $node ('$a' '$b')"; + } + int from = a.codeUnitAt(0); + int to = b.codeUnitAt(0); + sb.writeCharCode(random.nextInt(to - from + 1) + from, "visitRangeNode"); + } + + @override + void visitSequence(Sequence node) { + depth++; + node.visitChildren(this); + if (node.nodes.isNotEmpty) { + Node last = node.nodes.last; + if (last is Literal && last.content == ";") { + sb.addNewline(); + } + } + depth--; + } + + @override + void visitTildeNode(TildeNode node) { + Set no = {}; + List getNodes(Node node) { + List result = []; + if (node is Sequence) { + for (Node childNode in node.nodes) { + result.addAll(getNodes(childNode)); + } + } else if (node is OrNode) { + for (Node childNode in node.nodes) { + result.addAll(getNodes(childNode)); + } + } else if (node is Literal) { + result.add(node); + } else { + throw "Can't get nodes of ${node} (${node.runtimeType})"; + } + return result; + } + + List nodes = getNodes(node.a); + for (Node childNode in nodes) { + if (childNode is! Literal) { + throw "Not a literal...: ${childNode.runtimeType}: $node"; + } + Literal literal = childNode; + String s = literal.content; + for (int i = 0; i < s.length; i++) { + no.add(s.codeUnitAt(i)); + } + } + + int from = $SPACE; + int to = $z; + int charCode = random.nextInt(to - from + 1) + from; + while (no.contains(charCode)) { + charCode = random.nextInt(to - from + 1) + from; + } + sb.writeCharCode(charCode, "visitTildeNode"); + } + + @override + void visitZeroOrMore(ZeroOrMore node) { + depth++; + int count = random.nextInt(3); + if (node.a is Sequence && + (node.a as Sequence) + .nodes + .where( + (Node e) => e is Leaf && e.token.value == "topLevelDefinition") + .isNotEmpty) { + // We likely want more top level definitions. + count = random.nextInt(20); + } else if (node.a is Sequence && + (node.a as Sequence) + .nodes + .where((Node e) => e is Leaf && e.token.value == "metadatum") + .isNotEmpty) { + // We likely don't want too many metadata entries... + if (random.nextInt(100) < 90) { + count = 0; + } + } + if (isDepthHigh() || isOutputLarge() || isBigExpression()) { + count = 0; + } + for (int i = 0; i < count; i++) { + node.visitChildren(this); + + if (isBigExpression()) { + break; + } + } + depth--; + } +} + +class SanityChecker extends Visitor { + ProgramCreator programCreator = new ProgramCreator(createWhy: false); + + @override + void defaultNode(Node node) { + if (node.depth == null) throw "$node (${node.runtimeType}) has null depth"; + if (node is TildeNode) { + programCreator.visitTildeNode(node); + } + super.defaultNode(node); + } +} + +class DepthMarker implements Visitor { + bool recurse = false; + + @override + void defaultNode(Node node) { + throw "Should never get here."; + } + + @override + void visitAnyCharNode(AnyCharNode node) { + node.depth = 1; + if (recurse) node.visitChildren(this); + } + + @override + void visitDummyNode(DummyNode node) { + node.depth = 1; + if (recurse) node.visitChildren(this); + } + + @override + void visitEmpty(Empty node) { + node.depth = 1; + if (recurse) node.visitChildren(this); + } + + @override + void visitEndOfFileNode(EndOfFileNode node) { + node.depth = 1; + if (recurse) node.visitChildren(this); + } + + @override + void visitLeaf(Leaf node) { + Node linked = data[node.token.value]!; + if (linked.depth != null && linked.depth! > 0) { + node.depth = linked.depth! + 1; + } else if (node.depth == null) { + node.depth = -1; + linked.accept(this); + if (linked.depth! > 0) { + node.depth = linked.depth! + 1; + } else { + // Recursive... Keep the -1 depth and see that as "infinite". + } + } else { + // Recursive... Keep the -1 depth and see that as "infinite". + } + } + + @override + void visitLiteral(Literal node) { + node.depth = 1; + if (recurse) node.visitChildren(this); + } + + @override + void visitOneOrMore(OneOrMore node) { + node.visitChildren(this); + node.depth = node.a.depth! + 1; + } + + @override + void visitOptional(Optional node) { + node.depth = 1; + if (recurse) node.visitChildren(this); + } + + @override + void visitOrNode(OrNode node) { + node.visitChildren(this); + int min = node.nodes.first.depth!; + for (Node node in node.nodes) { + if (node.depth! > 0 && (node.depth! < min || min < 0)) min = node.depth!; + } + node.depth = min + 1; + } + + @override + void visitRangeNode(RangeNode node) { + node.depth = 1; + if (recurse) node.visitChildren(this); + } + + @override + void visitSequence(Sequence node) { + node.visitChildren(this); + int max = node.nodes.first.depth!; + for (Node childNode in node.nodes) { + if (childNode.depth! > max) max = childNode.depth!; + if (childNode.depth == -1) max = -1; + if (max == -1) { + node.depth = -1; + return; + } + } + node.depth = max + 1; + } + + @override + void visitTildeNode(TildeNode node) { + // Naively assume that this is true. + node.depth = 1; + if (recurse) node.visitChildren(this); + } + + @override + void visitZeroOrMore(ZeroOrMore node) { + node.depth = 1; + if (recurse) node.visitChildren(this); + } +} + +class EntryChecker extends Visitor { + String? parent; + + @override + void visitLeaf(Leaf node) { + if (!data.containsKey(node.token.value)) { + throw "Reference to unknown entry '${node.token.value}' from '$parent'"; + } + } +} + +Token? parseDef(Token token) { + if (token.value == "grammar" && + token.next?.next?.value == ";" && + token.next?.next?.next != null) { + // Skip something like 'grammar Dart;'. + token = token.next!.next!.next!; + } + if (token.value == "fragment") { + // Skip 'fragment'. + token = token.next!; + } + while (token.value == "@") { + // Skip injected java code. + while (token.value != "{") { + token = token.next!; + } + int count = 1; + token = token.next!; + while (count > 0) { + if (token.value == "{") { + count++; + } else if (token.value == "}") { + count--; + } + token = token.next!; + if (count == 0) { + break; + } + } + } + Token name = token; + token = token.next!; + if (token.value != ":") throw "Expected ':' at around ${token.offset}"; + token = token.next!; + token = parseContent(token, depth: 1); + if (token.value != ";") throw "Expected ';' at around offset ${token.offset}"; + + data[name.value] = stack.removeLast(); + if (stack.isNotEmpty) throw "Expected stack to be empty"; + + return token.next; +} + +void join() { + if (stack.length > 1) { + List sequenceList = []; + for (Node node in stack) { + if (node is! DummyNode) sequenceList.add(node); + } + if (sequenceList.length > 1) { + // If something like "'a' .. 'z'" make that into a RangeNode. + if (sequenceList.length == 3 && + sequenceList[0] is Literal && + sequenceList[1] is Leaf && + (sequenceList[1] as Leaf).token.value == ".." && + sequenceList[2] is Literal) { + RangeNode range = new RangeNode( + sequenceList[0] as Literal, sequenceList[2] as Literal); + stack = [range]; + } else { + Sequence sequence = new Sequence(sequenceList); + stack = [sequence]; + } + } else { + stack = sequenceList; + } + } +} + +Token parseContent(Token token, {required int depth}) { + Token originalToken = token; + try { + while (token.value != ";" && token.value != ")") { + if (token.value == "|" && stack.isEmpty) { + stderr.writeln("Warning: Seemingly stray '|' at ${token.offset}"); + token = token.next!; + } + if (token.value == "|") { + if (token != originalToken && depth > 1) break; + join(); + Node a = stack.removeLast(); + token = parseContent(token.next!, depth: depth + 1); + Node b = stack.removeLast(); + OrNode orNode = new OrNode(); + if (a is OrNode) { + orNode.nodes.addAll(a.nodes); + } else { + orNode.nodes.add(a); + } + if (b is OrNode) { + orNode.nodes.addAll(b.nodes); + } else { + orNode.nodes.add(b); + } + stack.add(orNode); + } else if (token.value == "(") { + List savedStack = stack; + stack = []; + token = parseContent(token.next!, depth: 1); + if (token.value != ")") throw "Expected ')'"; + if (stack.isEmpty) { + // () + stack.add(new Empty()); + } + if (stack.length != 1) throw "Expected stack to have length 1."; + savedStack.addAll(stack); + stack = savedStack; + token = token.next!; + } else if (token.value == "{") { + token = parseBrackets(token.next!); + } else if (token.value == "'") { + token = parseLiteral(token.next!); + } else if (token.value == "?") { + Node a = stack.removeLast(); + if (a is DummyNode) { + stack.add(a); + } else { + stack.add(new Optional(a)); + } + token = token.next!; + } else if (token.value == "+") { + Node a = stack.removeLast(); + stack.add(new OneOrMore(a)); + token = token.next!; + } else if (token.value == "*") { + Node a = stack.removeLast(); + stack.add(new ZeroOrMore(a)); + token = token.next!; + } else if (token.value == "~") { + token = parseContent(token.next!, depth: depth + 1); + Node a = stack.removeLast(); + stack.add(new TildeNode(a)); + } else if (token.value == ".") { + stack.add(new AnyCharNode(token)); + token = token.next!; + } else if (token.value == "EOF") { + stack.add(new EndOfFileNode(token)); + token = token.next!; + } else { + stack.add(new Leaf(token)); + token = token.next!; + } + } + join(); + } catch (e, st) { + print("Got error at around ${token.offset}: $st"); + rethrow; + } + return token; +} + +Token parseBrackets(Token token) { + while (token.value != "}") { + token = token.next!; + } + stack.add(new DummyNode()); + return token.next!; +} + +Token parseLiteral(Token token) { + List content = []; + while (token.value != "'") { + if (token.value == r"\" && token.next != null) { + // Escape stuff... + Token next = token.next!; + if (next.value == '\'') { + content.add(next.value); + token = next.next!; + } else if (next.value == '\\') { + content.add("\\"); + token = next.next!; + } else if (next.value == 'r') { + content.add("\r"); + token = next.next!; + } else if (next.value == 'n') { + content.add("\n"); + token = next.next!; + } else if (next.value == 'uFEFF') { + content.add("\uFEFF"); + token = next.next!; + } else if (next.value == 't') { + content.add("\t"); + token = next.next!; + } else { + print(token.next!.value); + } + } else { + content.add(token.value); + token = token.next!; + } + } + stack.add(new Literal(content.join())); + return token.next!; +} + +List tokenize(List bytes) { + List tmp = []; + int index = 0; + bool inComment = false; + bool inQuote = false; + bool inEscaped = false; + List tokens = []; + + void pushToken() { + if (inComment) { + tmp.clear(); + } else if (tmp.isNotEmpty) { + String s = utf8.decode(tmp); + if (s == "." && tokens.isNotEmpty && tokens.last.value == ".") { + // Hack: Join them. + Token last = tokens.removeLast(); + Token nextToken = new Token("..", last.offset); + if (tokens.isNotEmpty) tokens.last.next = nextToken; + tokens.add(nextToken); + } else { + Token nextToken = new Token(s, index); + if (tokens.isNotEmpty) tokens.last.next = nextToken; + tokens.add(nextToken); + } + tmp.clear(); + } + } + + while (index < bytes.length) { + int b = bytes[index++]; + if (b == $LF || b == $CR) { + pushToken(); + inComment = false; + inEscaped = false; + } else if (inComment) { + // ignore comments. + } else if (!inQuote && + b == $SLASH && + index < bytes.length && + bytes[index] == $SLASH) { + inComment = true; + } else if (!inEscaped && + b == $BACKSLASH && + index < bytes.length && + ((bytes[index] == $SQ) || (bytes[index] == $BACKSLASH))) { + // Escaped ' or escaped \. + inEscaped = true; + pushToken(); + tmp.add(b); + pushToken(); + } else if (b == $SQ) { + if (!inEscaped) { + inQuote = !inQuote; + } + inEscaped = false; + pushToken(); + tmp.add(b); + pushToken(); + } else if (b == $SPACE && !inQuote) { + pushToken(); + inEscaped = false; + } else if ((b >= $0 && b <= $9) || + (b >= $a && b <= $z) || + (b >= $A && b <= $Z) || + b == $_) { + tmp.add(b); + inEscaped = false; + } else { + pushToken(); + tmp.add(b); + pushToken(); + inEscaped = false; + } + } + pushToken(); + + return tokens; +} + +class Token { + final String value; + final int offset; + Token? next; + + Token(this.value, this.offset); + + @override + String toString() => "$value"; +} + +abstract class Node { + /// Not really depth, + /// but minimum number of steps to the possibility of ending. + int? depth; + + void accept(Visitor visitor); + void visitChildren(Visitor visitor); +} + +class DummyNode extends Node { + @override + void accept(Visitor visitor) { + visitor.visitDummyNode(this); + } + + @override + void visitChildren(Visitor visitor) {} +} + +class Empty extends Node { + @override + void accept(Visitor visitor) { + visitor.visitEmpty(this); + } + + @override + void visitChildren(Visitor visitor) {} +} + +class AnyCharNode extends Node { + final Token token; + + AnyCharNode(this.token); + + @override + String toString() => "$token"; + + @override + void accept(Visitor visitor) { + visitor.visitAnyCharNode(this); + } + + @override + void visitChildren(Visitor visitor) {} +} + +class EndOfFileNode extends Node { + final Token token; + + EndOfFileNode(this.token); + + @override + String toString() => "$token"; + + @override + void accept(Visitor visitor) { + visitor.visitEndOfFileNode(this); + } + + @override + void visitChildren(Visitor visitor) {} +} + +class Leaf extends Node { + final Token token; + + Leaf(this.token); + + @override + String toString() => "$token"; + + @override + void accept(Visitor visitor) { + visitor.visitLeaf(this); + } + + @override + void visitChildren(Visitor visitor) {} +} + +class Sequence extends Node { + final List nodes; + + Sequence(this.nodes); + + @override + String toString() => "$nodes"; + + @override + void accept(Visitor visitor) { + visitor.visitSequence(this); + } + + @override + void visitChildren(Visitor visitor) { + for (Node node in nodes) { + node.accept(visitor); + } + } +} + +class OrNode extends Node { + List nodes = []; + List? _nodesFiltered; + + List get nodesFiltered { + return _nodesFiltered ??= _filterNodes(); + } + + List _filterNodes() { + bool skipLeaf(Leaf leaf) { + const Set skip = { + "functionType", + "multiLineString", + "SUPER", + "awaitExpression", + "functionExpression", + }; + return skip.contains(leaf.token.value); + } + + List result = []; + for (Node node in nodes) { + if (node is Sequence && + node.nodes + .where((child) => child is Leaf && skipLeaf(child)) + .isNotEmpty) { + // Skip + } else if (node is Leaf && skipLeaf(node)) { + // Skip + } else { + result.add(node); + } + } + if (result.isEmpty) return nodes; + return result; + } + + @override + String toString() => "${nodes.join(" | ")}"; + + @override + void accept(Visitor visitor) { + visitor.visitOrNode(this); + } + + @override + void visitChildren(Visitor visitor) { + for (Node node in nodes) { + node.accept(visitor); + } + } +} + +class RangeNode extends Node { + final Literal a; + final Literal b; + + RangeNode(this.a, this.b); + + @override + String toString() => "$a .. $b"; + + @override + void accept(Visitor visitor) { + visitor.visitRangeNode(this); + } + + @override + void visitChildren(Visitor visitor) { + a.accept(visitor); + b.accept(visitor); + } +} + +class Optional extends Node { + final Node a; + + Optional(this.a); + + @override + String toString() => "($a)?"; + + @override + void accept(Visitor visitor) { + visitor.visitOptional(this); + } + + @override + void visitChildren(Visitor visitor) { + a.accept(visitor); + } +} + +class OneOrMore extends Node { + final Node a; + + OneOrMore(this.a); + + @override + String toString() => "($a)+"; + + @override + void accept(Visitor visitor) { + visitor.visitOneOrMore(this); + } + + @override + void visitChildren(Visitor visitor) { + a.accept(visitor); + } +} + +class ZeroOrMore extends Node { + final Node a; + + ZeroOrMore(this.a); + + @override + String toString() => "($a)*"; + + @override + void accept(Visitor visitor) { + visitor.visitZeroOrMore(this); + } + + @override + void visitChildren(Visitor visitor) { + a.accept(visitor); + } +} + +class TildeNode extends Node { + final Node a; + + TildeNode(this.a); + + @override + String toString() => "~($a)"; + + @override + void accept(Visitor visitor) { + visitor.visitTildeNode(this); + } + + @override + void visitChildren(Visitor visitor) { + a.accept(visitor); + } +} + +class Literal extends Node { + String content; + + Literal(this.content); + + @override + String toString() => "'${content}'"; + + @override + void accept(Visitor visitor) { + visitor.visitLiteral(this); + } + + @override + void visitChildren(Visitor visitor) {} +} + +abstract class Visitor { + void defaultNode(Node node) { + node.visitChildren(this); + } + + void visitDummyNode(DummyNode node) => defaultNode(node); + void visitEmpty(Empty node) => defaultNode(node); + void visitLeaf(Leaf node) => defaultNode(node); + void visitAnyCharNode(AnyCharNode node) => defaultNode(node); + void visitEndOfFileNode(EndOfFileNode node) => defaultNode(node); + void visitSequence(Sequence node) => defaultNode(node); + void visitOrNode(OrNode node) => defaultNode(node); + void visitRangeNode(RangeNode node) => defaultNode(node); + void visitOptional(Optional node) => defaultNode(node); + void visitOneOrMore(OneOrMore node) => defaultNode(node); + void visitZeroOrMore(ZeroOrMore node) => defaultNode(node); + void visitTildeNode(TildeNode node) => defaultNode(node); + void visitLiteral(Literal node) => defaultNode(node); +} diff --git a/pkg/front_end/tool/fuzz/minimizer.dart b/pkg/front_end/tool/fuzz/minimizer.dart new file mode 100644 index 000000000000..42f33de7ec87 --- /dev/null +++ b/pkg/front_end/tool/fuzz/minimizer.dart @@ -0,0 +1,244 @@ +// Copyright (c) 2025, 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:io'; +import 'dart:typed_data'; + +import 'package:_fe_analyzer_shared/src/scanner/token.dart'; + +import 'package:front_end/src/util/parser_ast.dart'; +import 'package:front_end/src/util/parser_ast_helper.dart'; + +import '../interval_list.dart'; +import 'compile_helper.dart'; +import 'stacktrace_utils.dart'; + +const bool debug = false; + +Future main(List args) async { + if (args.length != 1) throw "Needs exactly 1 input."; + + Helper helper = new Helper(); + await helper.setup(); + + Uint8List rawBytes = new File(args.single).readAsBytesSync(); + String sourceAsString = utf8.decode(rawBytes); + + (Object, StackTrace)? compile = await helper.compile(sourceAsString); + if (compile == null) throw "Input doesn't crash."; + String categorized = categorize(compile.$2); + + CompilationUnitEnd ast = getAST( + rawBytes, + includeComments: true, + enableExtensionMethods: true, + enableNonNullable: true, + enableTripleShift: true, + allowPatterns: true, + ); + ast.debugPrint(); + + Visitor v = new Visitor(helper, categorized, sourceAsString); + Token firstToken = (ast.children!.first as CompilationUnitBegin).token; + await v.walkTokens(firstToken); + await ast.accept(v); + + String? minimized = v.getMinimizedSource(); + if (minimized == null) { + print("Couldn't minimize."); + } else { + print("Minimized to: "); + print(minimized); + } +} + +class Visitor extends RecursiveParserAstVisitorWithDefaultNodeAsync { + final Helper helper; + final String wantedCategorizedCrash; + final String initialSource; + List<(int, int)> removeable = []; + + Visitor(this.helper, this.wantedCategorizedCrash, this.initialSource) { + prevBraceInfo = initialSource.getBraceCountString(); + print("Initial source brace info: $prevBraceInfo"); + } + + Future walkTokens(Token token) async { + Token t = token; + while (!t.isEof) { + if (t.endGroup != null) { + if (await _canRemove("Token range", t.runtimeType, t, t.endGroup!)) { + t = t.endGroup!.next!; + continue; + } + } + t = t.next!; + } + } + + @override + Future defaultNode(ParserAstNode node) async { + if (node is BeginAndEndTokenParserAstNode) { + if (await _canRemove( + node.what, node.runtimeType, node.beginToken, node.endToken)) { + return; + } + } else if (node is BinaryExpressionEnd) { + if (await _canRemove( + node.what, node.runtimeType, node.token, node.endToken)) { + return; + } + } else if (node is ThrowExpressionHandle) { + if (await _canRemove( + node.what, node.runtimeType, node.throwToken, node.endToken)) { + return; + } + } else if (node is LabelHandle) { + if (await _canRemove( + node.what, node.runtimeType, node.token.previous!, node.token)) { + return; + } + } else if (node is CaseExpressionEnd) { + if (await _canRemove( + node.what, node.runtimeType, node.caseKeyword, node.colon)) { + return; + } + } else if (node is UnaryPrefixAssignmentExpressionHandle) { + if (await _canRemove( + node.what, node.runtimeType, node.token, node.token)) { + return; + } + } else if (node is UnaryPrefixExpressionHandle) { + if (await _canRemove( + node.what, node.runtimeType, node.token, node.token)) { + return; + } + } else if (node is EmptyStatementHandle) { + if (await _canRemove( + node.what, node.runtimeType, node.token, node.token)) { + return; + } + } else if (node is AssignmentExpressionHandle) { + if (await _canRemove( + node.what, node.runtimeType, node.token, node.endToken)) { + return; + } + } else if (node is ConditionalExpressionEnd) { + if (await _canRemove(node.what, node.runtimeType, node.question.next!, + node.colon.previous!)) { + // Fine, but we still have to recurse. + } + if (await _canRemove( + node.what, node.runtimeType, node.colon.next!, node.endToken)) { + // Fine, but we still have to recurse. + } + } else if (node is BinaryPatternEnd) { + // This seems a little dicy. + if (await _canRemove( + node.what, node.runtimeType, node.token, node.token)) { + return; + } + } else if (node is PartEnd) { + if (await _canRemove( + node.what, node.runtimeType, node.partKeyword, node.semicolon)) { + return; + } + } else if (node is TypedefEnd) { + if (await _canRemove(node.what, node.runtimeType, + node.augmentToken ?? node.typedefKeyword, node.endToken)) { + return; + } + } + // We can't remove the node (or it isn't a Begin/End one) - recurse. + await super.defaultNode(node); + } + + Future _canRemove( + String what, Type type, Token beginToken, Token endToken) async { + int length = endToken.charEnd - beginToken.offset; + if (length > 0) { + String newSource = + _constructSourceFromRemovableAnd((type, beginToken, endToken)); + print("Compile (${newSource.length} vs ${initialSource.length})"); + (Object, StackTrace)? compile = await helper.compile(newSource); + if (compile != null) { + String categorized = categorize(compile.$2); + if (categorized == wantedCategorizedCrash) { + print(" => Can remove $what with length $length"); + // We can actually do this. + removeable.add((beginToken.charOffset, endToken.charEnd)); + return true; + } + } + } + return false; + } + + String _constructSourceFromRemovableAnd( + (Type type, Token beginToken, Token endToken)? nodeInfo) { + IntervalListBuilder intervalListBuilder = new IntervalListBuilder(); + if (nodeInfo != null) { + intervalListBuilder.addIntervalExcludingEnd( + nodeInfo.$2.charOffset, nodeInfo.$3.charEnd); + } + for ((int, int) removeThis in removeable) { + intervalListBuilder.addIntervalExcludingEnd(removeThis.$1, removeThis.$2); + } + IntervalList intervalList = intervalListBuilder.buildIntervalList(); + int from = 0; + StringBuffer sb = new StringBuffer(); + intervalList.forEach((int open, int close) { + sb.write(initialSource.substring(from, open)); + from = close; + }); + sb.write(initialSource.substring(from, initialSource.length)); + String output = sb.toString(); + if (debug) { + String braceInfo = output.getBraceCountString(); + if (braceInfo != prevBraceInfo) { + print("After cutting out ${nodeInfo?.$1} at " + "${nodeInfo?.$2.charOffset} brace info: " + "$braceInfo"); + prevBraceInfo = braceInfo; + } + } + return output; + } + + String? prevBraceInfo; + + String? getMinimizedSource() { + if (removeable.isEmpty) return null; + return _constructSourceFromRemovableAnd(null); + } +} + +extension on String { + List count() { + List result = List.filled(257, 0); + List codeUnits = this.codeUnits; + for (int i = 0; i < codeUnits.length; i++) { + int unit = codeUnits[i]; + if (unit < 0 || unit > 255) { + result[256]++; + } else { + result[unit]++; + } + } + return result; + } + + String getBraceCountString() { + List counts = count(); + StringBuffer sb = new StringBuffer(); + for (int codeUnit in [40, 41, 91, 93, 123, 125]) { + if (sb.isNotEmpty) sb.write(", "); + sb.writeCharCode(codeUnit); + sb.write(": "); + sb.write(counts[codeUnit]); + } + return sb.toString(); + } +} diff --git a/pkg/front_end/tool/fuzz/post_fix_checker.dart b/pkg/front_end/tool/fuzz/post_fix_checker.dart new file mode 100644 index 000000000000..2a7399d94a59 --- /dev/null +++ b/pkg/front_end/tool/fuzz/post_fix_checker.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2025, 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:io' show Directory, File, FileSystemEntity; + +import '../../test/utils/io_utils.dart' show computeRepoDirUri; + +import 'compile_helper.dart'; +import 'stacktrace_utils.dart'; + +final Uri repoDir = computeRepoDirUri(); + +Future main(List args) async { + Directory d = new Directory(args.single); + Helper helper = new Helper(); + await helper.setup(); + int good = 0; + int bad = 0; + for (FileSystemEntity file in d.listSync(recursive: false)) { + if (file is! File) continue; + (Object, StackTrace)? result = + await helper.compile(file.readAsStringSync()); + String filename = file.uri.pathSegments.last; + if (result == null) { + print("${filename}: OK"); + good++; + } else { + print("${filename}: Still crashes: ${categorize(result.$2)}"); + bad++; + } + } + print("Done. $good good, $bad bad."); +} diff --git a/pkg/front_end/tool/fuzz/reorganizer.dart b/pkg/front_end/tool/fuzz/reorganizer.dart new file mode 100644 index 000000000000..944826a0228d --- /dev/null +++ b/pkg/front_end/tool/fuzz/reorganizer.dart @@ -0,0 +1,62 @@ +// Copyright (c) 2025, 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:io' show Directory, File, FileSystemEntity; + +import '../../test/utils/io_utils.dart' show computeRepoDirUri; + +import 'compile_helper.dart'; +import 'stacktrace_utils.dart'; + +final Uri repoDir = computeRepoDirUri(); + +Future main(List args) async { + List directories = []; + Directory? outputDirectory; + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.startsWith("--output=")) { + outputDirectory = + new Directory(arg.substring("--output=".length)).absolute; + } else { + Directory d = new Directory(arg); + if (d.existsSync()) { + directories.add(d); + } else { + print("Error: $arg isn't a directory."); + } + } + } + if (outputDirectory == null) throw "No --output= given"; + + Helper helper = new Helper(); + await helper.setup(); + int good = 0; + int bad = 0; + + for (int i = 0; i < directories.length; i++) { + Directory d = directories[i]; + for (FileSystemEntity file in d.listSync(recursive: true)) { + if (file is! File) continue; + String content = file.readAsStringSync(); + (Object, StackTrace)? result = await helper.compile(content); + String filename = file.uri.pathSegments.last; + if (result == null) { + print("${filename}: OK"); + good++; + } else { + String category = categorize(result.$2); + print("${filename}: Still crashes: $category"); + bad++; + Directory d = + new Directory.fromUri(outputDirectory.uri.resolve("$category/")); + d.createSync(recursive: true); + File f = new File.fromUri( + outputDirectory.uri.resolve("$category/$bad.input")); + f.writeAsStringSync(content); + } + } + } + print("Done. $good good, $bad bad."); +} diff --git a/pkg/front_end/tool/fuzz/stacktrace_utils.dart b/pkg/front_end/tool/fuzz/stacktrace_utils.dart new file mode 100644 index 000000000000..a3da0fcd4532 --- /dev/null +++ b/pkg/front_end/tool/fuzz/stacktrace_utils.dart @@ -0,0 +1,76 @@ +// Copyright (c) 2025, 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 'package:_fe_analyzer_shared/src/scanner/characters.dart'; + +String categorize(StackTrace st) { + List lines = parseStackTrace(st); + List notSdk = + lines.where((l) => l.uri.scheme != "dart").toList(); + return "${notSdk.first.uri.pathSegments.last}/${notSdk.first.line}"; +} + +List parseStackTrace(StackTrace st) { + List result = []; + List lines = st.toString().split("\n"); + for (int i = 0; i < lines.length; i++) { + String s = lines[i]; + if (s == "") continue; + if (s == "...") continue; + if (s == "") continue; + if (s.startsWith("#")) { + int j = 1; + for (; j < s.length; j++) { + int c = s.codeUnitAt(j); + if (c >= $0 && c <= $9) { + // #numGoesHere + } else { + break; + } + } + s = s.substring(j).trim(); + int indexOfParen = s.indexOf("("); + String method = s.substring(0, indexOfParen).trim(); + if (s[s.length - 1] != ")") throw "s"; + String uriEtc = s.substring(indexOfParen + 1, s.length - 1); + List colonSplit = uriEtc.split(":"); + int column = -1; + int line = -1; + // If last two are numbers it's line and column --- the rest is uri; + // if last is number it's line --- the rest is uri + // else its all uri. + if (uriEtc.length >= 2) { + column = int.tryParse(colonSplit[colonSplit.length - 1]) ?? -1; + line = int.tryParse(colonSplit[colonSplit.length - 2]) ?? -1; + } + String uriPart; + if (line > -1 && column > -1) { + uriPart = colonSplit.take(colonSplit.length - 2).join(":"); + } else if (column > -1) { + line = column; + column = -1; + uriPart = colonSplit.take(colonSplit.length - 1).join(":"); + } else { + uriPart = uriEtc; + } + StackTraceLine stLine = new StackTraceLine( + method, Uri.parse(uriPart), line, column, lines[i]); + result.add(stLine); + } else { + throw "Unexpected line: '$s' in stacktrace: $st"; + } + } + + return result; +} + +class StackTraceLine { + final String method; + final Uri uri; + final int line; + final int column; + final String orgLine; + + StackTraceLine(this.method, this.uri, this.line, this.column, this.orgLine); +} diff --git a/pkg/front_end/tool/interval_list.dart b/pkg/front_end/tool/interval_list.dart index 65c659ab120b..476b97be5a34 100644 --- a/pkg/front_end/tool/interval_list.dart +++ b/pkg/front_end/tool/interval_list.dart @@ -102,4 +102,10 @@ class IntervalList { } return low == high && (low & 1) == 0; } + + void forEach(void action(int open, int close)) { + for (int i = 0; i < _intervalList.length; i += 2) { + action(_intervalList[i], _intervalList[i + 1]); + } + } } diff --git a/pkg/front_end/tool/parser_ast_helper_creator.dart b/pkg/front_end/tool/parser_ast_helper_creator.dart index f34871efe3f5..a58ccdca3dc3 100644 --- a/pkg/front_end/tool/parser_ast_helper_creator.dart +++ b/pkg/front_end/tool/parser_ast_helper_creator.dart @@ -77,6 +77,40 @@ abstract class ParserAstNode { } } + + + void debugPrint() { + StringBuffer sb = new StringBuffer(); + _debugPrintImpl(0, sb); + print(sb.toString()); + } + + void _debugPrintImpl(int indentation, StringBuffer sb) { + sb.write(" " * indentation); + sb.write(what); + sb.write(type.name); + Token? tokenWithSmallestOffset; + for (Object? value in deprecatedArguments.values) { + if (value is Token) { + if (tokenWithSmallestOffset == null || + value.charOffset < tokenWithSmallestOffset.charOffset) { + tokenWithSmallestOffset = value; + } + } + } + if (tokenWithSmallestOffset != null) { + sb.write( + " (${tokenWithSmallestOffset.lexeme} @ " + "${tokenWithSmallestOffset.charOffset})"); + } + sb.writeln(); + List? children = this.children; + if (children == null) return; + for (ParserAstNode child in children) { + child._debugPrintImpl(indentation + 2, sb); + } + } + // TODO(jensj): Compare two ASTs. } @@ -116,6 +150,22 @@ abstract class AbstractParserAstListener implements Listener { } out.write(r"}"); + out.write("class RecursiveParserAstVisitorWithDefaultNodeAsync " + "implements ParserAstVisitor> {"); + out.write("Future defaultNode(ParserAstNode node) async {"); + out.write(" List? children = node.children;"); + out.write(" if (children == null) return;"); + out.write(" for (ParserAstNode child in children) {"); + out.write(" await child.accept(this);"); + out.write(" }"); + out.write("}"); + + for (String name in listener.visitNames) { + out.write(" @override\n"); + out.write(" Future $name => defaultNode(node);\n\n"); + } + out.write(r"}"); + return new DartFormatter( languageVersion: DartFormatter.latestShortStyleLanguageVersion) .format("$out");