diff --git a/eng/Subsets.props b/eng/Subsets.props index 64079deecc48cb..2a0aee52b8e152 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -254,6 +254,7 @@ + @@ -510,7 +511,7 @@ - + + + + + + diff --git a/eng/Versions.props b/eng/Versions.props index ad0283439a0745..93a6e474f80463 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -121,6 +121,8 @@ 17.11.48 7.0.412701 6.0 + 4.6.6 + 4.6.6 1.6.0 17.4.0-preview-20220707-01 diff --git a/eng/pipelines/coreclr/ilasm.yml b/eng/pipelines/coreclr/ilasm.yml index 47477272843934..dc375ecff9b0c4 100644 --- a/eng/pipelines/coreclr/ilasm.yml +++ b/eng/pipelines/coreclr/ilasm.yml @@ -8,6 +8,7 @@ pr: include: - src/coreclr/ilasm/* - src/coreclr/ildasm/* + - src/tools/ilasm/* schedules: - cron: "0 19 * * 6" @@ -32,3 +33,20 @@ extends: - windows_x86 - windows_arm64 testGroup: ilasm + + stages: + - stage: Managed_ILAsm + displayName: Managed IL Assembler Tests + dependsOn: [] + jobs: + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + buildConfig: Debug + platforms: + - windows_x64 + jobParameters: + nameSuffix: ILAsm + buildArgs: -s tools.ilasm -c $(_BuildConfig) -test + timeoutInMinutes: 120 + condition: and(succeeded(), ne(variables['Build.Reason'], 'Schedule')) diff --git a/eng/pipelines/coreclr/templates/jit-outerloop-pipeline.yml b/eng/pipelines/coreclr/templates/jit-outerloop-pipeline.yml index 1e9c70114c5614..41bea22cbb7f11 100644 --- a/eng/pipelines/coreclr/templates/jit-outerloop-pipeline.yml +++ b/eng/pipelines/coreclr/templates/jit-outerloop-pipeline.yml @@ -4,6 +4,9 @@ parameters: - name: testGroup type: string default: outerloop + - name: stages + type: stageList + default: [] extends: template: /eng/pipelines/common/templates/pipeline-with-resources.yml @@ -55,3 +58,5 @@ extends: testGroup: ${{ parameters.testGroup }} liveLibrariesBuildConfig: Release unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig) + - ${{ each stage in parameters.stages }}: + - ${{ stage }} diff --git a/src/tools/ilasm/ilasm.slnx b/src/tools/ilasm/ilasm.slnx new file mode 100644 index 00000000000000..38a4c5d535964e --- /dev/null +++ b/src/tools/ilasm/ilasm.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/src/tools/ilasm/src/ILAssembler/BlobBuilderExtensions.cs b/src/tools/ilasm/src/ILAssembler/BlobBuilderExtensions.cs new file mode 100644 index 00000000000000..6519c50ab2dabd --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/BlobBuilderExtensions.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Text; + +namespace ILAssembler +{ + internal static class BlobBuilderExtensions + { + public static BlobBuilder SerializeSequence(this ImmutableArray sequence) + { + BlobBuilder builder = new BlobBuilder(); + builder.WriteSerializedSequence(sequence); + return builder; + } + + public static void WriteSerializedSequence(this BlobBuilder writer, ImmutableArray sequence) + { + foreach (T value in sequence) + { + WriteSerializedValue(writer, value); + } + } + + public static void WriteSerializedValue(this BlobBuilder writer, T value) + { + if (typeof(T) == typeof(bool)) + { + writer.WriteBoolean((bool)(object)value!); + } + else if (typeof(T) == typeof(int)) + { + writer.WriteInt32((int)(object)value!); + } + else if (typeof(T) == typeof(byte)) + { + writer.WriteByte((byte)(object)value!); + } + else if (typeof(T) == typeof(char)) + { + writer.WriteUInt16((char)(object)value!); + } + else if (typeof(T) == typeof(double)) + { + writer.WriteDouble((double)(object)value!); + } + else if (typeof(T) == typeof(short)) + { + writer.WriteInt16((short)(object)value!); + } + else if (typeof(T) == typeof(long)) + { + writer.WriteInt64((long)(object)value!); + } + else if (typeof(T) == typeof(sbyte)) + { + writer.WriteSByte((sbyte)(object)value!); + } + else if (typeof(T) == typeof(float)) + { + writer.WriteSingle((float)(object)value!); + } + else if (typeof(T) == typeof(ushort)) + { + writer.WriteUInt16((ushort)(object)value!); + } + else if (typeof(T) == typeof(uint)) + { + writer.WriteUInt32((uint)(object)value!); + } + else if (typeof(T) == typeof(ulong)) + { + writer.WriteUInt64((ulong)(object)value!); + } + else if (typeof(T) == typeof(string)) + { + writer.WriteSerializedString((string?)(object?)value); + } + } + + public static void WriteTypeEntity(this BlobBuilder builder, EntityRegistry.TypeEntity entity) + { + if (entity is EntityRegistry.FakeTypeEntity fakeEntity) + { + builder.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(fakeEntity.TypeSignatureHandle)); + } + else + { + builder.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(entity.Handle)); + } + } + } +} diff --git a/src/tools/ilasm/src/ILAssembler/CIL.g4 b/src/tools/ilasm/src/ILAssembler/CIL.g4 new file mode 100644 index 00000000000000..0ed37a1baee599 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/CIL.g4 @@ -0,0 +1,1152 @@ +/* +Licensed to the .NET Foundation under one or more agreements. +The .NET Foundation licenses this file to you under the MIT license. +*/ + +grammar CIL; + +import Instructions; + +tokens { IncludedFileEof, SyntheticIncludedFileEof } + +INT32: '-'? ('0x' [0-9A-Fa-f]+ | [0-9]+); +INT64: '-'? ('0x' [0-9A-Fa-f]+ | [0-9]+); +FLOAT64: '-'? [0-9]+ ('.' [0-9]+ | [eE] '-'? [0-9]+); +HEXBYTE: [0-9A-Fa-f][0-9A-Fa-f]; +DCOLON: '::'; +ELLIPSIS: '..'; +NULL: 'null'; +NULLREF: 'nullref'; +HASH: '.hash'; +CHAR: 'char'; +STRING: 'string'; +BOOL: 'bool'; +INT8: 'int8'; +INT16: 'int16'; +INT32_: 'int32'; +INT64_: 'int64'; +FLOAT32: 'float32'; +FLOAT64_: 'float64'; +fragment UNSIGNED: 'unsigned'; +UINT8: 'uint8' | (UNSIGNED INT8); +UINT16: 'uint16' | (UNSIGNED INT16); +UINT32: 'uint32' | (UNSIGNED INT32_); +UINT64: 'uint64' | (UNSIGNED INT64_); +INT: 'int'; +UINT: 'uint' | (UNSIGNED 'int'); +TYPE: 'type'; +OBJECT: 'object'; +MODULE: '.module'; +VALUE: 'value'; +VALUETYPE: 'valuetype'; +VOID: 'void'; +ENUM: 'enum'; +CUSTOM: 'custom'; +FIXED: 'fixed'; +SYSSTRING: 'systring'; +ARRAY: 'array'; +VARIANT: 'variant'; +CURRENCY: 'currency'; +SYSCHAR: 'syschar'; +ERROR: 'error'; +DECIMAL: 'decimal'; +DATE: 'date'; +BSTR: 'bstr'; +LPSTR: 'lpstr'; +LPWSTR: 'lpwstr'; +LPTSTR: 'lptstr'; +OBJECTREF: 'objectref'; +IUNKNOWN: 'iunknown'; +IDISPATCH: 'idispatch'; +STRUCT: 'struct'; +INTERFACE: 'interface'; +SAFEARRAY: 'safearray'; +NESTEDSTRUCT: 'nested' STRUCT; +VARIANTBOOL: VARIANT BOOL; +BYVALSTR: 'byvalstr'; +ANSI: 'ansi'; +ANSIBSTR: ANSI BSTR; +TBSTR: 'tbstr'; +METHOD: 'method'; +ANY: 'any'; +LPSTRUCT: 'lpstruct'; +VECTOR: 'vector'; +HRESULT: 'hresult'; +CARRAY: 'carray'; +USERDEFINED: 'userdefined'; +RECORD: 'record'; +FILETIME: 'filetime'; +BLOB: 'blob'; +STREAM: 'stream'; +STORAGE: 'storage'; +STREAMED_OBJECT: 'streamed_object'; +STORED_OBJECT: 'stored_object'; +BLOB_OBJECT: 'blob_object'; +CF: 'cf'; +CLSID: 'clsid'; +INSTANCE: 'instance'; +EXPLICIT: 'explicit'; +DEFAULT: 'default'; +VARARG: 'vararg'; +UNMANAGED: 'unmanaged'; +CDECL: 'cdecl'; +STDCALL: 'stdcall'; +THISCALL: 'thiscall'; +FASTCALL: 'fastcall'; +TYPE_PARAMETER: '!'; +METHOD_TYPE_PARAMETER: '!' '!'; +TYPEDREF: 'typedref'; +NATIVE_INT: 'native' 'int'; +NATIVE_UINT: ('native' 'unsigned' 'int') | ('native' 'uint'); +PARAM: '.param'; +CONSTRAINT: 'constraint'; + +THIS: '.this'; +BASE: '.base'; +NESTER: '.nester'; +REF: '&'; +ARRAY_TYPE_NO_BOUNDS: '[' ']'; +PTR: '*'; + +QSTRING: '"' (~('"' | '\\') | '\\' ('"' | '\\'))* '"'; +SQSTRING: '\'' (~('\'' | '\\') | '\\' ('\'' | '\\'))* '\''; +DOT: '.'; +PLUS: '+'; + +PP_DEFINE: '#define'; +PP_UNDEF: '#undef'; +PP_IFDEF: '#ifdef'; +PP_IFNDEF: '#ifndef'; +PP_ELSE: '#else'; +PP_ENDIF: '#endif'; +PP_INCLUDE: '#include'; +MRESOURCE: '.mresource'; + +// ID needs to be last to ensure it doesn't take priority over other token types +fragment IDSTART: [A-Za-z_#$@]; +fragment IDCONT: [A-Za-z0-9_#?$@`]; +DOTTEDNAME: (ID DOT)+ ID; +ID: IDSTART IDCONT*; + +id: + ID + | 'native' + | 'cil' + | 'optil' + | 'managed' + | 'unmanaged' + | 'forwardref' + | 'preservesig' + | 'runtime' + | 'internalcall' + | 'synchronized' + | 'noinlining' + | 'aggressiveinlining' + | 'nooptimization' + | 'aggressiveoptimization' + | 'async' + | 'extended' + | SQSTRING; +dottedName: DOTTEDNAME | ((ID '.')* ID); +compQstring: (QSTRING PLUS)* QSTRING; + + +WS: [ \t\r\n] -> skip; +SINGLE_LINE_COMMENT: '//' ~[\r\n]* -> skip; +COMMENT: '/*' .*? '*/' -> skip; + +decls: decl+; + +decl: + classHead '{' classDecls '}' + | nameSpaceHead '{' decls '}' + | methodHead '{' methodDecls '}' + | fieldDecl + | dataDecl + | vtableDecl + | vtfixupDecl + | extSourceSpec + | fileDecl + | assemblyBlock + | assemblyRefHead '{' assemblyRefDecls '}' + | exptypeHead '{' exptypeDecls '}' + | manifestResHead '{' manifestResDecls '}' + | moduleHead + | secDecl + | customAttrDecl + | subsystem + | corflags + | alignment + | imagebase + | stackreserve + | languageDecl + | typedefDecl + | compControl + | typelist + | mscorlib; + +subsystem: '.subsystem' int32; + +corflags: '.corflags' int32; + +alignment: '.file' 'alignment' int32; + +imagebase: '.imagebase' int64; + +stackreserve: '.stackreserve' int64; + +assemblyBlock: + '.assembly' asmAttr dottedName '{' assemblyDecls '}'; + +mscorlib: '.mscorlib'; + +languageDecl: + '.language' SQSTRING + | '.language' SQSTRING ',' SQSTRING + | '.language' SQSTRING ',' SQSTRING ',' SQSTRING; + +typelist: '.typelist' '{' (className)* '}'; + +int32: INT32; +int64: INT64 | INT32; + +float64: + FLOAT64 + | FLOAT32 '(' int32 ')' + | FLOAT64_ '(' int64 ')'; + +intOrWildcard: int32 | PTR; + +/* This is handled in the PreprocessedTokenSource lexer. We have this in the grammar just for completeness */ +compControl: + PP_DEFINE ID + | PP_DEFINE ID QSTRING + | PP_UNDEF ID + | PP_IFDEF ID + | PP_IFNDEF ID + | PP_ELSE + | PP_ENDIF + | PP_INCLUDE QSTRING + | ';'; + + +/* Aliasing of types, type specs, methods, fields and custom attributes */ +typedefDecl: + '.typedef' type 'as' dottedName + | '.typedef' className 'as' dottedName + | '.typedef' memberRef 'as' dottedName + | '.typedef' customDescr 'as' dottedName + | '.typedef' customDescrWithOwner 'as' dottedName; + +/* Custom attribute declarations */ +customDescr: + '.custom' customType + | '.custom' customType '=' compQstring + | '.custom' customType '=' '{' customBlobDescr '}' + | '.custom' customType '=' '(' bytes ')'; + +customDescrWithOwner: + '.custom' '(' ownerType ')' customType + | '.custom' '(' ownerType ')' customType '=' compQstring + | '.custom' '(' ownerType ')' customType '=' '{' customBlobDescr '}' + | '.custom' '(' ownerType ')' customType '=' '(' bytes ')'; + +customType: methodRef; + +ownerType: typeSpec | memberRef; + +/* Verbal description of custom attribute initialization blob */ +customBlobDescr: customBlobArgs customBlobNVPairs; + +customBlobArgs: (serInit | compControl)*; + +customBlobNVPairs: ( + fieldOrProp serializType dottedName '=' serInit + | compControl + )*; + +fieldOrProp: 'field' | 'property'; + +serializType: serializTypeElement (ARRAY_TYPE_NO_BOUNDS)?; + +serializTypeElement: + simpleType + | dottedName /* typedef */ + | TYPE + | OBJECT + | ENUM 'class' SQSTRING + | ENUM className; + +/* Module declaration */ +moduleHead: + MODULE + | MODULE dottedName + | MODULE 'extern' dottedName; + +/* VTable Fixup table declaration */ +vtfixupDecl: '.vtfixup' '[' int32 ']' vtfixupAttr 'at' id; + +vtfixupAttr: + /* EMPTY */ + | vtfixupAttr INT32_ + | vtfixupAttr INT64_ + | vtfixupAttr 'fromunmanaged' + | vtfixupAttr 'callmostderived' + | vtfixupAttr 'retainappdomain'; + +vtableDecl: '.vtable' '=' '(' bytes ')' /* deprecated */; + +/* Namespace and class declaration */ +nameSpaceHead: '.namespace' dottedName; + +classHead: + '.class' classAttr* dottedName typarsClause extendsClause implClause; + + +classAttr: + 'public' + | 'private' + | VALUE + | ENUM + | 'interface' + | 'sealed' + | 'abstract' + | 'auto' + | 'sequential' + | 'explicit' + | 'extended' + | ANSI + | 'unicode' + | 'autochar' + | 'import' + | 'serializable' + | 'windowsruntime' + | 'nested' 'public' + | 'nested' 'private' + | 'nested' 'family' + | 'nested' 'assembly' + | 'nested' 'famandassem' + | 'nested' 'famorassem' + | 'beforefieldinit' + | 'specialname' + | 'rtspecialname' + | 'flags' '(' int32 ')'; + +extendsClause: /* EMPTY */ | 'extends' typeSpec; + +implClause: /* EMPTY */ | 'implements' implList; + +classDecls: classDecl*; + +implList: (typeSpec ',')* typeSpec; + +/* External source declarations */ +esHead: '.line' | '#line'; + +extSourceSpec: + esHead int32 SQSTRING + | esHead int32 + | esHead int32 ':' int32 SQSTRING + | esHead int32 ':' int32 + | esHead int32 ':' int32 ',' int32 SQSTRING + | esHead int32 ':' int32 ',' int32 + | esHead int32 ',' int32 ':' int32 SQSTRING + | esHead int32 ',' int32 ':' int32 + | esHead int32 ',' int32 ':' int32 ',' int32 SQSTRING + | esHead int32 ',' int32 ':' int32 ',' int32 + | esHead int32 QSTRING; + +/* Manifest declarations */ +fileDecl: + '.file' fileAttr* dottedName fileEntry HASH '=' '(' bytes ')' fileEntry + | '.file' fileAttr* dottedName fileEntry; + +fileAttr: 'nometadata'; + +fileEntry: /* EMPTY */ | '.entrypoint'; + +asmAttrAny: + 'retargetable' + | 'windowsruntime' + | 'noplatform' + | 'legacy library' + | 'cil' + | 'x86' + | 'amd64' + | 'arm' + | 'arm64'; + +asmAttr: asmAttrAny*; + +/* IL instructions and associated definitions */ +instr_none: INSTR_NONE; + +instr_var: INSTR_VAR; + +instr_i: INSTR_I; + +instr_i8: INSTR_I8; + +instr_r: INSTR_R; + +instr_brtarget: INSTR_BRTARGET; + +instr_method: INSTR_METHOD; + +instr_field: INSTR_FIELD; + +instr_type: INSTR_TYPE; + +instr_string: INSTR_STRING; + +instr_sig: INSTR_SIG; + +instr_tok: INSTR_TOK; + +instr_switch: INSTR_SWITCH; + +instr: + instr_none + | instr_var int32 + | instr_var id + | instr_i int32 + | instr_i8 int64 + | instr_r float64 + | instr_r int64 + | instr_r '(' bytes ')' + | instr_brtarget int32 + | instr_brtarget id + | instr_method methodRef + | instr_field fieldRef + | instr_field mdtoken + | instr_type typeSpec + | instr_string compQstring + | instr_string ANSI '(' compQstring ')' + | instr_string 'bytearray' '(' bytes ')' + | instr_sig callConv type sigArgs + | instr_tok ownerType /* ownerType ::= memberRef | typeSpec */ + | instr_switch '(' labels ')'; + +labels: + /* empty */ + | (id | int32 ',')* (id | int32); + +typeArgs: '<' (type ',')* type '>'; + +bounds: '[' (bound ',')* bound ']'; + +sigArgs: '(' (sigArg ',')* sigArg ')' | '()'; + +sigArg: + ELLIPSIS + | paramAttr type marshalClause + | paramAttr type marshalClause id; + +/* Class referencing */ + +className: + '[' dottedName ']' slashedName + | '[' mdtoken ']' slashedName + | '[' PTR ']' slashedName + | '[' MODULE dottedName ']' slashedName + | slashedName + | mdtoken + | THIS + | BASE + | NESTER; + +slashedName: (dottedName '/')* dottedName; + +assemblyDecls: assemblyDecl*; + +assemblyDecl: (HASH 'algorithm' int32) | secDecl | asmOrRefDecl; + +typeSpec: + className + | '[' dottedName ']' + | '[' MODULE dottedName ']' + | type; + +/* Native types for marshaling signatures */ +nativeType: + /* EMPTY */ + | nativeTypeElement nativeTypeArrayPointerInfo*; + +nativeTypeArrayPointerInfo: + PTR # PointerNativeType + | ARRAY_TYPE_NO_BOUNDS # PointerArrayTypeNoSizeData + | '[' int32 ']' # PointerArrayTypeSize + | '[' int32 PLUS int32 ']' # PointerArrayTypeSizeParamIndex + | '[' PLUS int32 ']' # PointerArrayTypeParamIndex + ; + +nativeTypeElement: + /* EMPTY */ + | marshalType=CUSTOM '(' compQstring ',' compQstring ',' compQstring ',' compQstring ')' + | marshalType=CUSTOM '(' compQstring ',' compQstring ')' + | FIXED marshalType=SYSSTRING '[' int32 ']' + | FIXED marshalType=ARRAY '[' int32 ']' nativeType + | marshalType=VARIANT + | marshalType=CURRENCY + | marshalType=SYSCHAR + | marshalType=VOID + | marshalType=BOOL + | marshalType=INT8 + | marshalType=INT16 + | marshalType=INT32_ + | marshalType=INT64_ + | marshalType=FLOAT32 + | marshalType=FLOAT64_ + | marshalType=ERROR + | marshalType=UINT8 + | marshalType=UINT16 + | marshalType=UINT32 + | marshalType=UINT64 + | marshalType=DECIMAL + | marshalType=DATE + | marshalType=BSTR + | marshalType=LPSTR + | marshalType=LPWSTR + | marshalType=LPTSTR + | marshalType=OBJECTREF + | marshalType=IUNKNOWN iidParamIndex + | marshalType=IDISPATCH iidParamIndex + | marshalType=STRUCT + | marshalType=INTERFACE iidParamIndex + | marshalType=SAFEARRAY variantType + | marshalType=SAFEARRAY variantType ',' compQstring + | marshalType=INT + | marshalType=UINT + | marshalType=NESTEDSTRUCT + | marshalType=BYVALSTR + | marshalType=ANSIBSTR + | marshalType=TBSTR + | marshalType=VARIANTBOOL + | marshalType=METHOD + | marshalType=LPSTRUCT + | 'as' marshalType=ANY + | dottedName /* typedef */; + +iidParamIndex: /* EMPTY */ | '(' 'iidparam' '=' int32 ')'; + +variantType: + /*EMPTY */ + | variantTypeElement (ARRAY_TYPE_NO_BOUNDS | VECTOR | REF)*; + +variantTypeElement: + NULL + | VARIANT + | CURRENCY + | VOID + | BOOL + | INT8 + | INT16 + | INT32_ + | INT64_ + | FLOAT32 + | FLOAT64_ + | UINT8 + | UINT16 + | UINT32 + | UINT64 + | PTR + | DECIMAL + | DATE + | BSTR + | LPSTR + | LPWSTR + | IUNKNOWN + | IDISPATCH + | SAFEARRAY + | INT + | UINT + | ERROR + | HRESULT + | CARRAY + | USERDEFINED + | RECORD + | FILETIME + | BLOB + | STREAM + | STORAGE + | STREAMED_OBJECT + | STORED_OBJECT + | BLOB_OBJECT + | CF + | CLSID; + +/* Managed types for signatures */ +type: elementType typeModifiers*; + +typeModifiers: + '[' ']' # SZArrayModifier + | bounds # ArrayModifier + | REF # ByRefModifier + | PTR # PtrModifier + | 'pinned' # PinnedModifier + | 'modreq' '(' typeSpec ')' # RequiredModifier + | 'modopt' '(' typeSpec ')' # OptionalModifier + | typeArgs # GenericArgumentsModifier; + +elementType: + 'class' className + | OBJECT + | VALUE 'class' className + | VALUETYPE className + | 'method' callConv type PTR sigArgs + | METHOD_TYPE_PARAMETER int32 + | TYPE_PARAMETER int32 + | METHOD_TYPE_PARAMETER dottedName + | TYPE_PARAMETER dottedName + | TYPEDREF + | VOID + | NATIVE_INT + | NATIVE_UINT + | simpleType + | dottedName /* typedef */ + | ELLIPSIS type; + +simpleType: + CHAR + | STRING + | BOOL + | INT8 + | INT16 + | INT32_ + | INT64_ + | FLOAT32 + | FLOAT64_ + | UINT8 + | UINT16 + | UINT32 + | UINT64; + +bound: + | ELLIPSIS + | int32 + | int32 ELLIPSIS int32 + | int32 ELLIPSIS; + +/* Security declarations */ +PERMISSION: '.permission'; +PERMISSIONSET: '.permissionset'; + +secDecl: + PERMISSION secAction typeSpec '(' nameValPairs ')' + | PERMISSION secAction typeSpec '=' '{' customBlobDescr '}' + | PERMISSION secAction typeSpec + | PERMISSIONSET secAction '=' 'bytearray'? '(' bytes ')' + | PERMISSIONSET secAction compQstring + | PERMISSIONSET secAction '=' '{' secAttrSetBlob '}'; + +secAttrSetBlob: | (secAttrBlob ',')* secAttrBlob; + +secAttrBlob: + typeSpec '=' '{' customBlobNVPairs '}' + | 'class' SQSTRING '=' '{' customBlobNVPairs '}'; + +nameValPairs: (nameValPair ',')* nameValPair; + +nameValPair: compQstring '=' caValue; + +truefalse: 'true' | 'false'; + +caValue: + truefalse + | int32 + | INT32_ '(' int32 ')' + | compQstring + | className '(' INT8 ':' int32 ')' + | className '(' INT16 ':' int32 ')' + | className '(' INT32_ ':' int32 ')' + | className '(' int32 ')'; + +secAction: + 'request' + | 'demand' + | 'assert' + | 'deny' + | 'permitonly' + | 'linkcheck' + | 'inheritcheck' + | 'reqmin' + | 'reqopt' + | 'reqrefuse' + | 'prejitgrant' + | 'prejitdeny' + | 'noncasdemand' + | 'noncaslinkdemand' + | 'noncasinheritance'; + +/* Method referencing */ +methodRef: + callConv type typeSpec '::' methodName typeArgs? sigArgs + | callConv type typeSpec '::' methodName genArityNotEmpty sigArgs + | callConv type methodName typeArgs? sigArgs + | callConv type methodName genArityNotEmpty sigArgs + | mdtoken + | dottedName /* typeDef */; + +callConv: + INSTANCE callConv + | EXPLICIT callConv + | callKind + | 'callconv' '(' int32 ')'; + +callKind: + /* EMPTY */ + | DEFAULT + | VARARG + | UNMANAGED CDECL + | UNMANAGED STDCALL + | UNMANAGED THISCALL + | UNMANAGED FASTCALL + | UNMANAGED; + +mdtoken: 'mdtoken' '(' int32 ')'; + +memberRef: + 'method' methodRef + | 'field' fieldRef + | mdtoken; + +fieldRef: + type typeSpec '::' dottedName + | type dottedName + | dottedName // typedef + ; + +/* Generic type parameters declaration */ +typeList: (typeSpec ',')* typeSpec; + +typarsClause: /* EMPTY */ | '<' typars '>'; + +typarAttrib: + covariant = PLUS + | contravariant = '-' + | class = 'class' + | valuetype = VALUETYPE + | byrefLike = 'byreflike' + | ctor = '.ctor' + | 'flags' '(' flags = int32 ')'; + +typarAttribs: typarAttrib*; + +typar: typarAttribs tyBound? dottedName; + +typars: (typar ',')* typar; + +tyBound: '(' typeList ')'; + +genArity: /* EMPTY */ | genArityNotEmpty; + +genArityNotEmpty: '<' '[' int32 ']' '>'; + +/* Class body declarations */ +classDecl: + methodHead '{' methodDecls '}' + | classHead '{' classDecls '}' + | eventHead '{' eventDecls '}' + | propHead '{' propDecls '}' + | fieldDecl + | dataDecl + | secDecl + | extSourceSpec + | customAttrDecl + | '.size' int32 + | '.pack' int32 + | exportHead '{' exptypeDecls '}' + | OVERRIDE typeSpec '::' methodName 'with' callConv type typeSpec '::' methodName sigArgs + | OVERRIDE 'method' callConv type typeSpec '::' methodName genArity sigArgs 'with' 'method' + callConv type typeSpec '::' methodName genArity sigArgs + | languageDecl + | compControl + | PARAM TYPE '[' int32 ']' customAttrDecl* + | PARAM TYPE dottedName customAttrDecl* + | PARAM CONSTRAINT '[' int32 ']' ',' typeSpec customAttrDecl* + | PARAM CONSTRAINT dottedName ',' typeSpec customAttrDecl* + | '.interfaceimpl' TYPE typeSpec customDescr; + +/* Field declaration */ +fieldDecl: + '.field' repeatOpt (fieldAttr | 'marshal' '(' marshalBlob ')')* type dottedName atOpt initOpt; + +fieldAttr: + 'static' + | 'public' + | 'private' + | 'family' + | 'initonly' + | 'rtspecialname' + | 'specialname' + | 'assembly' + | 'famandassem' + | 'famorassem' + | 'privatescope' + | 'literal' + | 'notserialized' + | 'flags' '(' int32 ')'; + +atOpt: /* EMPTY */ | 'at' id; + +initOpt: /* EMPTY */ | '=' fieldInit; + +repeatOpt: /* EMPTY */ | '[' int32 ']'; + +/* Event declaration */ +eventHead: + '.event' eventAttr* typeSpec dottedName + | '.event' eventAttr* dottedName; + +eventAttr: + 'rtspecialname' + | 'specialname'; + +eventDecls: eventDecl*; + +eventDecl: + '.addon' methodRef + | '.removeon' methodRef + | '.fire' methodRef + | '.other' methodRef + | extSourceSpec + | customAttrDecl + | languageDecl + | compControl; + +/* Property declaration */ +propHead: + '.property' propAttr* callConv type dottedName sigArgs initOpt; + +propAttr: + 'rtspecialname' + | 'specialname'; + +propDecls: propDecl*; + +propDecl: + '.set' methodRef + | '.get' methodRef + | '.other' methodRef + | customAttrDecl + | extSourceSpec + | languageDecl + | compControl; + +/* Method declaration */ + +marshalClause: /* EMPTY */ | 'marshal' '(' marshalBlob ')'; + +marshalBlob: nativeType | '{' hexbytes '}'; + +paramAttr: paramAttrElement*; + +paramAttrElement: + '[' in = 'in' ']' + | '[' out = 'out' ']' + | '[' opt = 'opt' ']' + | '[' int32 ']'; + +methodHead: + '.method' (methAttr | pinvImpl)* callConv paramAttr type marshalClause methodName typarsClause sigArgs + implAttr*; + +methAttr: 'static' + | 'public' + | 'private' + | 'family' + | 'final' + | 'specialname' + | 'virtual' + | 'strict' + | 'abstract' + | 'assembly' + | 'famandassem' + | 'famorassem' + | 'privatescope' + | 'hidebysig' + | 'newslot' + | 'rtspecialname' + | 'unmanagedexp' + | 'reqsecobj' + | 'flags' '(' int32 ')'; + +pinvImpl: 'pinvokeimpl' '(' (compQstring ('as' compQstring)?)? pinvAttr* ')'; + +pinvAttr: + 'nomangle' + | 'ansi' + | 'unicode' + | 'autochar' + | 'lasterr' + | 'winapi' + | 'cdecl' + | 'stdcall' + | 'thiscall' + | 'fastcall' + | 'bestfit' ':' 'on' + | 'bestfit' ':' 'off' + | 'charmaperror' ':' 'on' + | 'charmaperror' ':' 'off' + | 'flags' '(' int32 ')'; + +methodName: '.ctor' | '.cctor' | dottedName; + +implAttr: + 'native' + | 'cil' + | 'optil' + | 'managed' + | 'unmanaged' + | 'forwardref' + | 'preservesig' + | 'runtime' + | 'internalcall' + | 'synchronized' + | 'noinlining' + | 'aggressiveinlining' + | 'nooptimization' + | 'aggressiveoptimization' + | 'async' + | 'flags' '(' int32 ')'; + +EMITBYTE: '.emitbyte'; +MAXSTACK: '.maxstack'; +ENTRYPOINT: '.entrypoint'; +ZEROINIT: '.zeroinit'; +LOCALS: '.locals'; +EXPORT: '.export'; +OVERRIDE: '.override'; +VTENTRY: '.vtentry'; + +methodDecls: methodDecl*; + +methodDecl: + EMITBYTE int32 + | sehBlock + | MAXSTACK int32 + | LOCALS sigArgs + | LOCALS 'init' sigArgs + | ENTRYPOINT + | ZEROINIT + | dataDecl + | instr + | id ':' + | secDecl + | extSourceSpec // Leave for later when I get to generating symbols. + | languageDecl // Leave for later when I get to generating symbols. + | customAttrDecl + | compControl + | EXPORT '[' int32 ']' + | EXPORT '[' int32 ']' 'as' id + | VTENTRY int32 ':' int32 + | OVERRIDE typeSpec '::' methodName + | OVERRIDE 'method' callConv type typeSpec '::' methodName genArity sigArgs + | scopeBlock + | PARAM TYPE '[' int32 ']' customAttrDecl* + | PARAM TYPE dottedName customAttrDecl* + | PARAM CONSTRAINT '[' int32 ']' ',' typeSpec customAttrDecl* + | PARAM CONSTRAINT dottedName ',' typeSpec customAttrDecl* + | PARAM '[' int32 ']' initOpt customAttrDecl*; + +scopeBlock: '{' methodDecls '}'; + +/* Structured exception handling directives */ +sehBlock: tryBlock sehClauses; + +sehClauses: sehClause+; + +tryBlock: + '.try' scopeBlock + | '.try' id 'to' id + | '.try' int32 'to' int32; + +sehClause: + catchClause handlerBlock + | filterClause handlerBlock + | finallyClause handlerBlock + | faultClause handlerBlock; + +filterClause: + 'filter' scopeBlock + | 'filter' id + | 'filter' int32; + +catchClause: 'catch' typeSpec; + +finallyClause: 'finally'; + +faultClause: 'fault'; + +handlerBlock: + scopeBlock + | 'handler' id 'to' id + | 'handler' int32 'to' int32; + +/* Data declaration */ +dataDecl: ddHead ddBody; + +ddHead: '.data' tls id '=' | '.data' tls; + +tls: /* EMPTY */ | 'tls' | 'cil'; + +ddBody: '{' ddItemList '}' | ddItem; + +ddItemList: (ddItem ',')* ddItem; + +ddItemCount: /* EMPTY */ | '[' int32 ']'; + +ddItem: + CHAR PTR '(' compQstring ')' + | REF '(' id ')' + | 'bytearray' '(' bytes ')' + | FLOAT32 '(' float64 ')' ddItemCount + | FLOAT64_ '(' float64 ')' ddItemCount + | INT64_ '(' int64 ')' ddItemCount + | INT32_ '(' int32 ')' ddItemCount + | INT16 '(' int32 ')' ddItemCount + | INT8 '(' int32 ')' ddItemCount + | FLOAT32 ddItemCount + | FLOAT64_ ddItemCount + | INT64_ ddItemCount + | INT32_ ddItemCount + | INT16 ddItemCount + | INT8 ddItemCount; + +/* Default values declaration for fields, parameters and verbal form of CA blob description */ +fieldSerInit: + FLOAT32 '(' float64 ')' + | FLOAT64_ '(' float64 ')' + | FLOAT32 '(' int32 ')' + | FLOAT64_ '(' int64 ')' + | INT64_ '(' int64 ')' + | INT32_ '(' int32 ')' + | INT16 '(' int32 ')' + | INT8 '(' int32 ')' + | UINT64 '(' int64 ')' + | UINT32 '(' int32 ')' + | UINT16 '(' int32 ')' + | UINT8 '(' int32 ')' + | CHAR '(' int32 ')' + | BOOL '(' truefalse ')' + | 'bytearray' '(' bytes ')'; + +bytes: hexbytes*; + +hexbytes: HEXBYTE+; +/* Field/parameter initialization */ +fieldInit: fieldSerInit | compQstring | NULLREF; + +/* Values for verbal form of CA blob description */ +serInit: + fieldSerInit + | STRING '(' NULLREF ')' + | STRING '(' SQSTRING ')' + | TYPE '(' 'class' SQSTRING ')' + | TYPE '(' className ')' + | TYPE '(' NULLREF ')' + | OBJECT '(' serInit ')' + | FLOAT32 '[' int32 ']' '(' f32seq ')' + | FLOAT64_ '[' int32 ']' '(' f64seq ')' + | INT64_ '[' int32 ']' '(' i64seq ')' + | INT32_ '[' int32 ']' '(' i32seq ')' + | INT16 '[' int32 ']' '(' i16seq ')' + | INT8 '[' int32 ']' '(' i8seq ')' + | UINT64 '[' int32 ']' '(' i64seq ')' + | UINT32 '[' int32 ']' '(' i32seq ')' + | UINT16 '[' int32 ']' '(' i16seq ')' + | UINT8 '[' int32 ']' '(' i8seq ')' + | CHAR '[' int32 ']' '(' i16seq ')' + | BOOL '[' int32 ']' '(' boolSeq ')' + | STRING '[' int32 ']' '(' sqstringSeq ')' + | TYPE '[' int32 ']' '(' classSeq ')' + | OBJECT '[' int32 ']' '(' objSeq ')'; + +f32seq: (float64 | int32)*; + +f64seq: (float64 | int64)*; + +i64seq: int64*; + +i32seq: int32*; + +i16seq: int32*; + +i8seq: int32*; + +boolSeq: truefalse*; + +sqstringSeq: (NULLREF | SQSTRING)*; + +classSeq: classSeqElement*; + +classSeqElement: NULLREF | 'class' SQSTRING | className; + +objSeq: serInit*; + +customAttrDecl: + customDescr + | customDescrWithOwner + | dottedName /* typedef */; + +/* Assembly References */ +asmOrRefDecl: + '.publicKey' '=' '(' bytes ')' + | '.ver' intOrWildcard ':' intOrWildcard ':' intOrWildcard ':' intOrWildcard + | '.locale' compQstring + | '.locale' '=' '(' bytes ')' + | customAttrDecl + | compControl; + +assemblyRefHead: + '.assembly' 'extern' asmAttr dottedName + | '.assembly' 'extern' asmAttr dottedName 'as' dottedName; + +assemblyRefDecls: assemblyRefDecl*; + +assemblyRefDecl: + '.hash' '=' '(' bytes ')' + | asmOrRefDecl + | '.publickeytoken' '=' '(' bytes ')' + | 'auto'; + +exptypeHead: '.class' 'extern' exptAttr* dottedName; + +exportHead: '.export' exptAttr* dottedName; + +exptAttr: + 'private' + | 'public' + | 'forwarder' + | 'nested' 'public' + | 'nested' 'private' + | 'nested' 'family' + | 'nested' 'assembly' + | 'nested' 'famandassem' + | 'nested' 'famorassem'; + +exptypeDecls: exptypeDecl*; + +exptypeDecl: + '.file' dottedName + | '.class' 'extern' slashedName + | '.assembly' 'extern' dottedName + | mdtoken + | '.class' int32 + | customAttrDecl + | compControl; + +manifestResHead: + MRESOURCE manresAttr* dottedName + | MRESOURCE manresAttr* dottedName 'as' dottedName; + +manresAttr: 'public' | 'private'; + +manifestResDecls: manifestResDecl*; + +manifestResDecl: + '.file' dottedName 'at' int32 + | '.assembly' 'extern' dottedName + | customAttrDecl + | compControl; + diff --git a/src/tools/ilasm/src/ILAssembler/Diagnostic.cs b/src/tools/ilasm/src/ILAssembler/Diagnostic.cs new file mode 100644 index 00000000000000..b5d6971cbab403 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/Diagnostic.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; + +namespace ILAssembler; + +public enum DiagnosticSeverity +{ + Error, + Warning, + Info, + Hidden +} + +public record Diagnostic(string Id, DiagnosticSeverity Severity, string Message, Location Location); + +internal static class DiagnosticIds +{ + public const string LiteralOutOfRange = "ILA0001"; + public const string UnsealedValueType = "ILA0002"; +} + +internal static class DiagnosticMessageTemplates +{ + public const string LiteralOutOfRange = "The value '{0}' is out of range"; + public const string UnsealedValueType = "The value type '{0}' is unsealed; implicitly sealed."; +} diff --git a/src/tools/ilasm/src/ILAssembler/DocumentCompiler.cs b/src/tools/ilasm/src/ILAssembler/DocumentCompiler.cs new file mode 100644 index 00000000000000..3b239357bfb5a3 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/DocumentCompiler.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using Antlr4.Runtime; + +namespace ILAssembler; +public sealed class DocumentCompiler +{ + public (ImmutableArray, PEBuilder?) Compile(SourceText document, Func includedDocumentLoader, Func resourceLocator, Options options) + { + var inputSource = new AntlrInputStream(document.Text) + { + name = document.Path + }; + CILLexer lexer = new(inputSource); + Dictionary loadedDocuments = new() + { + {document.Path!, document } + }; + PreprocessedTokenSource preprocessor = new(lexer, path => + { + var includedDocument = includedDocumentLoader(path); + + var includedSource = new AntlrInputStream(includedDocument.Text) + { + name = includedDocument.Path + }; + loadedDocuments.Add(includedDocument.Path, includedDocument); + return new CILLexer(includedSource); + }); + + ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder(); + preprocessor.OnPreprocessorSyntaxError += (source, start, length, msg) => + { + diagnostics.Add(new Diagnostic("Preprocessor", DiagnosticSeverity.Error, msg, new Location(new(start, length), loadedDocuments[source]))); + }; + + CILParser parser = new(new CommonTokenStream(lexer)); + var result = parser.decls(); + GrammarVisitor visitor = new GrammarVisitor(loadedDocuments, options, resourceLocator); + _ = result.Accept(visitor); + + var image = visitor.BuildImage(); + + bool anyErrors = diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error); + + diagnostics.AddRange(image.Diagnostics); + + return (diagnostics.ToImmutable(), anyErrors ? null : image.Image); + } +} diff --git a/src/tools/ilasm/src/ILAssembler/EntityRegistry.cs b/src/tools/ilasm/src/ILAssembler/EntityRegistry.cs new file mode 100644 index 00000000000000..1755fcc15920bf --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/EntityRegistry.cs @@ -0,0 +1,1337 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Text; + +namespace ILAssembler +{ + internal sealed class EntityRegistry + { + private readonly Dictionary> _seenEntities = new(); + private readonly Dictionary<(TypeDefinitionEntity? ContainingType, string Namespace, string Name), TypeDefinitionEntity> _seenTypeDefs = new(); + private readonly Dictionary<(EntityBase ResolutionScope, string Namespace, string Name), TypeReferenceEntity> _seenTypeRefs = new(); + private readonly Dictionary _seenAssemblyRefs = new(); + private readonly Dictionary _seenModuleRefs = new(); + private readonly Dictionary _seenTypeSpecs = new(new BlobBuilderContentEqualityComparer()); + private readonly Dictionary _seenStandaloneSignatures = new(new BlobBuilderContentEqualityComparer()); + private readonly Dictionary _seenFiles = new(); + private readonly List _manifestResourceEntities = new(); + private readonly Dictionary<(ExportedTypeEntity? ContainingType, string Namespace, string Name), ExportedTypeEntity> _seenExportedTypes = new(); + private readonly List _memberReferences = new(); + private readonly Dictionary<(EntityBase, BlobBuilder), MethodSpecificationEntity> _seenMethodSpecs = new(new MethodSpecEqualityComparer()); + + private sealed class BlobBuilderContentEqualityComparer : IEqualityComparer + { + public bool Equals(BlobBuilder? x, BlobBuilder? y) + { + if (x is null && y is null) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + return x.ContentEquals(y!); + } + + public int GetHashCode(BlobBuilder obj) + { + HashCode hash = default; + foreach (var b in obj.GetBlobs()) + { + hash.AddBytes(b.GetBytes()); + } + return hash.ToHashCode(); + } + } + + private sealed class MethodSpecEqualityComparer : IEqualityComparer<(EntityBase, BlobBuilder)> + { + public bool Equals((EntityBase, BlobBuilder) x, (EntityBase, BlobBuilder) y) + { + return x.Item1 == y.Item1 && x.Item2.ContentEquals(y.Item2); + } + + public int GetHashCode((EntityBase, BlobBuilder) obj) + { + return (obj.Item1.GetHashCode(), obj.Item2.Count).GetHashCode(); + } + } + + public enum WellKnownBaseType + { + System_Object, + System_ValueType, + System_Enum + } + + public EntityRegistry() + { + ModuleType = GetOrCreateTypeDefinition(null, "", "", moduleType => + { + moduleType.BaseType = null; + }); + } + + private IReadOnlyList GetSeenEntities(TableIndex table) + { + if (_seenEntities.TryGetValue(table, out var entities)) + { + return entities; + } + return Array.Empty(); + } + + public void WriteContentTo(MetadataBuilder builder, BlobBuilder ilStream) + { + // Now that we've seen all of the entities, we can write them out in the correct order. + // Record the entities in the correct order so they are assigned handles. + // After this, we'll write out the content of the entities in the correct order. + foreach (TypeDefinitionEntity type in GetSeenEntities(TableIndex.TypeDef)) + { + // Record entries for members defined in list columns + foreach (var method in type.Methods) + { + RecordEntityInTable(TableIndex.MethodDef, method); + foreach (var param in method.Parameters) + { + // COMPAT: Only record param entries for parameters that have names + // or other rows that would refer to it. + if (param.Name is not null + || param.MarshallingDescriptor.Count != 0 + || param.HasCustomAttributes) + { + RecordEntityInTable(TableIndex.Param, param); + } + } + } + foreach (var field in type.Fields) + { + RecordEntityInTable(TableIndex.Field, field); + } + foreach (var property in type.Properties) + { + RecordEntityInTable(TableIndex.Property, property); + } + foreach (var @event in type.Events) + { + RecordEntityInTable(TableIndex.Event, @event); + } + + // Record entries in tables that are sorted based on their containing/associated class + foreach (var impl in type.InterfaceImplementations) + { + RecordEntityInTable(TableIndex.InterfaceImpl, impl); + } + + foreach (var impl in type.MethodImplementations) + { + RecordEntityInTable(TableIndex.MethodImpl, impl); + } + + foreach (var genericParam in type.GenericParameters) + { + RecordEntityInTable(TableIndex.GenericParam, genericParam); + } + + // COMPAT: Record the generic parameter constraints based on the order saved in the TypeDef + foreach (var constraint in type.GenericParameterConstraints) + { + RecordEntityInTable(TableIndex.GenericParamConstraint, constraint); + } + } + + foreach (MemberReferenceEntity memberReferenceEntity in _memberReferences) + { + ResolveAndRecordMemberReference(memberReferenceEntity); + } + + // Now that we've recorded all of the entities that wouldn't have had handles before, + // we can start writing out the content of the entities. + builder.AddModule(0, Module.Name is null ? default : builder.GetOrAddString(Module.Name), builder.GetOrAddGuid(Guid.NewGuid()), default, default); + + foreach (TypeReferenceEntity type in GetSeenEntities(TableIndex.TypeRef)) + { + EntityBase resolutionScope = type.ResolutionScope; + builder.AddTypeReference( + resolutionScope is FakeTypeEntity fakeScope ? fakeScope.ResolutionScopeColumnHandle : resolutionScope.Handle, + builder.GetOrAddString(type.Namespace), + builder.GetOrAddString(type.Name)); + } + + for (int i = 0; i < GetSeenEntities(TableIndex.TypeDef).Count; i++) + { + TypeDefinitionEntity type = (TypeDefinitionEntity)GetSeenEntities(TableIndex.TypeDef)[i]; + builder.AddTypeDefinition( + type.Attributes, + builder.GetOrAddString(type.Namespace), + builder.GetOrAddString(type.Name), + type.BaseType is null ? default : type.BaseType.Handle, + (FieldDefinitionHandle)GetHandleForList(type.Fields, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Fields, i, TableIndex.Field), + (MethodDefinitionHandle)GetHandleForList(type.Methods, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Methods, i, TableIndex.MethodDef)); + + builder.AddEventMap( + (TypeDefinitionHandle)type.Handle, + (EventDefinitionHandle)GetHandleForList(type.Events, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Events, i, TableIndex.Event)); + builder.AddPropertyMap( + (TypeDefinitionHandle)type.Handle, + (PropertyDefinitionHandle)GetHandleForList(type.Properties, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Properties, i, TableIndex.Property)); + + // TODO: ClassLayout + if (type.ContainingType is not null) + { + builder.AddNestedType((TypeDefinitionHandle)type.Handle, (TypeDefinitionHandle)type.ContainingType.Handle); + } + } + + foreach (FieldDefinitionEntity fieldDef in GetSeenEntities(TableIndex.Field)) + { + builder.AddFieldDefinition( + fieldDef.Attributes, + builder.GetOrAddString(fieldDef.Name), + fieldDef.Signature!.Count == 0 ? default : builder.GetOrAddBlob(fieldDef.Signature)); + + // TODO: FieldLayout, FieldRVA + if (fieldDef.MarshallingDescriptor is not null) + { + builder.AddMarshallingDescriptor(fieldDef.Handle, builder.GetOrAddBlob(fieldDef.MarshallingDescriptor)); + } + } + + for (int i = 0; i < GetSeenEntities(TableIndex.MethodDef).Count; i++) + { + MethodDefinitionEntity methodDef = (MethodDefinitionEntity)GetSeenEntities(TableIndex.MethodDef)[i]; + + int rva = 0; + if (methodDef.MethodBody.CodeBuilder.Count != 0) + { + rva = ilStream.Count; + methodDef.MethodBody.CodeBuilder.WriteContentTo(ilStream); + } + + builder.AddMethodDefinition( + methodDef.MethodAttributes, + methodDef.ImplementationAttributes, + builder.GetOrAddString(methodDef.Name), + builder.GetOrAddBlob(methodDef.MethodSignature!), + rva, + (ParameterHandle)GetHandleForList(methodDef.Parameters, GetSeenEntities(TableIndex.MethodDef), method => ((MethodDefinitionEntity)method).Parameters, i, TableIndex.Param)); + + if (methodDef.MethodImportInformation is not null) + { + builder.AddMethodImport( + (MethodDefinitionHandle)methodDef.Handle, + methodDef.MethodImportInformation.Value.Attributes, + methodDef.MethodImportInformation.Value.EntryPointName is null ? default : builder.GetOrAddString(methodDef.MethodImportInformation.Value.EntryPointName), + (ModuleReferenceHandle)methodDef.MethodImportInformation.Value.ModuleName.Handle); + } + } + + foreach (ParameterEntity param in GetSeenEntities(TableIndex.Param)) + { + builder.AddParameter( + param.Attributes, + param.Name is null ? default : builder.GetOrAddString(param.Name), + param.Sequence); + + if (param.MarshallingDescriptor is not null) + { + builder.AddMarshallingDescriptor(param.Handle, builder.GetOrAddBlob(param.MarshallingDescriptor)); + } + } + + foreach (InterfaceImplementationEntity impl in GetSeenEntities(TableIndex.InterfaceImpl)) + { + builder.AddInterfaceImplementation( + (TypeDefinitionHandle)impl.Type.Handle, + impl.InterfaceType is FakeTypeEntity fakeType ? fakeType.TypeColumnHandle : impl.InterfaceType.Handle); + } + + foreach (MemberReferenceEntity memberRef in _memberReferences) + { + builder.AddMemberReference( + memberRef.Parent.Handle, + builder.GetOrAddString(memberRef.Name), + builder.GetOrAddBlob(memberRef.Signature)); + } + + foreach (DeclarativeSecurityAttributeEntity declSecurity in GetSeenEntities(TableIndex.DeclSecurity)) + { + builder.AddDeclarativeSecurityAttribute( + declSecurity.Parent?.Handle ?? default, + declSecurity.Action, + builder.GetOrAddBlob(declSecurity.PermissionSet)); + } + + foreach (StandaloneSignatureEntity standaloneSig in GetSeenEntities(TableIndex.StandAloneSig)) + { + builder.AddStandaloneSignature( + builder.GetOrAddBlob(standaloneSig.Signature)); + } + + foreach (EventEntity evt in GetSeenEntities(TableIndex.Event)) + { + builder.AddEvent( + evt.Attributes, + builder.GetOrAddString(evt.Name), + evt.Type.Handle); + + foreach (var accessor in evt.Accessors) + { + builder.AddMethodSemantics(evt.Handle, accessor.Semantic, (MethodDefinitionHandle)accessor.Method.Handle); + } + } + + foreach (PropertyEntity prop in GetSeenEntities(TableIndex.Property)) + { + builder.AddProperty( + prop.Attributes, + builder.GetOrAddString(prop.Name), + builder.GetOrAddBlob(prop.Type)); + + foreach (var accessor in prop.Accessors) + { + builder.AddMethodSemantics(prop.Handle, accessor.Semantic, (MethodDefinitionHandle)accessor.Method.Handle); + } + } + + foreach (ModuleReferenceEntity moduleRef in GetSeenEntities(TableIndex.ModuleRef)) + { + builder.AddModuleReference(builder.GetOrAddString(moduleRef.Name)); + } + + foreach (TypeSpecificationEntity typeSpec in GetSeenEntities(TableIndex.TypeSpec)) + { + builder.AddTypeSpecification(builder.GetOrAddBlob(typeSpec.Signature)); + } + + if (Assembly is not null) + { + builder.AddAssembly( + builder.GetOrAddString(Assembly.Name), + Assembly.Version ?? new Version(), + Assembly.Culture is null ? default : builder.GetOrAddString(Assembly.Culture), + Assembly.PublicKeyOrToken is null ? default : builder.GetOrAddBlob(Assembly.PublicKeyOrToken), + Assembly.Flags, + Assembly.HashAlgorithm); + } + + foreach (FileEntity file in GetSeenEntities(TableIndex.File)) + { + builder.AddAssemblyFile( + builder.GetOrAddString(file.Name), + file.Hash is not null ? builder.GetOrAddBlob(file.Hash) : default, + file.HasMetadata); + } + + foreach (ExportedTypeEntity exportedType in GetSeenEntities(TableIndex.ExportedType)) + { + builder.AddExportedType( + exportedType.Attributes, + builder.GetOrAddString(exportedType.Name), + builder.GetOrAddString(exportedType.Namespace), + exportedType.Implementation?.Handle ?? default, + exportedType.TypeDefinitionId); + } + + foreach (ManifestResourceEntity resource in GetSeenEntities(TableIndex.ManifestResource)) + { + builder.AddManifestResource( + resource.Attributes, + builder.GetOrAddString(resource.Name), + resource.Implementation?.Handle ?? default, + resource.Offset); + } + + foreach (MethodSpecificationEntity methodSpec in GetSeenEntities(TableIndex.MethodSpec)) + { + builder.AddMethodSpecification(methodSpec.Parent.Handle, builder.GetOrAddBlob(methodSpec.Signature)); + } + + static EntityHandle GetHandleForList(IReadOnlyList list, IReadOnlyList listOwner, Func> getList, int ownerIndex, TableIndex tokenType) + { + // Return the first entry in the list. + // If the list is empty, return the start of the next list. + // If there is no next list, return one past the end of the previous list. + if (list.Count != 0) + { + return list[0].Handle; + } + + for (int i = 0; i < listOwner.Count; i++) + { + var otherList = getList(listOwner[i]); + if (otherList.Count != 0) + { + return otherList[0].Handle; + } + } + + for (int i = ownerIndex - 1; i >= 0; i--) + { + var otherList = getList(listOwner[i]); + if (otherList.Count != 0) + { + return MetadataTokens.EntityHandle(tokenType, MetadataTokens.GetRowNumber(otherList[otherList.Count - 1].Handle) + 1); + } + } + return MetadataTokens.EntityHandle(tokenType, 0); + } + } + + public TypeEntity? ResolveImplicitBaseType(WellKnownBaseType? type) + { + if (type is null) + { + return null; + } + return type switch + { + WellKnownBaseType.System_Object => SystemObjectType, + WellKnownBaseType.System_ValueType => SystemValueTypeType, + WellKnownBaseType.System_Enum => SystemEnumType, + _ => throw new ArgumentOutOfRangeException(nameof(type)) + }; + } + + private TypeEntity? _systemObject; + public TypeEntity SystemObjectType + { + get + { + return _systemObject ??= ResolveFromCoreAssembly("System.Object"); + } + } + + private TypeEntity? _systemValueType; + public TypeEntity SystemValueTypeType + { + get + { + return _systemValueType ??= ResolveFromCoreAssembly("System.ValueType"); + } + } + + private TypeEntity? _systemEnum; + public TypeEntity SystemEnumType + { + get + { + return _systemEnum ??= ResolveFromCoreAssembly("System.Enum"); + } + } + + public TypeDefinitionEntity ModuleType { get; } + + public ModuleEntity Module { get; } = new ModuleEntity(); + + public AssemblyEntity? Assembly { get; set; } + + private TypeReferenceEntity ResolveFromCoreAssembly(string typeName) + { + // TODO: System.Private.CoreLib as the core assembly? + var coreAsmRef = GetOrCreateAssemblyReference("mscorlib", new Version(4, 0), culture: null, publicKeyOrToken: null, 0, ProcessorArchitecture.None); + return GetOrCreateTypeReference(coreAsmRef, new TypeName(null, typeName)); + } + + public interface IHasHandle + { + EntityHandle Handle { get; } + void SetHandle(EntityHandle token); + } + + public void RecordEntityInTable(TableIndex table, EntityBase entity) + { + if (!_seenEntities.TryGetValue(table, out List? entities)) + { + _seenEntities[table] = entities = new List(); + } + entities.Add(entity); + ((IHasHandle)entity).SetHandle(MetadataTokens.EntityHandle(table, entities.Count)); + } + + private TEntity GetOrCreateEntity(TKey key, TableIndex table, Dictionary cache, Func constructor, Action onCreate) + where TKey : notnull + where TEntity : EntityBase + { + if (cache.TryGetValue(key, out TEntity? entity)) + { + return entity; + } + entity = constructor(key); + RecordEntityInTable(table, entity); + cache.Add(key, entity); + onCreate(entity); + return entity; + } + + private TEntity CreateEntity(TableIndex table, List cache, Func constructor) + where TEntity : EntityBase + { + TEntity entity = constructor(); + RecordEntityInTable(table, entity); + cache.Add(entity); + return entity; + } + + public TypeDefinitionEntity GetOrCreateTypeDefinition(TypeDefinitionEntity? containingType, string @namespace, string name, Action onCreateType) + { + return GetOrCreateEntity((containingType, @namespace, name), TableIndex.TypeDef, _seenTypeDefs, (key) => new(key.Item1, key.Item2, key.Item3), onCreateType); + } + + public TypeDefinitionEntity? FindTypeDefinition(TypeDefinitionEntity? containingType, string @namespace, string @name) + { + if (_seenTypeDefs.TryGetValue((containingType, @namespace, name), out var typeDef)) + { + return typeDef; + } + return null; + } + + public AssemblyReferenceEntity GetOrCreateAssemblyReference(string name, Action onCreateAssemblyReference) + { + return GetOrCreateEntity(new(name), TableIndex.AssemblyRef, _seenAssemblyRefs, _ => new(name), onCreateAssemblyReference); + } + + public ModuleReferenceEntity GetOrCreateModuleReference(string name, Action onCreateModuleReference) + { + return GetOrCreateEntity(name, TableIndex.ModuleRef, _seenModuleRefs, name => new(name), onCreateModuleReference); + } + + public ModuleReferenceEntity? FindModuleReference(string name) + { + if (_seenModuleRefs.TryGetValue(name, out var moduleRef)) + { + return moduleRef; + } + return null; + } + + public static GenericParameterEntity CreateGenericParameter(GenericParameterAttributes attributes, string name) + { + GenericParameterEntity param = new(attributes, name); + return param; + } + + public static GenericParameterConstraintEntity CreateGenericConstraint(TypeEntity baseType) + { + GenericParameterConstraintEntity constraint = new(baseType); + return constraint; + } + + public TypeSpecificationEntity GetOrCreateTypeSpec(BlobBuilder signature) + { + return GetOrCreateEntity(signature, TableIndex.TypeSpec, _seenTypeSpecs, signature => new(signature), _ => { }); + } + + public EntityBase ResolveHandleToEntity(EntityHandle entityHandle) + { + _ = MetadataTokens.TryGetTableIndex(entityHandle.Kind, out var tableIndex); + if (_seenEntities.TryGetValue(tableIndex, out var entity)) + { + int rowNumber = MetadataTokens.GetRowNumber(entityHandle); + if (entity.Count < rowNumber - 1) + { + return entity[rowNumber - 1]; + } + } + // Row entry does not exist. Use our FakeTypeEntity type to record the invalid handle. + return new FakeTypeEntity(entityHandle); + } + + public TypeReferenceEntity GetOrCreateTypeReference(EntityBase resolutionContext, TypeName name) + { + Stack<(string Namespace, string Name)> allTypeNames = new(); + // Record all of the containing type names + for (TypeName? containingType = name; containingType is not null; containingType = containingType.ContainingTypeName) + { + allTypeNames.Push(NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName)); + } + + EntityBase scope = resolutionContext; + while (scope is TypeReferenceEntity typeRef) + { + allTypeNames.Push((typeRef.Namespace, typeRef.Name)); + scope = typeRef.ResolutionScope; + } + while (allTypeNames.Count > 0) + { + var typeName = allTypeNames.Pop(); + scope = GetOrCreateEntity((scope, typeName.Namespace, typeName.Name), TableIndex.TypeRef, _seenTypeRefs, value => new TypeReferenceEntity(scope, value.Namespace, value.Name), typeRef => + { + StringBuilder builder = new(typeRef.Namespace.Length + typeRef.Name.Length + 1); + builder.AppendFormat("{0}.{1}", typeRef.Namespace, typeRef.Name); + if (resolutionContext is AssemblyReferenceEntity asmRef) + { + // TODO: Do full assembly name here + builder.Append(asmRef.Name); + } + typeRef.ReflectionNotation = builder.ToString(); + }); + } + return (TypeReferenceEntity)scope; + } + + public static MethodDefinitionEntity CreateUnrecordedMethodDefinition(TypeDefinitionEntity containingType, string name) + { + return new MethodDefinitionEntity(containingType, name); + } + + public static bool TryAddMethodDefinitionToContainingType(MethodDefinitionEntity methodDef) + { + if (methodDef.MethodSignature is null) + { + throw new ArgumentException("The method signature must be defined before recording the method definition, to enable detecting duplicate methods."); + } + bool allowDuplicate = (methodDef.MethodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.PrivateScope; + if (!allowDuplicate) + { + foreach (var method in methodDef.ContainingType.Methods) + { + if (methodDef.Name == method.Name + && methodDef.MethodSignature.ContentEquals(method.MethodSignature!) + && (method.MethodAttributes & MethodAttributes.MemberAccessMask) != MethodAttributes.PrivateScope) + { + return false; + } + } + } + methodDef.ContainingType.Methods.Add(methodDef); + return true; + } + + public static FieldDefinitionEntity? CreateUnrecordedFieldDefinition(FieldAttributes attributes, TypeDefinitionEntity containingType, string name, BlobBuilder signature) + { + var field = new FieldDefinitionEntity(attributes, containingType, name, signature); + bool allowDuplicate = (field.Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.PrivateScope; + if (!allowDuplicate) + { + foreach (var fieldDef in field.ContainingType.Fields) + { + if (fieldDef.Name == field.Name + && fieldDef.Signature.ContentEquals(field.Signature!) + && (fieldDef.Attributes & FieldAttributes.FieldAccessMask) != FieldAttributes.PrivateScope) + { + return null; + } + } + } + field.ContainingType.Fields.Add(field); + return field; + } + + public static InterfaceImplementationEntity CreateUnrecordedInterfaceImplementation(TypeDefinitionEntity implementingType, TypeEntity interfaceType) + { + return new InterfaceImplementationEntity(implementingType, interfaceType); + } + + public static ParameterEntity CreateParameter(ParameterAttributes attributes, string? name, BlobBuilder marshallingDescriptor, int sequence) + { + return new ParameterEntity(attributes, name, marshallingDescriptor, sequence); + } + + public MemberReferenceEntity CreateLazilyRecordedMemberReference(TypeEntity containingType, string name, BlobBuilder signature) + { + var entity = new MemberReferenceEntity(containingType, name, signature); + _memberReferences.Add(entity); + return entity; + } + + private sealed class SignatureRewriter : ISignatureTypeProvider + { + public readonly struct BlobOrHandle + { + public BlobOrHandle(BlobBuilder? blob) + { + Blob = blob; + Handle = default; + HandleIsValueType = false; + } + + public BlobOrHandle(EntityHandle handle, bool handleIsValueType) + { + Blob = default; + Handle = handle; + HandleIsValueType = handleIsValueType; + } + + private BlobBuilder? Blob { get; } + public EntityHandle Handle { get; } + public bool HandleIsValueType { get; } + + public static implicit operator BlobOrHandle(BlobBuilder blob) => new(blob); + public static implicit operator BlobBuilder(BlobOrHandle blobOrHandle) + { + if (blobOrHandle.Blob is not null) + { + return blobOrHandle.Blob; + } + var signatureTypeEncoder = new SignatureTypeEncoder(new BlobBuilder()); + signatureTypeEncoder.Type(blobOrHandle.Handle, blobOrHandle.HandleIsValueType); + return signatureTypeEncoder.Builder; + } + + public void WriteBlobTo(BlobBuilder builder) + { + ((BlobBuilder)this).WriteContentTo(builder); + } + } + + public BlobOrHandle GetArrayType(BlobOrHandle elementType, ArrayShape shape) + { + var encoder = new ArrayShapeEncoder(elementType); + encoder.Shape(shape.Rank, shape.Sizes, shape.LowerBounds); + return encoder.Builder; + } + + public BlobOrHandle GetByReferenceType(BlobOrHandle elementType) + { + var paramEncoder = new ParameterTypeEncoder(new BlobBuilder()); + paramEncoder.Type(isByRef: true); + elementType.WriteBlobTo(paramEncoder.Builder); + return paramEncoder.Builder; + } + + public BlobOrHandle GetFunctionPointerType(MethodSignature signature) + { + var sig = new SignatureTypeEncoder(new BlobBuilder()); + sig.FunctionPointer(signature.Header.CallingConvention, (FunctionPointerAttributes)signature.Header.Attributes, signature.GenericParameterCount) + .Parameters(signature.ParameterTypes.Length, out var retTypeBuilder, out var parametersEncoder); + signature.ReturnType.WriteBlobTo(retTypeBuilder.Builder); + for (int i = 0; i < signature.ParameterTypes.Length; i++) + { + if (i == signature.RequiredParameterCount) + { + parametersEncoder.StartVarArgs(); + } + BlobBuilder paramType = signature.ParameterTypes[i]; + paramType.WriteContentTo(parametersEncoder.AddParameter().Builder); + } + return sig.Builder; + } + + public BlobOrHandle GetGenericInstantiation(BlobOrHandle genericType, ImmutableArray typeArguments) + { + var encoder = new SignatureTypeEncoder(new BlobBuilder()); + var parameterEncoder = encoder.GenericInstantiation(genericType.Handle, typeArguments.Length, genericType.HandleIsValueType); + foreach (var typeArg in typeArguments) + { + typeArg.WriteBlobTo(parameterEncoder.AddArgument().Builder); + } + return encoder.Builder; + } + + public BlobOrHandle GetGenericMethodParameter(EmptyGenericContext genericContext, int index) + { + var encoder = new SignatureTypeEncoder(new BlobBuilder()); + encoder.GenericMethodTypeParameter(index); + return encoder.Builder; + } + public BlobOrHandle GetGenericTypeParameter(EmptyGenericContext genericContext, int index) + { + var encoder = new SignatureTypeEncoder(new BlobBuilder()); + encoder.GenericTypeParameter(index); + return encoder.Builder; + } + public BlobOrHandle GetModifiedType(BlobOrHandle modifier, BlobOrHandle unmodifiedType, bool isRequired) + { + var builder = new BlobBuilder(); + if (isRequired) + { + builder.WriteByte((byte)SignatureTypeCode.RequiredModifier); + } + else + { + builder.WriteByte((byte)SignatureTypeCode.OptionalModifier); + } + unmodifiedType.WriteBlobTo(builder); + return builder; + } + public BlobOrHandle GetPinnedType(BlobOrHandle elementType) => throw new NotImplementedException(); + public BlobOrHandle GetPointerType(BlobOrHandle elementType) + { + var paramEncoder = new ParameterTypeEncoder(new BlobBuilder()); + paramEncoder.Type().Pointer(); + elementType.WriteBlobTo(paramEncoder.Builder); + return paramEncoder.Builder; + } + public BlobOrHandle GetPrimitiveType(PrimitiveTypeCode typeCode) + { + var paramEncoder = new ParameterTypeEncoder(new BlobBuilder()); + paramEncoder.Type().PrimitiveType(typeCode); + return paramEncoder.Builder; + } + public BlobOrHandle GetSZArrayType(BlobOrHandle elementType) + { + var paramEncoder = new ParameterTypeEncoder(new BlobBuilder()); + paramEncoder.Type().SZArray(); + elementType.WriteBlobTo(paramEncoder.Builder); + return paramEncoder.Builder; + } + public BlobOrHandle GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + { + return new BlobOrHandle(handle, rawTypeKind == (byte)SignatureTypeKind.ValueType); + } + public BlobOrHandle GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + { + return new BlobOrHandle(handle, rawTypeKind == (byte)SignatureTypeKind.ValueType); + } + + public BlobOrHandle GetTypeFromSpecification(MetadataReader reader, EmptyGenericContext genericContext, TypeSpecificationHandle handle, byte rawTypeKind) + { + return new BlobOrHandle(handle, rawTypeKind == (byte)SignatureTypeKind.ValueType); + } + + public struct EmptyGenericContext { } + + } + + private void ResolveAndRecordMemberReference(MemberReferenceEntity memberRef) + { + // We need to resolve a MemberReference in a few scenarios: + // 1. The MemberReference references a local MethodDefinition + // - This case may occur when a method is referenced by a property or event, which can only reference MethodDefinition entities + // TODO-COMPAT: The following scenarios are required for compat with the existing ILASM, but are not required to produce valid metadata: + // 2. The MemberReference refers to a local FieldDefinition + + var signature = memberRef.Signature.ToArray(); + SignatureHeader header = new(signature[0]); + if (header.Kind == SignatureKind.Method) + { + if (header.CallingConvention == SignatureCallingConvention.VarArgs) + { + UpdateMemberRefForVarargSignatures(memberRef, signature); + } + switch (memberRef.Parent) + { + // Use this weird construction to look up TypeDefs as we may change TypeRef resolution to use a similar model to MemberReference + // where we always return a TypeReference type, but it might just point to a TypeDef handle. + case TypeEntity { Handle.Kind: HandleKind.TypeDefinition } type: + { + var typeDef = (TypeDefinitionEntity)GetSeenEntities(TableIndex.TypeDef)[MetadataTokens.GetRowNumber(type.Handle) - 1]; + // Look on this type for methods with the same name and signature + foreach (var method in typeDef.Methods) + { + if (method.Name == memberRef.Name + && method.MethodSignature!.ContentEquals(memberRef.Signature)) + { + ((IHasHandle)memberRef).SetHandle(method.Handle); + return; + } + } + } + break; + } + } + RecordEntityInTable(TableIndex.MemberRef, memberRef); + } + + private void UpdateMemberRefForVarargSignatures(MemberReferenceEntity memberRef, byte[] signature) + { + var decoder = new SignatureDecoder(new SignatureRewriter(), null!, default); + BlobEncoder methodDefSig = new(new BlobBuilder()); + bool hasVarargParameters = false; + // TODO-SRM: Propose a public API to construct a blob reader over a byte array or ReadOnlyMemory + // to avoid the unsafe block. + // Alternatively, propose an API to get the corresponding MethodDefSig for a MethodRefSig and move all of this logic into SRM. + unsafe + { + fixed (byte* ptr = &signature[0]) + { + var reader = new BlobReader(ptr, signature.Length); + var methodSignature = decoder.DecodeMethodSignature(ref reader); + + if (methodSignature.RequiredParameterCount != methodSignature.ParameterTypes.Length) + { + hasVarargParameters = true; + + methodDefSig.MethodSignature(methodSignature.Header.CallingConvention, methodSignature.GenericParameterCount, methodSignature.Header.Attributes.HasFlag(SignatureAttributes.Instance)) + .Parameters(methodSignature.RequiredParameterCount, out var retTypeBuilder, out var parametersEncoder); + methodSignature.ReturnType.WriteBlobTo(retTypeBuilder.Builder); + for (int i = 0; i < methodSignature.RequiredParameterCount; i++) + { + methodSignature.ParameterTypes[i].WriteBlobTo(parametersEncoder.AddParameter().Builder); + } + } + } + } + + // If the method has vararg parameters, then this needs to be a MemberRef whose parent is a reference to the method with the signature without any vararg parameters. + if (hasVarargParameters) + { + var methodRef = new MemberReferenceEntity(memberRef.Parent, memberRef.Name, methodDefSig.Builder); + ResolveAndRecordMemberReference(methodRef); + memberRef.SetMemberRefParent(methodRef); + } + } + + public MethodSpecificationEntity GetOrCreateMethodSpecification(EntityBase method, BlobBuilder signature) + { + return GetOrCreateEntity( + (method, signature), + TableIndex.MethodSpec, + _seenMethodSpecs, + ((EntityBase method, BlobBuilder signature) value) => new(method, signature), + _ => { }); + } + + public StandaloneSignatureEntity GetOrCreateStandaloneSignature(BlobBuilder signature) + { + return GetOrCreateEntity(signature, TableIndex.StandAloneSig, _seenStandaloneSignatures, (sig) => new(sig), _ => { }); + } + + public DeclarativeSecurityAttributeEntity CreateDeclarativeSecurityAttribute(DeclarativeSecurityAction action, BlobBuilder permissionSet) + { + var entity = new DeclarativeSecurityAttributeEntity(action, permissionSet); + RecordEntityInTable(TableIndex.DeclSecurity, entity); + return entity; + } + + public CustomAttributeEntity CreateCustomAttribute(EntityBase constructor, BlobBuilder value) + { + var entity = new CustomAttributeEntity(constructor, value); + RecordEntityInTable(TableIndex.CustomAttribute, entity); + return entity; + } + + public static MethodImplementationEntity CreateUnrecordedMethodImplementation(MethodDefinitionEntity methodBody, MemberReferenceEntity methodDeclaration) + { + return new MethodImplementationEntity(methodBody, methodDeclaration); + } + + public FileEntity GetOrCreateFile(string name, bool hasMetadata, BlobBuilder? hash) + { + return GetOrCreateEntity(name, TableIndex.File, _seenFiles, (name) => new FileEntity(name), entity => + { + entity.HasMetadata = hasMetadata; + entity.Hash = hash; + }); + } + + public FileEntity? FindFile(string name) + { + if (_seenFiles.TryGetValue(name, out var file)) + { + return file; + } + return null; + } + + public AssemblyReferenceEntity? FindAssemblyReference(string name) + { + if (_seenAssemblyRefs.TryGetValue(new AssemblyName(name), out var file)) + { + return file; + } + return null; + } + + public AssemblyReferenceEntity GetOrCreateAssemblyReference(string name, Version version, string? culture, BlobBuilder? publicKeyOrToken, AssemblyFlags flags, ProcessorArchitecture architecture) + { + AssemblyName key = new AssemblyName(name) + { + Version = version, + CultureName = culture, + Flags = (AssemblyNameFlags)flags, +#pragma warning disable SYSLIB0037 // ProcessorArchitecture is obsolete + ProcessorArchitecture = architecture +#pragma warning restore SYSLIB0037 // ProcessorArchitecture is obsolete + }; + return GetOrCreateEntity(key, TableIndex.AssemblyRef, _seenAssemblyRefs, (value) => new AssemblyReferenceEntity(name), entity => + { + entity.Version = version; + entity.Culture = culture; + entity.PublicKeyOrToken = publicKeyOrToken; + entity.Flags = flags; + entity.ProcessorArchitecture = architecture; + }); + } + + public ManifestResourceEntity CreateManifestResource(string name, uint offset) + { + return CreateEntity(TableIndex.ManifestResource, _manifestResourceEntities, () => new ManifestResourceEntity(name, offset)); + } + + public ExportedTypeEntity GetOrCreateExportedType(EntityBase? implementation, string @namespace, string name, Action onCreateType) + { + // We only key on the implementation if the type is nested. + return GetOrCreateEntity((implementation as ExportedTypeEntity, @namespace, name), TableIndex.ExportedType, _seenExportedTypes, (key) => new(key.Item3, key.Item2, key.Item1), onCreateType); + } + + public ExportedTypeEntity? FindExportedType(ExportedTypeEntity? containingType, string @namespace, string @name) + { + if (_seenExportedTypes.TryGetValue((containingType, @namespace, name), out var typeDef)) + { + return typeDef; + } + return null; + } + + public IHasHandle? EntryPoint { get; set; } + + public abstract class EntityBase : IHasHandle + { + public EntityHandle Handle { get; private set; } + + protected virtual void SetHandle(EntityHandle token) + { + Handle = token; + } + + void IHasHandle.SetHandle(EntityHandle token) => SetHandle(token); + } + + public abstract class TypeEntity : EntityBase + { + } + + public interface IHasReflectionNotation + { + string ReflectionNotation { get; } + } + + // COMPAT: The ilasm grammar allows ModuleRefs and AssemblyRefs to be returned in addition to types in typeSpec rules and arbitrary tokens to be returned + // by the mdtoken grammar, which can replace a type amongst other things. + // We'll record the actual handle as callers might need to retrieve it, + // but for emit we'll use the specialized properties depending on the case where the invalid handle is used. + public sealed class FakeTypeEntity : TypeEntity + { + public FakeTypeEntity(EntityHandle realEntity) + { + ((IHasHandle)this).SetHandle(realEntity); + TypeColumnHandle = default(TypeDefinitionHandle); + ResolutionScopeColumnHandle = default(ModuleDefinitionHandle); + TypeSignatureHandle = MetadataTokens.TypeDefinitionHandle(MetadataTokens.GetRowNumber(realEntity)); + ImplementationHandle = default(AssemblyReferenceHandle); + } + + /// + /// For cases where an arbitrary (non-TypeDefOrRefOrSpec) token is referenced by a column in a metadata table that uses coded indices, the encoding fails, + /// ilasm asserts, and a nil token for 0 option (TypeDef) of the coded index is emitted. + /// + public EntityHandle TypeColumnHandle { get; } + + /// + /// For cases where an arbitrary (non-ResolutionScope) token is referenced by a column in a metadata table that uses coded indices, the encoding fails, + /// ilasm asserts, and a nil token for 0 option (ModuleDefinition) of the coded index is emitted. + /// + public EntityHandle ResolutionScopeColumnHandle { get; } + + /// + /// In cases where an arbitrary (non-TypeDefOrRefOrSpec) token is referenced in a metadata blob (like a signature blob), + /// the token is emitted as a compressed integer of the row entry, shifted to account for encoding the table type. + /// + public EntityHandle TypeSignatureHandle { get; } + + /// + /// In cases where an arbitrary (non-Implementation) token is referenced in a column in a metadata table, + /// the the token is emitted as the nil token. + /// + public EntityHandle ImplementationHandle { get; } + } + + public sealed class TypeDefinitionEntity : TypeEntity, IHasReflectionNotation + { + public TypeDefinitionEntity(TypeDefinitionEntity? containingType, string @namespace, string name) + { + ContainingType = containingType; + Namespace = @namespace; + Name = name; + + ReflectionNotation = CreateReflectionNotation(this); + + static string CreateReflectionNotation(TypeDefinitionEntity typeDefinition) + { + StringBuilder builder = new(); + Stack containingTypes = new(); + for (TypeDefinitionEntity? containingType = typeDefinition; containingType is not null; containingType = containingType.ContainingType) + { + containingTypes.Push(containingType); + } + while (containingTypes.Count != 0) + { + TypeDefinitionEntity containingType = containingTypes.Pop(); + builder.Append(containingType.Namespace); + builder.Append('.'); + builder.Append(containingType.Name); + if (containingTypes.Count > 0) + { + builder.Append('+'); + } + } + return builder.ToString(); + } + } + public TypeDefinitionEntity? ContainingType { get; } + public string Namespace { get; } + public string Name { get; } + public TypeAttributes Attributes { get; set; } + public TypeEntity? BaseType { get; set; } + + public NamedElementList GenericParameters { get; } = new(); + + // COMPAT: Save the list of generic parameter constraints here to ensure we can match ILASM's emit order for generic parameter constraints exactly. + public List GenericParameterConstraints { get; } = new(); + + public List Methods { get; } = new(); + + public List MethodImplementations { get; } = new(); + + public List Fields { get; } = new(); + + public List Properties { get; } = new(); + + public List Events { get; } = new(); + + public List InterfaceImplementations { get; } = new(); + + public string ReflectionNotation { get; } + } + + public sealed class TypeReferenceEntity(EntityBase resolutionScope, string @namespace, string name) : TypeEntity, IHasReflectionNotation + { + public EntityBase ResolutionScope { get; } = resolutionScope; + + public string Namespace { get; } = @namespace; + + public string Name { get; } = name; + + public string ReflectionNotation { get; set; } = string.Empty; + } + + public sealed class TypeSpecificationEntity(BlobBuilder signature) : TypeEntity + { + public BlobBuilder Signature { get; } = signature; + } + + public sealed class GenericParameterEntity(GenericParameterAttributes attributes, string name) : EntityBase, INamed + { + public GenericParameterAttributes Attributes { get; } = attributes; + + public EntityBase? Owner { get; set; } + + public int Index { get; set; } + + public List Constraints { get; } = new(); + + public string Name { get; } = name; + } + + public sealed class GenericParameterConstraintEntity(TypeEntity baseType) : EntityBase + { + public GenericParameterEntity? Owner { get; set; } + + public TypeEntity BaseType { get; } = baseType; + } + + public sealed class ModuleReferenceEntity(string name) : EntityBase + { + public string Name { get; } = name; + } + + public sealed class MethodDefinitionEntity(TypeDefinitionEntity containingType, string name) : EntityBase, IHasHandle + { + public TypeDefinitionEntity ContainingType { get; } = containingType; + public string Name { get; } = name; + + public MethodAttributes MethodAttributes { get; set; } + + public List Parameters { get; } = new(); + + public NamedElementList GenericParameters { get; } = new(); + + // COMPAT: Save the list of generic parameter constraints here to ensure we can match ILASM's emit order for generic parameter constraints exactly. + public List GenericParameterConstraints { get; } = new(); + + public SignatureHeader SignatureHeader { get; set; } + + public BlobBuilder? MethodSignature { get; set; } + + public StandaloneSignatureEntity? LocalsSignature { get; set; } + + public InstructionEncoder MethodBody { get; } = new(new BlobBuilder(), new ControlFlowBuilder()); + + public MethodBodyAttributes BodyAttributes { get; set; } + + public int MaxStack { get; set; } + + public (ModuleReferenceEntity ModuleName, string? EntryPointName, MethodImportAttributes Attributes)? MethodImportInformation { get; set; } + public MethodImplAttributes ImplementationAttributes { get; set; } + } + + public sealed class ParameterEntity(ParameterAttributes attributes, string? name, BlobBuilder marshallingDescriptor, int sequence) : EntityBase + { + public ParameterAttributes Attributes { get; } = attributes; + public string? Name { get; } = name; + public BlobBuilder MarshallingDescriptor { get; set; } = marshallingDescriptor; + public bool HasCustomAttributes { get; set; } + public int Sequence { get; } = sequence; + } + + public sealed class MemberReferenceEntity(EntityBase parent, string name, BlobBuilder signature) : EntityBase + { + // In the case of a MemberRef to a specific instantiation of a vararg method, we need to update the owner at emit time. + public EntityBase Parent { get; private set; } = parent; + public string Name { get; } = name; + public BlobBuilder Signature { get; } = signature; + + private readonly List _placesToWriteResolvedHandle = new(); + + public void RecordBlobToWriteResolvedHandle(Blob blob) + { + _placesToWriteResolvedHandle.Add(blob); + } + + protected override void SetHandle(EntityHandle token) + { + base.SetHandle(token); + // Now that we've set the handle, backpatch all the blobs that need to be updated. + // This way we can determine the right token to use for the member reference + // after we've processed all of the source code. + foreach (var blob in _placesToWriteResolvedHandle) + { + var writer = new BlobWriter(blob); + writer.WriteInt32(MetadataTokens.GetToken(token)); + } + } + + internal void SetMemberRefParent(MemberReferenceEntity parent) + { + Parent = parent; + } + } + + public sealed class MethodSpecificationEntity(EntityBase parent, BlobBuilder signature) : EntityBase + { + public EntityBase Parent { get; } = parent; + public BlobBuilder Signature { get; } = signature; + } + + public sealed class StandaloneSignatureEntity(BlobBuilder signature) : EntityBase + { + public BlobBuilder Signature { get; } = signature; + } + + public sealed class DeclarativeSecurityAttributeEntity(DeclarativeSecurityAction action, BlobBuilder permissionSet) : EntityBase + { + public EntityBase? Parent { get; set; } + public DeclarativeSecurityAction Action { get; } = action; + public BlobBuilder PermissionSet { get; } = permissionSet; + } + + public sealed class CustomAttributeEntity(EntityBase constructor, BlobBuilder value) : EntityBase + { + public EntityBase? Owner { get; set; } + public EntityBase Constructor { get; } = constructor; + public BlobBuilder Value { get; } = value; + } + + public sealed class MethodImplementationEntity(MethodDefinitionEntity methodBody, MemberReferenceEntity methodDeclaration) : EntityBase + { + public MethodDefinitionEntity MethodBody { get; } = methodBody; + public MemberReferenceEntity MethodDeclaration { get; } = methodDeclaration; + } + + public sealed class FieldDefinitionEntity(FieldAttributes attributes, TypeDefinitionEntity type, string name, BlobBuilder signature) : EntityBase + { + public FieldAttributes Attributes { get; } = attributes; + public TypeDefinitionEntity ContainingType { get; } = type; + public string Name { get; } = name; + public BlobBuilder Signature { get; } = signature; + + public BlobBuilder? MarshallingDescriptor { get; set; } + public string? DataDeclarationName { get; set; } + } + + public sealed class InterfaceImplementationEntity(TypeDefinitionEntity type, TypeEntity interfaceType) : EntityBase + { + public TypeDefinitionEntity Type { get; } = type; + public TypeEntity InterfaceType { get; } = interfaceType; + } + + public sealed class EventEntity(EventAttributes attributes, TypeEntity type, string name) : EntityBase + { + public EventAttributes Attributes { get; } = attributes; + public TypeEntity Type { get; } = type; + public string Name { get; } = name; + + public List<(MethodSemanticsAttributes Semantic, EntityBase Method)> Accessors { get; } = new(); + } + + public sealed class PropertyEntity(PropertyAttributes attributes, BlobBuilder type, string name) : EntityBase + { + public PropertyAttributes Attributes { get; } = attributes; + public BlobBuilder Type { get; } = type; + public string Name { get; } = name; + + public List<(MethodSemanticsAttributes Semantic, EntityBase Method)> Accessors { get; } = new(); + } + + public sealed class FileEntity(string name) : EntityBase + { + public string Name { get; } = name; + public bool HasMetadata { get; set; } + public BlobBuilder? Hash { get; set; } + } + + public sealed class ModuleEntity : EntityBase + { + public string? Name { get; set; } + } + + public abstract class AssemblyOrRefEntity(string name) : EntityBase + { + public string Name { get; set; } = name; + public Version? Version { get; set; } + public string? Culture { get; set; } + public BlobBuilder? PublicKeyOrToken { get; set; } + public AssemblyFlags Flags { get; set; } + public ProcessorArchitecture ProcessorArchitecture { get; set; } + } + + public sealed class AssemblyEntity(string name) : AssemblyOrRefEntity(name) + { + public AssemblyHashAlgorithm HashAlgorithm { get; set; } + } + + public sealed class AssemblyReferenceEntity(string name) : AssemblyOrRefEntity(name) + { + public BlobBuilder? Hash { get; set; } + } + + public sealed class ManifestResourceEntity(string name, uint offset) : EntityBase + { + public string Name { get; } = name; + public uint Offset { get; } = offset; + public ManifestResourceAttributes Attributes { get; set; } + public EntityBase? Implementation { get; set; } + } + + public sealed class ExportedTypeEntity : EntityBase + { + public ExportedTypeEntity(string name, string @namespace, EntityBase? implementation) + { + Name = name; + Namespace = @namespace; + Implementation = implementation; + } + + public string Name { get; } + public string Namespace { get; } + public TypeAttributes Attributes { get; set; } + public EntityBase? Implementation { get; } + + public int TypeDefinitionId { get; set; } + } + } +} diff --git a/src/tools/ilasm/src/ILAssembler/GrammarVisitor.cs b/src/tools/ilasm/src/ILAssembler/GrammarVisitor.cs new file mode 100644 index 00000000000000..d7db285d256924 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/GrammarVisitor.cs @@ -0,0 +1,4348 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Xml.XPath; +using Antlr4.Runtime; +using Antlr4.Runtime.Misc; +using Antlr4.Runtime.Tree; + +namespace ILAssembler +{ + internal abstract record GrammarResult + { + protected GrammarResult() { } + + public sealed record String(string Value) : GrammarResult; + + public sealed record Literal(T Value) : GrammarResult; + + public sealed record Sequence(ImmutableArray Value) : GrammarResult; + + /// + /// A formatted blob of bytes. + /// + /// The bytes of the blob. + public sealed record FormattedBlob(BlobBuilder Value) : GrammarResult; + + public sealed record SentinelValue + { + public static SentinelValue Instance { get; } = new(); + + public static Literal Result { get; } = new(Instance); + } + + public sealed record Flag(T Value, bool ShouldAppend = true) : GrammarResult + where T : struct, Enum + { + private readonly T _groupMask; + public Flag(T value, bool shouldAppend, T groupMask) + : this(value, shouldAppend) + { + _groupMask = groupMask; + } + public Flag(T value, T groupMask) + : this(value) + { + _groupMask = groupMask; + } + + public static T operator |(T lhs, Flag rhs) + { + if (!rhs.ShouldAppend) + { + return rhs.Value; + } + return (T)(object)(((int)(object)lhs & (~(int)(object)rhs._groupMask)) | (int)(object)rhs.Value); + } + } + } + +#pragma warning disable CA1822 // Mark members as static + internal sealed class GrammarVisitor : ICILVisitor + { + private const string NodeShouldNeverBeDirectlyVisited = "This node should never be directly visited. It should be directly processed by its parent node."; + private readonly ImmutableArray.Builder _diagnostics = ImmutableArray.CreateBuilder(); + private readonly EntityRegistry _entityRegistry = new(); + private readonly IReadOnlyDictionary _documents; + private readonly Options _options; + private readonly MetadataBuilder _metadataBuilder = new(); + private readonly Func _resourceLocator; + + // Record the mapped field data directly into the blob to ensure we preserve ordering + private readonly BlobBuilder _mappedFieldData = new(); + private readonly Dictionary _mappedFieldDataNames = new(); + private readonly Dictionary> _mappedFieldDataReferenceFixups = new(); + private readonly BlobBuilder _manifestResources = new(); + + public GrammarVisitor(IReadOnlyDictionary documents, Options options, Func resourceLocator) + { + _documents = documents; + _options = options; + _resourceLocator = resourceLocator; + } + + public (ImmutableArray Diagnostics, PEBuilder? Image) BuildImage() + { + if (_diagnostics.Any(diag => diag.Severity == DiagnosticSeverity.Error)) + { + return (_diagnostics.ToImmutable(), null); + } + + BlobBuilder ilStream = new(); + _entityRegistry.WriteContentTo(_metadataBuilder, ilStream); + MetadataRootBuilder rootBuilder = new(_metadataBuilder); + PEHeaderBuilder header = new( + fileAlignment: _alignment, + imageBase: (ulong)_imageBase, + subsystem: _subsystem); + + MethodDefinitionHandle entryPoint = default; + if (_entityRegistry.EntryPoint is not null) + { + entryPoint = (MethodDefinitionHandle)_entityRegistry.EntryPoint.Handle; + } + + ManagedPEBuilder peBuilder = new( + header, + rootBuilder, + ilStream, + _mappedFieldData, + _manifestResources, + flags: CorFlags.ILOnly, entryPoint: entryPoint); + + return (_diagnostics.ToImmutable(), peBuilder); + } + + public GrammarResult Visit(IParseTree tree) => tree.Accept(this); + + GrammarResult ICILVisitor.VisitAlignment(CILParser.AlignmentContext context) => VisitAlignment(context); + public GrammarResult.Literal VisitAlignment(CILParser.AlignmentContext context) + { + return VisitInt32(context.int32()); + } + + GrammarResult ICILVisitor.VisitAsmAttr(CILParser.AsmAttrContext context) => VisitAsmAttr(context); + public GrammarResult.Literal VisitAsmAttr(CILParser.AsmAttrContext context) + => new(context.asmAttrAny().Select(VisitAsmAttrAny).Aggregate((AssemblyFlags)0, (lhs, rhs) => lhs | rhs)); + GrammarResult ICILVisitor.VisitAsmAttrAny(CILParser.AsmAttrAnyContext context) => VisitAsmAttrAny(context); + public GrammarResult.Flag VisitAsmAttrAny(CILParser.AsmAttrAnyContext context) + { + return context.GetText() switch + { + "retargetable" => new(AssemblyFlags.Retargetable), + "windowsruntime" => new(AssemblyFlags.WindowsRuntime), + "noplatform" => new(AssemblyFlags.NoPlatform), + "legacy library" => new(0), + "cil" => new(GetFlagForArch(ProcessorArchitecture.MSIL), AssemblyFlags.ArchitectureMask), + "x86" => new(GetFlagForArch(ProcessorArchitecture.X86), AssemblyFlags.ArchitectureMask), + "amd64" => new(GetFlagForArch(ProcessorArchitecture.Amd64), AssemblyFlags.ArchitectureMask), + "arm" => new(GetFlagForArch(ProcessorArchitecture.Arm), AssemblyFlags.ArchitectureMask), + "arm64" => new(GetFlagForArch((ProcessorArchitecture)6), AssemblyFlags.ArchitectureMask), + _ => throw new UnreachableException() + }; + } + + private static AssemblyFlags GetFlagForArch(ProcessorArchitecture arch) + { + return (AssemblyFlags)((int)arch << 4); + } + + private static (ProcessorArchitecture, AssemblyFlags) GetArchAndFlags(AssemblyFlags flags) + { + var arch = (ProcessorArchitecture)(((int)flags & 0xF0) >> 4); + var newFlags = flags & ~((AssemblyFlags)((int)arch << 4)); + return (arch, newFlags); + } + + private EntityRegistry.AssemblyOrRefEntity? _currentAssemblyOrRef; + public GrammarResult VisitAsmOrRefDecl(CILParser.AsmOrRefDeclContext context) + { + Debug.Assert(_currentAssemblyOrRef is not null); + + if (context.customAttrDecl() is { } attr) + { + var customAttr = VisitCustomAttrDecl(attr).Value; + customAttr?.Owner = _currentAssemblyOrRef; + return GrammarResult.SentinelValue.Result; + } + + string decl = context.GetChild(0).GetText(); + if (decl == ".publicKey") + { + BlobBuilder blob = new(); + blob.WriteBytes(VisitBytes(context.bytes()).Value); + _currentAssemblyOrRef!.PublicKeyOrToken = blob; + } + else if (decl == ".ver") + { + var versionComponents = context.intOrWildcard(); + _currentAssemblyOrRef!.Version = new Version( + VisitIntOrWildcard(versionComponents[0]).Value ?? 0, + VisitIntOrWildcard(versionComponents[1]).Value ?? 0, + VisitIntOrWildcard(versionComponents[2]).Value ?? 0, + VisitIntOrWildcard(versionComponents[3]).Value ?? 0); + } + else if (decl == ".locale") + { + _currentAssemblyOrRef!.Culture = context.compQstring() is { } compQstring + ? VisitCompQstring(compQstring).Value + : Encoding.Unicode.GetString([.. VisitBytes(context.bytes()).Value]); + } + return GrammarResult.SentinelValue.Result; + } + + GrammarResult ICILVisitor.VisitAssemblyBlock(CILParser.AssemblyBlockContext context) => VisitAssemblyBlock(context); + public GrammarResult VisitAssemblyBlock(CILParser.AssemblyBlockContext context) + { + _entityRegistry.Assembly ??= new EntityRegistry.AssemblyEntity(VisitDottedName(context.dottedName()).Value); + var attr = VisitAsmAttr(context.asmAttr()).Value; + (_entityRegistry.Assembly.ProcessorArchitecture, _entityRegistry.Assembly.Flags) = GetArchAndFlags(attr); + foreach (var decl in context.assemblyDecls().assemblyDecl()) + { + VisitAssemblyDecl(decl); + } + return GrammarResult.SentinelValue.Result; + } + + GrammarResult ICILVisitor.VisitAssemblyDecl(CILParser.AssemblyDeclContext context) => VisitAssemblyDecl(context); + public GrammarResult VisitAssemblyDecl(CILParser.AssemblyDeclContext context) + { + if (context.secDecl() is { } secDecl) + { + var declarativeSecurity = VisitSecDecl(secDecl); + if (declarativeSecurity.Value is { } sec) + { + sec.Parent = _entityRegistry.Assembly; + } + } + else if (context.int32() is { } hashAlg) + { + _entityRegistry.Assembly!.HashAlgorithm = (AssemblyHashAlgorithm)VisitInt32(hashAlg).Value; + } + else if (context.asmOrRefDecl() is { } asmOrRef) + { + _currentAssemblyOrRef = _entityRegistry.Assembly; + VisitAsmOrRefDecl(asmOrRef); + _currentAssemblyOrRef = null; + } + return GrammarResult.SentinelValue.Result; + } + public GrammarResult VisitAssemblyDecls(CILParser.AssemblyDeclsContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + public GrammarResult VisitAssemblyRefDecl(CILParser.AssemblyRefDeclContext context) + { + if (context.asmOrRefDecl() is { } asmOrRef) + { + VisitAsmOrRefDecl(asmOrRef); + } + string decl = context.GetChild(0).GetText(); + if (decl == ".hash") + { + var blob = new BlobBuilder(); + blob.WriteBytes(VisitBytes(context.bytes()).Value); + ((EntityRegistry.AssemblyReferenceEntity)_currentAssemblyOrRef!).Hash = blob; + } + if (decl == ".publickeytoken") + { + var blob = new BlobBuilder(); + blob.WriteBytes(VisitBytes(context.bytes()).Value); + _currentAssemblyOrRef!.PublicKeyOrToken = blob; + } + return GrammarResult.SentinelValue.Result; + } + public GrammarResult VisitAssemblyRefDecls(CILParser.AssemblyRefDeclsContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitAssemblyRefHead(CILParser.AssemblyRefHeadContext context) => VisitAssemblyRefHead(context); + public GrammarResult.Literal VisitAssemblyRefHead(CILParser.AssemblyRefHeadContext context) + { + var (arch, flags) = GetArchAndFlags(VisitAsmAttr(context.asmAttr()).Value); + var dottedNames = context.dottedName(); + string name = VisitDottedName(dottedNames[0]).Value; + string alias = name; + if (dottedNames.Length > 1) + { + alias = VisitDottedName(dottedNames[1]).Value; + } + return new(_entityRegistry.GetOrCreateAssemblyReference(alias, asmref => + { + asmref.Name = name; + asmref.Flags = flags; + asmref.ProcessorArchitecture = arch; + })); + } + + GrammarResult ICILVisitor.VisitAtOpt(CILParser.AtOptContext context) => VisitAtOpt(context); + public static GrammarResult.Literal VisitAtOpt(CILParser.AtOptContext context) => context.id() is {} id ? new(VisitId(id).Value) : new(null); + + GrammarResult ICILVisitor.VisitBoolSeq(CILParser.BoolSeqContext context) => VisitBoolSeq(context); + public static GrammarResult.FormattedBlob VisitBoolSeq(CILParser.BoolSeqContext context) + { + var builder = ImmutableArray.CreateBuilder(); + + foreach (var item in context.truefalse()) + { + builder.AddRange(VisitTruefalse(item).Value); + } + + return new(builder.ToImmutable().SerializeSequence()); + } + GrammarResult ICILVisitor.VisitBound(CILParser.BoundContext context) => VisitBound(context); + public GrammarResult.Literal<(int? Lower, int? Upper)> VisitBound(CILParser.BoundContext context) + { + bool hasEllipsis = context.ELLIPSIS() is not null; + if (context.ChildCount == 0 || (context.ChildCount == 1 && hasEllipsis)) + { + return new((null, null)); + } + + var indicies = context.int32(); + + int firstValue = VisitInt32(indicies[0]).Value; + + return (indicies.Length, hasEllipsis) switch + { + (1, false) => new((0, firstValue)), + (1, true) => new((firstValue, null)), + (2, false) => new((firstValue, VisitInt32(indicies[1]).Value - firstValue + 1)), + _ => throw new UnreachableException() + }; + } + + GrammarResult ICILVisitor.VisitBounds(CILParser.BoundsContext context) => VisitBounds(context); + public GrammarResult.Sequence<(int? Lower, int? Upper)> VisitBounds(CILParser.BoundsContext context) + { + return new(context.bound().Select(bound => VisitBound(bound).Value).ToImmutableArray()); + } + + GrammarResult ICILVisitor.VisitBytes(CILParser.BytesContext context) => VisitBytes(context); + public static GrammarResult.Sequence VisitBytes(CILParser.BytesContext context) + { + var builder = ImmutableArray.CreateBuilder(); + + foreach (var item in context.hexbytes()) + { + builder.AddRange(VisitHexbytes(item).Value); + } + + return new(builder.ToImmutable()); + } + GrammarResult ICILVisitor.VisitCallConv(CILParser.CallConvContext context) => VisitCallConv(context); + public GrammarResult.Literal VisitCallConv(CILParser.CallConvContext context) + { + if (context.callKind() is CILParser.CallKindContext callKind) + { + return new((byte)VisitCallKind(callKind).Value); + } + else if (context.int32() is CILParser.Int32Context int32) + { + return new((byte)VisitInt32(int32).Value); + } + else if (context.INSTANCE() is not null) + { + return new((byte)(VisitCallConv(context.callConv()).Value | (byte)SignatureAttributes.Instance)); + } + else if (context.EXPLICIT() is not null) + { + return new((byte)(VisitCallConv(context.callConv()).Value | (byte)SignatureAttributes.ExplicitThis)); + } + return new(0); + } + GrammarResult ICILVisitor.VisitCallKind(CILParser.CallKindContext context) => VisitCallKind(context); + public GrammarResult.Literal VisitCallKind(CILParser.CallKindContext context) + { + int childType = context.GetChild(context.ChildCount - 1).Symbol.Type; + return new(childType switch + { + CILParser.DEFAULT => SignatureCallingConvention.Default, + CILParser.VARARG => SignatureCallingConvention.VarArgs, + CILParser.CDECL => SignatureCallingConvention.CDecl, + CILParser.STDCALL => SignatureCallingConvention.StdCall, + CILParser.THISCALL => SignatureCallingConvention.ThisCall, + CILParser.FASTCALL => SignatureCallingConvention.FastCall, + CILParser.UNMANAGED => SignatureCallingConvention.Unmanaged, + _ => throw new UnreachableException() + }); + } + + GrammarResult ICILVisitor.VisitCatchClause(CILParser.CatchClauseContext context) => VisitCatchClause(context); + public GrammarResult.Literal VisitCatchClause(CILParser.CatchClauseContext context) => VisitTypeSpec(context.typeSpec()); + + GrammarResult ICILVisitor.VisitCaValue(CILParser.CaValueContext context) => VisitCaValue(context); + public GrammarResult.FormattedBlob VisitCaValue(CILParser.CaValueContext context) + { + BlobBuilder blob = new(); + if (context.truefalse() is CILParser.TruefalseContext truefalse) + { + blob.WriteByte((byte)SerializationTypeCode.Boolean); + blob.WriteBoolean(VisitTruefalse(truefalse).Value); + } + else if (context.compQstring() is CILParser.CompQstringContext str) + { + blob.WriteUTF8(VisitCompQstring(str).Value); + blob.WriteByte(0); + } + else if (context.className() is CILParser.ClassNameContext className) + { + var name = VisitClassName(className).Value; + blob.WriteByte((byte)SerializationTypeCode.Enum); + blob.WriteUTF8((name as EntityRegistry.IHasReflectionNotation)?.ReflectionNotation ?? ""); + blob.WriteByte(0); + byte size = 4; + if (context.INT8() is not null) + { + size = 1; + } + else if (context.INT16() is not null) + { + size = 2; + } + blob.WriteByte(size); + blob.WriteInt32(VisitInt32(context.int32()).Value); + } + else + { + blob.WriteByte((byte)SerializationTypeCode.Int32); + blob.WriteInt32(VisitInt32(context.int32()).Value); + } + return new(blob); + } + + public GrammarResult VisitChildren(IRuleNode node) + { + for (int i = 0; i < node.ChildCount; i++) + { + node.GetChild(i).Accept(this); + } + return GrammarResult.SentinelValue.Result; + } + GrammarResult ICILVisitor.VisitClassAttr(CILParser.ClassAttrContext context) => VisitClassAttr(context); + + public GrammarResult.Literal<(GrammarResult.Flag Attribute, EntityRegistry.WellKnownBaseType? FallbackBase, bool RequireSealed)> VisitClassAttr(CILParser.ClassAttrContext context) + { + if (context.int32() is CILParser.Int32Context int32) + { + int value = VisitInt32(int32).Value; + // COMPAT: The VALUE and ENUM keywords use sentinel values to pass through the fallback base type + // in ILASM. These sentinel values can be provided through the "pass the value of the flag" feature, + // so we detect those old flags here and provide the correct fallback type. + bool requireSealed = false; + EntityRegistry.WellKnownBaseType? fallbackBase = null; + if ((value & 0x80000000) != 0) + { + requireSealed = true; + fallbackBase = EntityRegistry.WellKnownBaseType.System_ValueType; + } + if ((value & 0x40000000) != 0) + { + fallbackBase = EntityRegistry.WellKnownBaseType.System_Enum; + } + // Mask off the sentinel bits + value &= unchecked((int)~0xC0000000); + // COMPAT: When explicit flags are provided they always supercede previously set flags + // (other than the sentinel values) + return new((new((TypeAttributes)value, ShouldAppend: false), fallbackBase, requireSealed)); + } + + if (context.ENUM() is not null) + { + // COMPAT: ilasm implies the Sealed flag when using the 'value' keyword in a type declaration + // even when the 'enum' keyword is used. + return new((new(context.VALUE() is not null ? TypeAttributes.Sealed : 0), EntityRegistry.WellKnownBaseType.System_Enum, false)); + } + else if (context.VALUE() is not null) + { + // COMPAT: ilasm implies the Sealed flag when using the 'value' keyword in a type declaration + return new((new(TypeAttributes.Sealed), EntityRegistry.WellKnownBaseType.System_ValueType, true)); + } + + switch (context.GetText()) + { + case "private": + return new((new(TypeAttributes.NotPublic), null, false)); + case "ansi": + return new((new(TypeAttributes.AnsiClass), null, false)); + case "autochar": + return new((new(TypeAttributes.AutoClass), null, false)); + case "auto": + return new((new(TypeAttributes.AutoLayout), null, false)); + case "sequential": + return new((new(TypeAttributes.SequentialLayout), null, false)); + case "explicit": + return new((new(TypeAttributes.ExplicitLayout), null, false)); + case "extended": + return new((new(TypeAttributes.ExtendedLayout), null, false)); + default: + return new((new((TypeAttributes)Enum.Parse(typeof(TypeAttributes), context.GetText(), true)), null, false)); + } + } + + private sealed class CurrentMethodContext + { + public CurrentMethodContext(EntityRegistry.MethodDefinitionEntity definition) + { + Definition = definition; + } + + public EntityRegistry.MethodDefinitionEntity Definition { get; } + + public Dictionary Labels { get; } = new(); + + public Dictionary ArgumentNames { get; } = new(); + + public List> LocalsScopes { get; } = new(); + + public List AllLocals { get; } = new(); + } + + private CurrentMethodContext? _currentMethod; + + public GrammarResult VisitClassDecl(CILParser.ClassDeclContext context) + { + if (context.classHead() is CILParser.ClassHeadContext classHead) + { + _currentTypeDefinition.Push(VisitClassHead(classHead).Value); + VisitClassDecls(context.classDecls()); + _currentTypeDefinition.Pop(); + } + else if (context.methodHead() is CILParser.MethodHeadContext methodHead) + { + _currentMethod = new(VisitMethodHead(methodHead).Value); + VisitMethodDecls(context.methodDecls()); + _currentMethod = null; + } + else if (context.secDecl() is {} secDecl) + { + var declarativeSecurity = VisitSecDecl(secDecl).Value; + declarativeSecurity?.Parent = _currentTypeDefinition.PeekOrDefault(); + } + else if (context.fieldDecl() is {} fieldDecl) + { + _ = VisitFieldDecl(fieldDecl); + } + + return GrammarResult.SentinelValue.Result; + } + public GrammarResult VisitClassDecls(CILParser.ClassDeclsContext context) => VisitChildren(context); + + + GrammarResult ICILVisitor.VisitClassHead(CILParser.ClassHeadContext context) => VisitClassHead(context); + public GrammarResult.Literal VisitClassHead(CILParser.ClassHeadContext context) + { + string typeFullName = VisitDottedName(context.dottedName()).Value; + int typeFullNameLastDot = typeFullName.LastIndexOf('.'); + string typeNS; + if (_currentTypeDefinition.Count != 0) + { + if (typeFullNameLastDot == -1) + { + typeNS = string.Empty; + } + else + { + typeNS = typeFullName.Substring(0, typeFullNameLastDot); + } + } + else + { + if (typeFullNameLastDot == -1) + { + typeNS = _currentNamespace.PeekOrDefault() ?? string.Empty; + } + else + { + typeNS = $"{_currentNamespace.PeekOrDefault()}{typeFullName.Substring(0, typeFullNameLastDot)}"; + } + } + + bool isNewType = false; + + var typeDefinition = _entityRegistry.GetOrCreateTypeDefinition( + _currentTypeDefinition.PeekOrDefault(), + typeNS, + typeFullNameLastDot != -1 + ? typeFullName.Substring(typeFullNameLastDot) + : typeFullName, + (newTypeDef) => + { + isNewType = true; + EntityRegistry.WellKnownBaseType? fallbackBase = _options.NoAutoInherit ? null : EntityRegistry.WellKnownBaseType.System_Object; + bool requireSealed = false; + newTypeDef.Attributes = context.classAttr().Select(VisitClassAttr).Aggregate( + (TypeAttributes)0, + (acc, result) => + { + var (attribute, implicitBase, attrRequireSealed) = result.Value; + if (implicitBase is not null) + { + // COMPAT: Any base type specified by an attribute is ignored if + // the user specified an explicit base type in an 'extends' clause. + fallbackBase = implicitBase; + } + // COMPAT: When a flags value is specified as an integer, it overrides + // all of the provided flags, including any compat sentinel flags that will require + // the sealed modifier to be provided. + if (!attribute.ShouldAppend) + { + requireSealed = attrRequireSealed; + return attribute.Value; + } + requireSealed |= attrRequireSealed; + if (TypeAttributes.LayoutMask.HasFlag(attribute.Value)) + { + return (acc & ~TypeAttributes.LayoutMask) | attribute.Value; + } + if (TypeAttributes.StringFormatMask.HasFlag(attribute.Value)) + { + return (acc & ~TypeAttributes.StringFormatMask) | attribute.Value; + } + if (TypeAttributes.VisibilityMask.HasFlag(attribute.Value)) + { + return (acc & ~TypeAttributes.VisibilityMask) | attribute.Value; + } + if (attribute.Value == TypeAttributes.RTSpecialName) + { + // COMPAT: ILASM ignores the rtspecialname directive on a type. + return acc; + } + if (attribute.Value == TypeAttributes.Interface) + { + // COMPAT: interface implies abstract + return acc | TypeAttributes.Interface | TypeAttributes.Abstract; + } + + return acc | attribute.Value; + }); + + + for (int i = 0; i < VisitTyparsClause(context.typarsClause()).Value.Length; i++) + { + EntityRegistry.GenericParameterEntity? param = VisitTyparsClause(context.typarsClause()).Value[i]; + param.Owner = newTypeDef; + param.Index = i; + newTypeDef.GenericParameters.Add(param); + foreach (var constraint in param.Constraints) + { + constraint.Owner = param; + newTypeDef.GenericParameterConstraints.Add(constraint); + } + } + + // Temporarily push the new type as the current type definition so we can resolve type parameters + // that are used in the base type and interface types. + _currentTypeDefinition.Push(newTypeDef); + + if (context.extendsClause() is CILParser.ExtendsClauseContext extends) + { + newTypeDef.BaseType = VisitExtendsClause(context.extendsClause()).Value; + } + + if (context.implClause() is CILParser.ImplClauseContext impl) + { + newTypeDef.InterfaceImplementations.AddRange(VisitImplClause(context.implClause()).Value); + } + + _currentTypeDefinition.Pop(); + + newTypeDef.BaseType ??= _entityRegistry.ResolveImplicitBaseType(fallbackBase); + + // When the user has provided a type definition for a type that directly inherits + // System.ValueType but has not sealed it, emit a warning and add the 'sealed' modifier. + if (!newTypeDef.Attributes.HasFlag(TypeAttributes.Sealed) && + (requireSealed // COMPAT: when both the sentinel values for 'value' and 'enum' are explicitly + // specified, the sealed modifier is required even though + // the base type isn't System.ValueType. + || _entityRegistry.SystemValueTypeType.Equals(newTypeDef.BaseType))) + { + _diagnostics.Add( + new Diagnostic( + DiagnosticIds.UnsealedValueType, + DiagnosticSeverity.Error, + string.Format(DiagnosticMessageTemplates.UnsealedValueType, newTypeDef.Name), + Location.From(context.dottedName().Stop, _documents))); + newTypeDef.Attributes |= TypeAttributes.Sealed; + } + }); + + if (!isNewType) + { + // COMPAT: Still visit some of the clauses to ensure the provided types are still imported, + // even if unused. + _ = context.extendsClause()?.Accept(this); + _ = context.typarsClause().Accept(this); + } + + return new(typeDefinition); + } + + GrammarResult ICILVisitor.VisitClassName(CILParser.ClassNameContext context) => VisitClassName(context); + public GrammarResult.Literal VisitClassName(CILParser.ClassNameContext context) + { + if (context.THIS() is not null) + { + if (_currentTypeDefinition.Count == 0) + { + // TODO: Report diagnostic. + return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle))); + } + var thisType = _currentTypeDefinition.Peek(); + return new(thisType); + } + else if (context.BASE() is not null) + { + if (_currentTypeDefinition.Count == 0) + { + // TODO: Report diagnostic. + return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle))); + } + var baseType = _currentTypeDefinition.Peek().BaseType; + if (baseType is null) + { + // TODO: Report diagnostic. + return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle))); + } + return new(baseType); + } + else if (context.NESTER() is not null) + { + if (_currentTypeDefinition.Count < 2) + { + // TODO: Report diagnostic. + return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle))); + } + var nesterType = _currentTypeDefinition.Peek().ContainingType!; + return new(nesterType); + } + else if (context.slashedName() is CILParser.SlashedNameContext slashedName) + { + EntityRegistry.EntityBase? resolutionContext = null; + if (context.dottedName() is CILParser.DottedNameContext dottedAssemblyOrModuleName) + { + if (context.MODULE() is not null) + { + resolutionContext = _entityRegistry.FindModuleReference(VisitDottedName(dottedAssemblyOrModuleName).Value); + if (resolutionContext is null) + { + // TODO: Report diagnostic + return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle))); + } + } + else + { + resolutionContext = _entityRegistry.GetOrCreateAssemblyReference(VisitDottedName(dottedAssemblyOrModuleName).Value, newRef => { }); + } + } + else if (context.mdtoken() is CILParser.MdtokenContext typeRefScope) + { + resolutionContext = VisitMdtoken(typeRefScope).Value; + } + else if (context.PTR() is not null) + { + resolutionContext = new EntityRegistry.FakeTypeEntity(default(ModuleDefinitionHandle)); + } + + if (resolutionContext is not null) + { + EntityRegistry.TypeReferenceEntity typeRef = _entityRegistry.GetOrCreateTypeReference(resolutionContext, VisitSlashedName(slashedName).Value); + return new(typeRef); + } + + Debug.Assert(resolutionContext is null); + + return new(ResolveTypeDef()); + + // Resolve typedef references + EntityRegistry.TypeEntity ResolveTypeDef() + { + TypeName typeName = VisitSlashedName(slashedName).Value; + if (typeName.ContainingTypeName is null) + { + // TODO: Check for typedef. + } + Stack containingTypes = new(); + for (TypeName? containingType = typeName; containingType is not null; containingType = containingType.ContainingTypeName) + { + containingTypes.Push(containingType); + } + EntityRegistry.TypeDefinitionEntity? typeDef = null; + while (containingTypes.Count != 0) + { + TypeName containingType = containingTypes.Pop(); + + (string ns, string name) = NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName); + + typeDef = _entityRegistry.FindTypeDefinition( + typeDef, + ns, + name); + + if (typeDef is null) + { + // TODO: Report diagnostic for missing type name + return new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)); + } + } + + return typeDef!; + } + } + else if (context.mdtoken() is CILParser.MdtokenContext typeToken) + { + EntityRegistry.EntityBase resolvedToken = VisitMdtoken(typeToken).Value; + + if (resolvedToken is not EntityRegistry.TypeEntity type) + { + return new(new EntityRegistry.FakeTypeEntity(resolvedToken.Handle)); + } + return new(type); + } + + throw new UnreachableException(); + } + + GrammarResult ICILVisitor.VisitClassSeq(CILParser.ClassSeqContext context) => VisitClassSeq(context); + public GrammarResult.FormattedBlob VisitClassSeq(CILParser.ClassSeqContext context) + { + // We're going to add all of the elements in the sequence as prefix blobs to this blob. + BlobBuilder objSeqBlob = new(0); + foreach (var item in context.classSeqElement()) + { + objSeqBlob.LinkPrefix(VisitClassSeqElement(item).Value); + } + return new(objSeqBlob); + } + + GrammarResult ICILVisitor.VisitClassSeqElement(CILParser.ClassSeqElementContext context) => VisitClassSeqElement(context); + + public GrammarResult.FormattedBlob VisitClassSeqElement(CILParser.ClassSeqElementContext context) + { + BlobBuilder blob = new(); + if (context.className() is CILParser.ClassNameContext className) + { + if (VisitClassName(className).Value is EntityRegistry.IHasReflectionNotation notation) + { + blob.WriteSerializedString(notation.ReflectionNotation); + } + else + { + blob.WriteSerializedString(""); + } + return new(blob); + } + + blob.WriteSerializedString(context.SQSTRING()?.Symbol.Text); + return new(blob); + } + public GrammarResult VisitCompControl(CILParser.CompControlContext context) + { + // All compilation control directives that need special handling will be handled + // directly in the token stream before parsing. + // Any that reach here can be ignored. + return GrammarResult.SentinelValue.Result; + } + + GrammarResult ICILVisitor.VisitCompQstring(CILParser.CompQstringContext context) + { + return VisitCompQstring(context); + } + + private static GrammarResult.String VisitCompQstring(CILParser.CompQstringContext context) + { + StringBuilder builder = new(); + foreach (var item in context.QSTRING()) + { + builder.Append(item.Symbol.Text); + } + return new(builder.ToString()); + } + + GrammarResult ICILVisitor.VisitCorflags(CILParser.CorflagsContext context) => VisitCorflags(context); + public GrammarResult.Literal VisitCorflags(CILParser.CorflagsContext context) => VisitInt32(context.int32()); + + GrammarResult ICILVisitor.VisitCustomAttrDecl(CILParser.CustomAttrDeclContext context) => VisitCustomAttrDecl(context); + public GrammarResult.Literal VisitCustomAttrDecl(CILParser.CustomAttrDeclContext context) + { + if (context.dottedName() is {}) + { + // TODO: typedef + return new(null); + } + if (context.customDescrWithOwner() is {} descrWithOwner) + { + // Visit the custom attribute descriptor to record it, + // but don't return it as it will already have its owner recorded. + _ = VisitCustomDescrWithOwner(descrWithOwner); + return new(null); + } + if (context.customDescr() is {} descr) + { +#nullable disable // Disable nullability to work around lack of variance. + return VisitCustomDescr(descr); +#nullable restore + } + throw new UnreachableException(); + } + + GrammarResult ICILVisitor.VisitCustomBlobArgs(CILParser.CustomBlobArgsContext context) => VisitCustomBlobArgs(context); + public GrammarResult.FormattedBlob VisitCustomBlobArgs(CILParser.CustomBlobArgsContext context) + { + BlobBuilder blob = new(); + foreach (var item in context.serInit()) + { + VisitSerInit(item).Value.WriteContentTo(blob); + } + return new(blob); + } + + private const int CustomAttributeBlobFormatVersion = 1; + + GrammarResult ICILVisitor.VisitCustomBlobDescr(CILParser.CustomBlobDescrContext context) => VisitCustomBlobDescr(context); + public GrammarResult.FormattedBlob VisitCustomBlobDescr(CILParser.CustomBlobDescrContext context) + { + var blob = new BlobBuilder(); + blob.WriteInt32(CustomAttributeBlobFormatVersion); + VisitCustomBlobArgs(context.customBlobArgs()).Value.WriteContentTo(blob); + VisitCustomBlobNVPairs(context.customBlobNVPairs()).Value.WriteContentTo(blob); + return new(blob); + } + + GrammarResult ICILVisitor.VisitCustomBlobNVPairs(CILParser.CustomBlobNVPairsContext context) => VisitCustomBlobNVPairs(context); + public GrammarResult.FormattedBlob VisitCustomBlobNVPairs(CILParser.CustomBlobNVPairsContext context) + { + var blob = new BlobBuilder(); + var fieldOrProps = context.fieldOrProp(); + var types = context.serializType(); + var names = context.dottedName(); + var values = context.serInit(); + + blob.WriteInt16((short)fieldOrProps.Length); + + for (int i = 0; i < fieldOrProps.Length; i++) + { + var fieldOrProp = fieldOrProps[i].GetText() == "field" ? CustomAttributeNamedArgumentKind.Field : CustomAttributeNamedArgumentKind.Property; + var type = VisitSerializType(types[i]).Value; + var name = VisitDottedName(names[i]).Value; + var value = VisitSerInit(values[i]).Value; + blob.WriteByte((byte)fieldOrProp); + type.WriteContentTo(blob); + blob.WriteSerializedString(name); + value.WriteContentTo(blob); + } + return new(blob); + } + + GrammarResult ICILVisitor.VisitCustomDescr(CILParser.CustomDescrContext context) => VisitCustomDescr(context); + public GrammarResult.Literal VisitCustomDescr(CILParser.CustomDescrContext context) + { + var ctor = VisitCustomType(context.customType()).Value; + BlobBuilder value; + if (context.customBlobDescr() is {} customBlobDescr) + { + value = VisitCustomBlobDescr(customBlobDescr).Value; + } + else if (context.bytes() is {} bytes) + { + value = new(); + value.WriteBytes(VisitBytes(bytes).Value); + } + else if (context.compQstring() is {} str) + { + value = new(); + value.WriteUTF8(VisitCompQstring(str).Value); + // COMPAT: We treat this string as a string-reprensentation of a blob, + // so we don't emit the null terminator. + } + else + { + throw new UnreachableException(); + } + + return new(_entityRegistry.CreateCustomAttribute(ctor, value)); + } + + GrammarResult ICILVisitor.VisitCustomDescrWithOwner(CILParser.CustomDescrWithOwnerContext context) => VisitCustomDescrWithOwner(context); + public GrammarResult.Literal VisitCustomDescrWithOwner(CILParser.CustomDescrWithOwnerContext context) + { + var ctor = VisitCustomType(context.customType()).Value; + BlobBuilder value; + if (context.customBlobDescr() is {} customBlobDescr) + { + value = VisitCustomBlobDescr(customBlobDescr).Value; + } + else if (context.bytes() is {} bytes) + { + value = new(); + value.WriteBytes(VisitBytes(bytes).Value); + } + else if (context.compQstring() is {} str) + { + value = new(); + value.WriteUTF8(VisitCompQstring(str).Value); + // COMPAT: We treat this string as a string-reprensentation of a blob, + // so we don't emit the null terminator. + } + else + { + throw new UnreachableException(); + } + + var attr = _entityRegistry.CreateCustomAttribute(ctor, value); + + attr.Owner = VisitOwnerType(context.ownerType()).Value; + + return new(attr); + } + + GrammarResult ICILVisitor.VisitCustomType(CILParser.CustomTypeContext context) => VisitCustomType(context); + public GrammarResult.Literal VisitCustomType(CILParser.CustomTypeContext context) => VisitMethodRef(context.methodRef()); + + public GrammarResult VisitDataDecl(CILParser.DataDeclContext context) + { + _ = VisitDdHead(context.ddHead()); + _ = VisitDdBody(context.ddBody()); + return GrammarResult.SentinelValue.Result; + } + public GrammarResult VisitDdBody(CILParser.DdBodyContext context) + { + if (context.ddItemList() is CILParser.DdItemListContext ddItemList) + { + _ = VisitDdItemList(ddItemList); + } + else + { + _ = VisitDdItem(context.ddItem()); + } + return GrammarResult.SentinelValue.Result; + } + public GrammarResult VisitDdHead(CILParser.DdHeadContext context) + { + if (context.id() is CILParser.IdContext id) + { + string name = VisitId(id).Value; + if (!_mappedFieldDataNames.ContainsKey(name)) + { + _mappedFieldDataNames.Add(name, _mappedFieldData.Count); + } + } + return GrammarResult.SentinelValue.Result; + } + public GrammarResult VisitDdItem(CILParser.DdItemContext context) + { + if (context.compQstring() is CILParser.CompQstringContext str) + { + var value = VisitCompQstring(str).Value; + _mappedFieldData.WriteUTF16(value); + return GrammarResult.SentinelValue.Result; + } + else if (context.id() is CILParser.IdContext id) + { + string name = VisitId(id).Value; + if (!_mappedFieldDataReferenceFixups.TryGetValue(name, out var fixups)) + { + _mappedFieldDataReferenceFixups[name] = fixups = new(); + } + + // TODO: Figure out how to handle relocs correctly + fixups.Add(_mappedFieldData.ReserveBytes(4)); + return GrammarResult.SentinelValue.Result; + } + else if (context.bytes() is CILParser.BytesContext bytes) + { + _mappedFieldData.WriteBytes(VisitBytes(bytes).Value); + return GrammarResult.SentinelValue.Result; + } + + int itemCount = VisitDdItemCount(context.ddItemCount()).Value; + + if (context.INT8() is not null) + { + _mappedFieldData.WriteBytes(context.int32() is CILParser.Int32Context int32 ? (byte)VisitInt32(int32).Value : (byte)0, itemCount); + } + else if (context.INT16() is not null) + { + for (int i = 0; i < itemCount; i++) + { + _mappedFieldData.WriteInt16(context.int32() is CILParser.Int32Context int32 ? (short)VisitInt32(int32).Value : (short)0); + } + } + else if (context.INT32_() is not null) + { + for (int i = 0; i < itemCount; i++) + { + _mappedFieldData.WriteInt32(context.int32() is CILParser.Int32Context int32 ? VisitInt32(int32).Value : 0); + } + } + else if (context.INT64_() is not null) + { + for (int i = 0; i < itemCount; i++) + { + _mappedFieldData.WriteInt64(context.int64() is CILParser.Int64Context int64 ? VisitInt64(int64).Value : 0); + } + } + else if (context.FLOAT32() is not null) + { + for (int i = 0; i < itemCount; i++) + { + _mappedFieldData.WriteSingle(context.float64() is CILParser.Float64Context float64 ? (float)VisitFloat64(float64).Value : 0); + } + } + else if (context.FLOAT64_() is not null) + { + for (int i = 0; i < itemCount; i++) + { + _mappedFieldData.WriteDouble(context.float64() is CILParser.Float64Context float64 ? VisitFloat64(float64).Value : 0); + } + } + return GrammarResult.SentinelValue.Result; + } + GrammarResult ICILVisitor.VisitDdItemCount(CILParser.DdItemCountContext context) => VisitDdItemCount(context); + public GrammarResult.Literal VisitDdItemCount(CILParser.DdItemCountContext context) => new(context.int32() is CILParser.Int32Context ? VisitInt32(context.int32()).Value : 1); + public GrammarResult VisitDdItemList(CILParser.DdItemListContext context) + { + foreach (var item in context.ddItem()) + { + VisitDdItem(item); + } + return GrammarResult.SentinelValue.Result; + } + + private readonly Stack _currentNamespace = new(); + + private readonly Stack _currentTypeDefinition = new(); + + public GrammarResult VisitDecl(CILParser.DeclContext context) + { + if (context.nameSpaceHead() is CILParser.NameSpaceHeadContext ns) + { + string namespaceName = VisitNameSpaceHead(ns).Value; + _currentNamespace.Push($"{_currentNamespace.PeekOrDefault()}.{namespaceName}"); + VisitDecls(context.decls()); + _currentNamespace.Pop(); + return GrammarResult.SentinelValue.Result; + } + if (context.classHead() is CILParser.ClassHeadContext classHead) + { + _currentTypeDefinition.Push(VisitClassHead(classHead).Value); + VisitClassDecls(context.classDecls()); + _currentTypeDefinition.Pop(); + return GrammarResult.SentinelValue.Result; + } + if (context.methodHead() is CILParser.MethodHeadContext methodHead) + { + _currentMethod = new(VisitMethodHead(methodHead).Value); + VisitMethodDecls(context.methodDecls()); + _currentMethod = null; + return GrammarResult.SentinelValue.Result; + } + if (context.fieldDecl() is { } fieldDecl) + { + _ = VisitFieldDecl(fieldDecl); + return GrammarResult.SentinelValue.Result; + } + if (context.dataDecl() is { } dataDecl) + { + _ = VisitDataDecl(dataDecl); + return GrammarResult.SentinelValue.Result; + } + if (context.vtableDecl() is { } vtable) + { + _ = VisitVtableDecl(vtable); + return GrammarResult.SentinelValue.Result; + } + if (context.vtfixupDecl() is { } vtFixup) + { + _ = VisitVtfixupDecl(vtFixup); + return GrammarResult.SentinelValue.Result; + } + if (context.extSourceSpec() is { } extSourceSpec) + { + _ = VisitExtSourceSpec(extSourceSpec); + return GrammarResult.SentinelValue.Result; + } + if (context.fileDecl() is { } fileDecl) + { + _ = VisitFileDecl(fileDecl); + return GrammarResult.SentinelValue.Result; + } + if (context.assemblyBlock() is { } assemblyBlock) + { + _ = VisitAssemblyBlock(assemblyBlock); + return GrammarResult.SentinelValue.Result; + } + if (context.assemblyRefHead() is { } assemblyRef) + { + var asmRef = VisitAssemblyRefHead(assemblyRef).Value; + _currentAssemblyOrRef = asmRef; + foreach (var decl in context.assemblyRefDecls().assemblyRefDecl()) + { + _ = VisitAssemblyRefDecl(decl); + } + _currentAssemblyOrRef = null; + } + if (context.exptypeHead() is { } exptypeHead) + { + var (attrs, dottedName) = VisitExptypeHead(exptypeHead).Value; + (string typeNamespace, string name) = NameHelpers.SplitDottedNameToNamespaceAndName(dottedName); + var (impl, typeDefId, customAttrs) = VisitExptypeDecls(context.exptypeDecls()).Value; + var exp = _entityRegistry.GetOrCreateExportedType(impl, typeNamespace, name, exp => + { + exp.Attributes = attrs; + exp.TypeDefinitionId = typeDefId; + }); + foreach (var attr in customAttrs) + { + attr.Owner = exp; + } + return GrammarResult.SentinelValue.Result; + } + if (context.manifestResHead() is { } manifestResHead) + { + var (name, alias, flags) = VisitManifestResHead(manifestResHead).Value; + var (implementation, offset, attrs) = VisitManifestResDecls(context.manifestResDecls()).Value; + if (implementation is null) + { + offset = (uint)_manifestResources.Count; + _manifestResources.WriteBytes(_resourceLocator(alias)); + } + var res = _entityRegistry.CreateManifestResource(name, offset); + res.Attributes = flags; + res.Implementation = implementation; + foreach (var attr in attrs) + { + attr.Owner = res; + } + return GrammarResult.SentinelValue.Result; + } + if (context.moduleHead() is { } moduleHead) + { + if (moduleHead.dottedName() is null) + { + _entityRegistry.Module.Name = null; + } + else if (moduleHead.ChildCount == 2) + { + _entityRegistry.Module.Name = VisitDottedName(moduleHead.dottedName()).Value; + } + else + { + var name = VisitDottedName(moduleHead.dottedName()).Value; + _entityRegistry.GetOrCreateModuleReference(name, _ => { }); + } + return GrammarResult.SentinelValue.Result; + } + if (context.subsystem() is { } subsystem) + { + _subsystem = (Subsystem)VisitSubsystem(subsystem).Value; + } + if (context.corflags() is { } corflags) + { + _corflags = (CorFlags)VisitCorflags(corflags).Value; + } + if (context.alignment() is { } alignment) + { + _alignment = VisitAlignment(alignment).Value; + } + if (context.imagebase() is { } imagebase) + { + _imageBase = VisitImagebase(imagebase).Value; + } + if (context.stackreserve() is { } stackreserve) + { + _stackReserve = VisitStackreserve(stackreserve).Value; + } + if (context.languageDecl() is { } languageDecl) + { + VisitLanguageDecl(languageDecl); + } + if (context.typedefDecl() is { } typedefDecl) + { + VisitTypedefDecl(typedefDecl); + } + if (context.typelist() is { } typelist) + { + foreach (var name in typelist.className()) + { + _ = VisitClassName(name); + } + } + if (context.mscorlib() is { } mscorlib) + { + VisitMscorlib(mscorlib); + } + return GrammarResult.SentinelValue.Result; + } + + public GrammarResult VisitDecls(CILParser.DeclsContext context) + { + foreach (var decl in context.decl()) + { + _ = VisitDecl(decl); + } + return GrammarResult.SentinelValue.Result; + } + + GrammarResult ICILVisitor.VisitDottedName(CILParser.DottedNameContext context) + { + return VisitDottedName(context); + } + + public static GrammarResult.String VisitDottedName(CILParser.DottedNameContext context) + { + return new(context.GetText()); + } + GrammarResult ICILVisitor.VisitElementType(CILParser.ElementTypeContext context) => VisitElementType(context); + public GrammarResult.FormattedBlob VisitElementType(CILParser.ElementTypeContext context) + { + BlobBuilder blob = new(5); + if (context.OBJECT() is not null) + { + blob.WriteByte((byte)SignatureTypeCode.Object); + } + else if (context.className() is CILParser.ClassNameContext className) + { + EntityRegistry.TypeEntity typeEntity = VisitClassName(className).Value; + if (context.VALUE() is not null || context.VALUETYPE() is not null) + { + blob.WriteByte((byte)SignatureTypeKind.ValueType); + blob.WriteTypeEntity(typeEntity); + } + else + { + blob.WriteByte((byte)SignatureTypeKind.Class); + blob.WriteTypeEntity(typeEntity); + } + } + else if (context.callConv() is CILParser.CallConvContext callConv) + { + // Emit function pointer signature. + blob.WriteByte((byte)SignatureTypeCode.FunctionPointer); + byte sigCallConv = VisitCallConv(callConv).Value; + blob.WriteByte(sigCallConv); + var signatureArgs = VisitSigArgs(context.sigArgs()).Value; + int numArgs = signatureArgs.Count(arg => !arg.IsSentinel); + blob.WriteCompressedInteger(numArgs); + blob.LinkSuffix(VisitType(context.type()).Value); + foreach (var arg in signatureArgs) + { + blob.LinkSuffix(arg.SignatureBlob); + } + } + else if (context.ELLIPSIS() is not null) + { + blob.WriteByte((byte)SignatureTypeCode.Sentinel); + blob.LinkSuffix(VisitType(context.type()).Value); + } + else if (context.METHOD_TYPE_PARAMETER() is not null) + { + if (context.int32() is CILParser.Int32Context int32) + { + // COMPAT: Always write a reference to a generic method parameter by index + // even if we aren't in a method or the index is out of range. We want to be able to write invalid IL like this. + blob.WriteByte((byte)SignatureTypeCode.GenericMethodParameter); + blob.WriteCompressedInteger(VisitInt32(int32).Value); + } + else + { + string dottedName = VisitDottedName(context.dottedName()).Value; + if (_currentMethod is null) + { + // TODO: Report diagnostic + blob.WriteByte((byte)SignatureTypeCode.GenericMethodParameter); + blob.WriteCompressedInteger(0); + } + else + { + blob.WriteByte((byte)SignatureTypeCode.GenericMethodParameter); + bool foundParameter = false; + for (int i = 0; i < _currentMethod.Definition.GenericParameters.Count; i++) + { + EntityRegistry.GenericParameterEntity? genericParameter = _currentMethod.Definition.GenericParameters[i]; + if (genericParameter.Name == dottedName) + { + foundParameter = true; + blob.WriteCompressedInteger(i); + break; + } + } + if (!foundParameter) + { + // BREAK-COMPAT: ILASM would silently emit an invalid signature when a method uses an invalid method type parameter but doesn't have method type parameters. + // The signature used completely invalid undocumented codes (that were really sentinel values for how ilasm later detected errors due to how the parsing model worked with a YACC-based parser) + // and when a method had no type parameters, it didn't run the code to process out these values and emit errors. + // This seems like a scenario that doesn't need to be brought forward. + // Instead, we'll just emit a reference to "generic method parameter" 0 and report an error. + + // TODO: Report diagnostic + blob.WriteCompressedInteger(0); + } + } + } + } + else if (context.TYPE_PARAMETER() is not null) + { + if (context.int32() is CILParser.Int32Context int32) + { + // COMPAT: Always write a reference to a generic type parameter by index + // even if we aren't in a type or the index is out of range. We want to be able to write invalid IL like this. + blob.WriteByte((byte)SignatureTypeCode.GenericTypeParameter); + blob.WriteCompressedInteger(VisitInt32(int32).Value); + } + else + { + string dottedName = VisitDottedName(context.dottedName()).Value; + if (_currentTypeDefinition.Count == 0) + { + // TODO: Report diagnostic + blob.WriteByte((byte)SignatureTypeCode.GenericTypeParameter); + blob.WriteCompressedInteger(0); + } + else + { + blob.WriteByte((byte)SignatureTypeCode.GenericTypeParameter); + bool foundParameter = false; + for (int i = 0; i < _currentTypeDefinition.Peek().GenericParameters.Count; i++) + { + EntityRegistry.GenericParameterEntity? genericParameter = _currentTypeDefinition.Peek().GenericParameters[i]; + if (genericParameter.Name == dottedName) + { + foundParameter = true; + blob.WriteCompressedInteger(i); + break; + } + } + if (!foundParameter) + { + // BREAK-COMPAT: ILASM would silently emit an invalid signature when a type uses an invalid method type parameter but doesn't have any type parameters. + // The signature used completely invalid undocumented codes (that were really sentinel values for how ilasm later detected errors due to how the parsing model worked with a YACC-based parser) + // and when a method had no type parameters, it didn't run the code to process out these values and emit errors. + // This seems like a scenario that doesn't need to be brought forward. + // Instead, we'll just emit a reference to "generic method parameter" 0 and report an error. + + // TODO: Report diagnostic + blob.WriteCompressedInteger(0); + } + } + } + } + else if (context.TYPEDREF() is not null) + { + blob.WriteByte((byte)SignatureTypeCode.TypedReference); + } + else if (context.VOID() is not null) + { + blob.WriteByte((byte)SignatureTypeCode.Void); + } + else if (context.NATIVE_INT() is not null) + { + blob.WriteByte((byte)SignatureTypeCode.IntPtr); + } + else if (context.NATIVE_UINT() is not null) + { + blob.WriteByte((byte)SignatureTypeCode.UIntPtr); + } + else if (context.simpleType() is CILParser.SimpleTypeContext simpleType) + { + blob.WriteByte((byte)VisitSimpleType(simpleType).Value); + } + else if (context.dottedName() is CILParser.DottedNameContext dottedName) + { + // TODO: typedef + throw new NotImplementedException(); + } + else + { + throw new UnreachableException(); + } + return new(blob); + } + + public GrammarResult VisitErrorNode(IErrorNode node) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + public GrammarResult VisitEsHead(CILParser.EsHeadContext context) => throw new NotImplementedException("TODO: Symbols"); + + GrammarResult ICILVisitor.VisitEventAttr(CILParser.EventAttrContext context) => VisitEventAttr(context); + public GrammarResult.Flag VisitEventAttr(CILParser.EventAttrContext context) + { + return context.GetText() switch + { + "specialname" => new(EventAttributes.SpecialName), + "rtspecialname" => new(0), // COMPAT: Ignore + _ => throw new UnreachableException(), + }; + } + + GrammarResult ICILVisitor.VisitEventDecl(CILParser.EventDeclContext context) => VisitEventDecl(context); + public GrammarResult.Literal<(MethodSemanticsAttributes, EntityRegistry.EntityBase)?> VisitEventDecl(CILParser.EventDeclContext context) + { + if (context.ChildCount != 2) + { + return new(null); + } + string accessor = context.GetChild(0).GetText(); + EntityRegistry.EntityBase memberReference = VisitMethodRef(context.methodRef()).Value; + MethodSemanticsAttributes methodSemanticsAttributes = accessor switch + { + ".addon" => MethodSemanticsAttributes.Adder, + ".removeon" => MethodSemanticsAttributes.Remover, + ".fire" => MethodSemanticsAttributes.Raiser, + ".other" => MethodSemanticsAttributes.Other, + _ => throw new UnreachableException(), + }; + return new((methodSemanticsAttributes, memberReference)); + } + + GrammarResult ICILVisitor.VisitEventDecls(CILParser.EventDeclsContext context) => VisitEventDecls(context); + public GrammarResult.Sequence<(MethodSemanticsAttributes, EntityRegistry.EntityBase)> VisitEventDecls(CILParser.EventDeclsContext context) + => new( + context.eventDecl() + .Select(decl => VisitEventDecl(decl).Value) + .Where(decl => decl is not null) + .Select(decl => decl!.Value).ToImmutableArray()); + + GrammarResult ICILVisitor.VisitEventHead(CILParser.EventHeadContext context) => VisitEventHead(context); + public GrammarResult.Literal VisitEventHead(CILParser.EventHeadContext context) + { + string name = VisitDottedName(context.dottedName()).Value; + EventAttributes eventAttributes = context.eventAttr().Select(attr => VisitEventAttr(attr).Value).Aggregate((a, b) => a | b); + return new(new EntityRegistry.EventEntity(eventAttributes, VisitTypeSpec(context.typeSpec()).Value, name)); + } + + public GrammarResult VisitExportHead(CILParser.ExportHeadContext context) => throw new NotImplementedException("Obsolete syntax"); + + GrammarResult ICILVisitor.VisitExptAttr(CILParser.ExptAttrContext context) => VisitExptAttr(context); + public static GrammarResult.Flag VisitExptAttr(CILParser.ExptAttrContext context) + { + return context.GetText() switch + { + "private" => new(TypeAttributes.NotPublic, TypeAttributes.VisibilityMask), + "public" => new(TypeAttributes.Public, TypeAttributes.VisibilityMask), + "forwarder" => new(TypeAttributes.Forwarder), + "nestedpublic" => new(TypeAttributes.NestedPublic, TypeAttributes.VisibilityMask), + "nestedprivate" => new(TypeAttributes.NestedPrivate, TypeAttributes.VisibilityMask), + "nestedfamily" => new(TypeAttributes.NestedFamily, TypeAttributes.VisibilityMask), + "nestedassembly" => new(TypeAttributes.NestedAssembly, TypeAttributes.VisibilityMask), + "nestedfamandassem" => new(TypeAttributes.NestedFamANDAssem, TypeAttributes.VisibilityMask), + "nestedfamorassem" => new(TypeAttributes.NestedFamORAssem, TypeAttributes.VisibilityMask), + _ => throw new UnreachableException(), + }; + } + + // TODO: Implement multimodule type exports and fowarders + public GrammarResult VisitExptypeDecl(CILParser.ExptypeDeclContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + + GrammarResult ICILVisitor.VisitExptypeDecls(CILParser.ExptypeDeclsContext context) => VisitExptypeDecls(context); + public GrammarResult.Literal<(EntityRegistry.EntityBase? implementation, int typedefId, ImmutableArray attrs)> VisitExptypeDecls(CILParser.ExptypeDeclsContext context) + { + // COMPAT: The following order specifies the precedence of the various export kinds. + // File, Assembly, Class (enclosing type), invalid token. + // We'll process through all of the options here and then return the one that is valid. + // We'll also record custom attributes here. + EntityRegistry.EntityBase? implementationEntity = null; + int typedefId = 0; + var attrs = ImmutableArray.CreateBuilder(); + var declarations = context.exptypeDecl(); + for (int i = 0; i < declarations.Length; i++) + { + if (declarations[i].customAttrDecl() is { } attr) + { + if (VisitCustomAttrDecl(attr).Value is EntityRegistry.CustomAttributeEntity customAttribute) + { + attrs.Add(customAttribute); + } + continue; + } + if (declarations[i].mdtoken() is { } mdToken) + { + var entity = VisitMdtoken(mdToken).Value; + if (entity is null) + { + // TODO: Report diagnostic + } + implementationEntity = ResolveBetterEntity(entity); + continue; + } + string kind = declarations[i].GetText(); + if (kind == ".file") + { + implementationEntity = _entityRegistry.FindFile(VisitDottedName(declarations[i].dottedName()).Value); + if (implementationEntity is null) + { + // TODO: Report diagnostic + } + } + else if (kind == ".assembly") + { + implementationEntity = _entityRegistry.FindAssemblyReference(VisitDottedName(declarations[i].dottedName()).Value); + if (implementationEntity is null) + { + // TODO: Report diagnostic + } + } + else if (kind == ".class") + { + if (declarations[i].int32() is CILParser.Int32Context int32) + { + typedefId = VisitInt32(int32).Value; + } + else + { + _ = VisitSlashedName(declarations[i].slashedName()); + var containing = ResolveExportedType(declarations[i].slashedName()); + if (containing is null) + { + // TODO: Report diagnostic + } + else + { + implementationEntity = ResolveBetterEntity(containing); + } + } + } + } + + return new((implementationEntity, typedefId, attrs.ToImmutable())); + + EntityRegistry.EntityBase? ResolveBetterEntity(EntityRegistry.EntityBase? newImplementation) + { + return (implementationEntity, newImplementation) switch + { + (null, _) => newImplementation, + (_, null) => implementationEntity, + (_, EntityRegistry.FileEntity) => newImplementation, + (EntityRegistry.FileEntity, _) => implementationEntity, + (_, EntityRegistry.AssemblyEntity) => newImplementation, + (EntityRegistry.AssemblyEntity, _) => implementationEntity, + (_, EntityRegistry.TypeEntity) => newImplementation, + (EntityRegistry.TypeEntity, _) => implementationEntity, + _ => throw new UnreachableException(), + }; + } + + // Resolve ExportedType reference + EntityRegistry.ExportedTypeEntity? ResolveExportedType(CILParser.SlashedNameContext slashedName) + { + TypeName typeName = VisitSlashedName(slashedName).Value; + if (typeName.ContainingTypeName is null) + { + // TODO: Check for typedef. + } + Stack containingTypes = new(); + for (TypeName? containingType = typeName; containingType is not null; containingType = containingType.ContainingTypeName) + { + containingTypes.Push(containingType); + } + EntityRegistry.ExportedTypeEntity? exportedType = null; + while (containingTypes.Count != 0) + { + TypeName containingType = containingTypes.Pop(); + + (string ns, string name) = NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName); + + exportedType = _entityRegistry.FindExportedType( + exportedType, + ns, + name); + + if (exportedType is null) + { + // TODO: Report diagnostic for missing type name + return null; + } + } + + return exportedType!; + } + } + GrammarResult ICILVisitor.VisitExptypeHead(CILParser.ExptypeHeadContext context) => VisitExptypeHead(context); + public GrammarResult.Literal<(TypeAttributes attrs, string dottedName)> VisitExptypeHead(CILParser.ExptypeHeadContext context) + { + var attrs = context.exptAttr().Select(VisitExptAttr).Aggregate((TypeAttributes)0, (a, b) => a | b); + return new((attrs, VisitDottedName(context.dottedName()).Value)); + } + GrammarResult ICILVisitor.VisitExtendsClause(CILParser.ExtendsClauseContext context) => VisitExtendsClause(context); + + public GrammarResult.Literal VisitExtendsClause(CILParser.ExtendsClauseContext context) + { + if (context.typeSpec() is CILParser.TypeSpecContext typeSpec) + { + return new(VisitTypeSpec(typeSpec).Value); + } + else + { + return new(null); + } + } + + public GrammarResult VisitExtSourceSpec(CILParser.ExtSourceSpecContext context) => throw new NotImplementedException("TODO: Symbols"); + + GrammarResult ICILVisitor.VisitF32seq(CILParser.F32seqContext context) => VisitF32seq(context); + public GrammarResult.FormattedBlob VisitF32seq(CILParser.F32seqContext context) + { + var builder = ImmutableArray.CreateBuilder(); + + foreach (var item in context.children) + { + builder.Add((float)(item switch + { + CILParser.Int32Context int32 => VisitInt32(int32).Value, + CILParser.Float64Context float64 => VisitFloat64(float64).Value, + _ => throw new UnreachableException() + })); + } + return new(builder.MoveToImmutable().SerializeSequence()); + } + GrammarResult ICILVisitor.VisitF64seq(CILParser.F64seqContext context) => VisitF64seq(context); + public GrammarResult.FormattedBlob VisitF64seq(CILParser.F64seqContext context) + { + var builder = ImmutableArray.CreateBuilder(); + + foreach (var item in context.children) + { + builder.Add((double)(item switch + { + CILParser.Int64Context int64 => VisitInt64(int64).Value, + CILParser.Float64Context float64 => VisitFloat64(float64).Value, + _ => throw new UnreachableException() + })); + } + return new(builder.MoveToImmutable().SerializeSequence()); + } + + public GrammarResult VisitFaultClause(CILParser.FaultClauseContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + + GrammarResult ICILVisitor.VisitFieldAttr(CILParser.FieldAttrContext context) => VisitFieldAttr(context); + public GrammarResult.Flag VisitFieldAttr(CILParser.FieldAttrContext context) + { + if (context.int32() is { } int32) + { + return new((FieldAttributes)VisitInt32(int32).Value, ShouldAppend: false); + } + + return context.GetText() switch + { + "static" => new(FieldAttributes.Static), + "public" => new(FieldAttributes.Public, FieldAttributes.FieldAccessMask), + "private" => new(FieldAttributes.Private, FieldAttributes.FieldAccessMask), + "family" => new(FieldAttributes.Family, FieldAttributes.FieldAccessMask), + "initonly" => new(FieldAttributes.InitOnly), + "rtspecialname" => new(0), // COMPAT: Don't emit rtspecialname + "specialname" => new(FieldAttributes.SpecialName), + "assembly" => new(FieldAttributes.Assembly, FieldAttributes.FieldAccessMask), + "famandassem" => new(FieldAttributes.FamANDAssem, FieldAttributes.FieldAccessMask), + "famorassem" => new(FieldAttributes.FamORAssem, FieldAttributes.FieldAccessMask), + "privatescope" => new(FieldAttributes.PrivateScope, FieldAttributes.FieldAccessMask), + "literal" => new(FieldAttributes.Literal), +#pragma warning disable SYSLIB0050 // FieldAttributes.NotSeralized is obsolete + "notserialized" => new(FieldAttributes.NotSerialized), +#pragma warning restore SYSLIB0050 // FieldAttributes.NotSeralized is obsolete + _ => throw new UnreachableException() + }; + } + GrammarResult ICILVisitor.VisitFieldDecl(CILParser.FieldDeclContext context) => VisitFieldDecl(context); + public GrammarResult VisitFieldDecl(CILParser.FieldDeclContext context) + { + var fieldAttrs = context.fieldAttr().Select(VisitFieldAttr).Aggregate((FieldAttributes)0, (a, b) => a | b); + var fieldType = VisitType(context.type()).Value; + var marshalBlobs = context.marshalBlob(); + var marshalBlob = VisitMarshalBlob(marshalBlobs[marshalBlobs.Length - 1]).Value; + var name = VisitDottedName(context.dottedName()).Value; + var rvaOffset = VisitAtOpt(context.atOpt()).Value; + _ = VisitInitOpt(context.initOpt()); + + var signature = new BlobEncoder(new BlobBuilder()); + _ = signature.Field(); + fieldType.WriteContentTo(signature.Builder); + + var field = EntityRegistry.CreateUnrecordedFieldDefinition(fieldAttrs, _currentTypeDefinition.PeekOrDefault()!, name, signature.Builder); + + if (field is not null) + { + field.MarshallingDescriptor = marshalBlob; + field.DataDeclarationName = rvaOffset; + } + + return GrammarResult.SentinelValue.Result; + } + + public GrammarResult VisitFieldInit(CILParser.FieldInitContext context) => throw new NotImplementedException("TODO-SRM: Need support for an arbitrary byte blob as a constant value"); + + public GrammarResult VisitFieldOrProp(CILParser.FieldOrPropContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + + GrammarResult ICILVisitor.VisitFieldRef(CILParser.FieldRefContext context) => VisitFieldRef(context); + public GrammarResult.Literal VisitFieldRef(CILParser.FieldRefContext context) + { + if (context.type() is not CILParser.TypeContext type) + { + // TODO: typedef + throw new NotImplementedException(); + } + + var fieldTypeSig = VisitType(type).Value; + EntityRegistry.TypeEntity definingType = _currentTypeDefinition.PeekOrDefault() ?? _entityRegistry.ModuleType; + if (context.typeSpec() is CILParser.TypeSpecContext typeSpec) + { + definingType = VisitTypeSpec(typeSpec).Value; + } + + var name = VisitDottedName(context.dottedName()).Value; + + var fieldSig = new BlobBuilder(fieldTypeSig.Count + 1); + fieldSig.WriteByte((byte)SignatureKind.Field); + fieldTypeSig.WriteContentTo(fieldSig); + return new(_entityRegistry.CreateLazilyRecordedMemberReference(definingType, name, fieldSig)); + } + + GrammarResult ICILVisitor.VisitFieldSerInit(CILParser.FieldSerInitContext context) => VisitFieldSerInit(context); + public GrammarResult.FormattedBlob VisitFieldSerInit(CILParser.FieldSerInitContext context) + { + // The max length for the majority of the blobs is 9 bytes. 1 for the type of blob, 8 for the max 64-bit value. + // Byte arrays can be larger, so we handle that case separately. + const int CommonMaxBlobLength = 9; + BlobBuilder builder; + var bytesNode = context.bytes(); + if (bytesNode is not null) + { + var bytesResult = VisitBytes(bytesNode); + // Our blob length is the number of bytes in the byte array + the code for the byte array. + builder = new BlobBuilder(bytesResult.Value.Length + 1); + builder.WriteByte((byte)SerializationTypeCode.String); + builder.WriteBytes(bytesResult.Value); + return new(builder); + } + builder = new BlobBuilder(CommonMaxBlobLength); + + int tokenType = ((ITerminalNode)context.GetChild(0)).Symbol.Type; + + builder.WriteByte((byte)GetTypeCodeForToken(tokenType)); + + switch (tokenType) + { + case CILParser.BOOL: + builder.WriteBoolean(VisitTruefalse(context.truefalse()).Value); + break; + case CILParser.INT8: + case CILParser.UINT8: + builder.WriteByte((byte)VisitInt32(context.int32()).Value); + break; + case CILParser.CHAR: + case CILParser.INT16: + case CILParser.UINT16: + builder.WriteInt16((short)VisitInt32(context.int32()).Value); + break; + case CILParser.INT32: + case CILParser.UINT32: + builder.WriteInt32(VisitInt32(context.int32()).Value); + break; + case CILParser.INT64: + case CILParser.UINT64: + builder.WriteInt64(VisitInt64(context.int64()).Value); + break; + case CILParser.FLOAT32: + { + if (context.float64() is CILParser.Float64Context float64) + { + builder.WriteSingle((float)VisitFloat64(float64).Value); + } + if (context.int32() is CILParser.Int32Context int32) + { + int value = VisitInt32(int32).Value; + builder.WriteSingle(BitConverter.Int32BitsToSingle(value)); + } + break; + } + case CILParser.FLOAT64: + { + if (context.float64() is CILParser.Float64Context float64) + { + builder.WriteDouble(VisitFloat64(float64).Value); + } + if (context.int64() is CILParser.Int64Context int64) + { + long value = VisitInt64(int64).Value; + builder.WriteDouble(BitConverter.Int64BitsToDouble(value)); + } + break; + } + } + + return new(builder); + } + + GrammarResult ICILVisitor.VisitFileAttr(CILParser.FileAttrContext context) => VisitFileAttr(context); + public GrammarResult.Literal VisitFileAttr(CILParser.FileAttrContext context) + => context.ChildCount != 0 ? new(false) : new(true); + GrammarResult ICILVisitor.VisitFileDecl(CILParser.FileDeclContext context) => VisitFileDecl(context); + public GrammarResult.Literal VisitFileDecl(CILParser.FileDeclContext context) + { + string dottedName = VisitDottedName(context.dottedName()).Value; + ImmutableArray? hash = context.HASH() is not null ? VisitBytes(context.bytes()).Value : null; + var hashBlob = hash is not null ? new BlobBuilder() : null; + hashBlob?.WriteBytes(hash!.Value); + + bool hasMetadata = context.fileAttr().Aggregate(true, (acc, attr) => acc || VisitFileAttr(attr).Value); + bool isEntrypoint = context.fileEntry().Aggregate(true, (acc, attr) => acc || VisitFileEntry(attr).Value); + var entity = _entityRegistry.GetOrCreateFile(dottedName, hasMetadata, hashBlob); + if (isEntrypoint) + { + _entityRegistry.EntryPoint = entity; + } + return new(entity); + } + GrammarResult ICILVisitor.VisitFileEntry(CILParser.FileEntryContext context) => VisitFileEntry(context); + public GrammarResult.Literal VisitFileEntry(CILParser.FileEntryContext context) + => context.ChildCount != 0 ? new(true) : new(false); + + GrammarResult ICILVisitor.VisitFilterClause(CILParser.FilterClauseContext context) => VisitFilterClause(context); + public GrammarResult.Literal VisitFilterClause(CILParser.FilterClauseContext context) + { + if (context.scopeBlock() is CILParser.ScopeBlockContext scopeBlock) + { + LabelHandle start = _currentMethod!.Definition.MethodBody.DefineLabel(); + _currentMethod.Definition.MethodBody.MarkLabel(start); + _ = VisitScopeBlock(scopeBlock); + return new(start); + } + if (context.id() is CILParser.IdContext id) + { + var start = _currentMethod!.Labels.TryGetValue(VisitId(id).Value, out LabelHandle startLabel) ? startLabel : _currentMethod.Labels[VisitId(id).Value] = _currentMethod.Definition.MethodBody.DefineLabel(); + return new(start); + } + if (context.int32() is CILParser.Int32Context offset) + { + var start = _currentMethod!.Definition.MethodBody.DefineLabel(); + _currentMethod.Definition.MethodBody.MarkLabel(start, VisitInt32(offset).Value); + return new(start); + } + throw new UnreachableException(); + } + + public GrammarResult VisitFinallyClause(CILParser.FinallyClauseContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + + GrammarResult ICILVisitor.VisitFloat64(CILParser.Float64Context context) => VisitFloat64(context); + public GrammarResult.Literal VisitFloat64(CILParser.Float64Context context) + { + if (context.FLOAT64() is ITerminalNode float64) + { + string text = float64.Symbol.Text; + bool neg = text.StartsWith('-'); + if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out double result)) + { + result = neg ? double.MaxValue : double.MinValue; + } + return new(result); + } + else if (context.int32() is CILParser.Int32Context int32) + { + int value = VisitInt32(int32).Value; + return new(BitConverter.Int32BitsToSingle(value)); + } + else if (context.int64() is CILParser.Int64Context int64) + { + long value = VisitInt64(int64).Value; + return new(BitConverter.Int64BitsToDouble(value)); + } + throw new UnreachableException(); + } + GrammarResult ICILVisitor.VisitGenArity(CILParser.GenArityContext context) => VisitGenArity(context); + public GrammarResult.Literal VisitGenArity(CILParser.GenArityContext context) + => context.genArityNotEmpty() is CILParser.GenArityNotEmptyContext genArity ? VisitGenArityNotEmpty(genArity) : new(0); + + GrammarResult ICILVisitor.VisitGenArityNotEmpty(CILParser.GenArityNotEmptyContext context) => VisitGenArityNotEmpty(context); + public GrammarResult.Literal VisitGenArityNotEmpty(CILParser.GenArityNotEmptyContext context) => VisitInt32(context.int32()); + + GrammarResult ICILVisitor.VisitHandlerBlock(CILParser.HandlerBlockContext context) => VisitHandlerBlock(context); + + public GrammarResult.Literal<(LabelHandle Start, LabelHandle End)> VisitHandlerBlock(CILParser.HandlerBlockContext context) + { + if (context.scopeBlock() is CILParser.ScopeBlockContext scopeBlock) + { + LabelHandle start = _currentMethod!.Definition.MethodBody.DefineLabel(); + _currentMethod.Definition.MethodBody.MarkLabel(start); + _ = VisitScopeBlock(scopeBlock); + LabelHandle end = _currentMethod.Definition.MethodBody.DefineLabel(); + _currentMethod.Definition.MethodBody.MarkLabel(end); + return new((start, end)); + } + if (context.id() is CILParser.IdContext[] ids) + { + var start = _currentMethod!.Labels.TryGetValue(VisitId(ids[0]).Value, out LabelHandle startLabel) ? startLabel : _currentMethod.Labels[VisitId(ids[0]).Value] = _currentMethod.Definition.MethodBody.DefineLabel(); + var end = _currentMethod!.Labels.TryGetValue(VisitId(ids[1]).Value, out LabelHandle endLabel) ? endLabel : _currentMethod.Labels[VisitId(ids[1]).Value] = _currentMethod.Definition.MethodBody.DefineLabel(); + return new((start, end)); + } + if (context.int32() is CILParser.Int32Context[] offsets) + { + var start = _currentMethod!.Definition.MethodBody.DefineLabel(); + var end = _currentMethod.Definition.MethodBody.DefineLabel(); + _currentMethod.Definition.MethodBody.MarkLabel(start, VisitInt32(offsets[0]).Value); + _currentMethod.Definition.MethodBody.MarkLabel(end, VisitInt32(offsets[1]).Value); + return new((start, end)); + } + throw new UnreachableException(); + } + + GrammarResult ICILVisitor.VisitHexbytes(CILParser.HexbytesContext context) + { + return VisitHexbytes(context); + } + + public static GrammarResult.Sequence VisitHexbytes(CILParser.HexbytesContext context) + { + ITerminalNode[] bytes = context.HEXBYTE(); + var builder = ImmutableArray.CreateBuilder(bytes.Length); + foreach (var @byte in bytes) + { + builder.Add(byte.Parse(@byte.Symbol.Text, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture)); + } + return new(builder.MoveToImmutable()); + } + GrammarResult ICILVisitor.VisitI16seq(CILParser.I16seqContext context) => VisitI16seq(context); + public GrammarResult.FormattedBlob VisitI16seq(CILParser.I16seqContext context) + { + var values = context.int32(); + var builder = ImmutableArray.CreateBuilder(values.Length); + foreach (var value in values) + { + builder.Add((short)VisitInt32(value).Value); + } + return new(builder.MoveToImmutable().SerializeSequence()); + } + GrammarResult ICILVisitor.VisitI32seq(CILParser.I32seqContext context) => VisitI32seq(context); + public GrammarResult.FormattedBlob VisitI32seq(CILParser.I32seqContext context) + { + var values = context.int32(); + var builder = ImmutableArray.CreateBuilder(values.Length); + foreach (var value in values) + { + builder.Add(VisitInt32(value).Value); + } + return new(builder.MoveToImmutable().SerializeSequence()); + } + + GrammarResult ICILVisitor.VisitI8seq(CILParser.I8seqContext context) => VisitI8seq(context); + public GrammarResult.FormattedBlob VisitI8seq(CILParser.I8seqContext context) + { + var values = context.int32(); + var builder = ImmutableArray.CreateBuilder(values.Length); + foreach (var value in values) + { + builder.Add((byte)VisitInt32(value).Value); + } + return new(builder.MoveToImmutable().SerializeSequence()); + } + + GrammarResult ICILVisitor.VisitI64seq(CILParser.I64seqContext context) => VisitI64seq(context); + public GrammarResult.FormattedBlob VisitI64seq(CILParser.I64seqContext context) + { + var values = context.int64(); + var builder = ImmutableArray.CreateBuilder(values.Length); + foreach (var value in values) + { + builder.Add(VisitInt64(value).Value); + } + return new(builder.MoveToImmutable().SerializeSequence()); + } + GrammarResult ICILVisitor.VisitId(CILParser.IdContext context) => VisitId(context); + public static GrammarResult.String VisitId(CILParser.IdContext context) + { + return new GrammarResult.String(context.GetText()); + } + + GrammarResult ICILVisitor.VisitIidParamIndex(CILParser.IidParamIndexContext context) => VisitIidParamIndex(context); + public GrammarResult.Literal VisitIidParamIndex(CILParser.IidParamIndexContext context) + => context.int32() is CILParser.Int32Context int32 ? new(VisitInt32(int32).Value) : new(null); + + GrammarResult ICILVisitor.VisitImagebase(CILParser.ImagebaseContext context) => VisitImagebase(context); + public GrammarResult.Literal VisitImagebase(CILParser.ImagebaseContext context) => VisitInt64(context.int64()); + + GrammarResult ICILVisitor.VisitImplAttr(ILAssembler.CILParser.ImplAttrContext context) => VisitImplAttr(context); + public GrammarResult.Flag VisitImplAttr(CILParser.ImplAttrContext context) + { + if (context.int32() is CILParser.Int32Context int32) + { + return new((MethodImplAttributes)VisitInt32(int32).Value, ShouldAppend: false); + } + string attribute = context.GetText(); + return attribute switch + { + "native" => new(MethodImplAttributes.Native, MethodImplAttributes.CodeTypeMask), + "cil" => new(MethodImplAttributes.IL, MethodImplAttributes.CodeTypeMask), + "optil" => new(MethodImplAttributes.OPTIL, MethodImplAttributes.CodeTypeMask), + "managed" => new(MethodImplAttributes.Managed, MethodImplAttributes.ManagedMask), + "unmanaged" => new(MethodImplAttributes.Unmanaged, MethodImplAttributes.ManagedMask), + "forwardref" => new(MethodImplAttributes.ForwardRef), + "preservesig" => new(MethodImplAttributes.PreserveSig), + "runtime" => new(MethodImplAttributes.Runtime, MethodImplAttributes.CodeTypeMask), + "internalcall" => new(MethodImplAttributes.InternalCall), + "synchronized" => new(MethodImplAttributes.Synchronized), + "noinlining" => new(MethodImplAttributes.NoInlining), + "aggressiveinlining" => new(MethodImplAttributes.AggressiveInlining), + "nooptimization" => new(MethodImplAttributes.NoOptimization), + "aggressiveoptimization" => new(MethodImplAttributes.AggressiveOptimization), + "async" => new(MethodImplAttributes.Async), + _ => throw new UnreachableException(), + }; + } + + GrammarResult ICILVisitor.VisitImplClause(CILParser.ImplClauseContext context) => VisitImplClause(context); + public GrammarResult.Sequence VisitImplClause(CILParser.ImplClauseContext context) => context.implList() is {} implList ? VisitImplList(implList) : new(ImmutableArray.Empty); + + GrammarResult ICILVisitor.VisitImplList(CILParser.ImplListContext context) => VisitImplList(context); + public GrammarResult.Sequence VisitImplList(CILParser.ImplListContext context) + { + var builder = ImmutableArray.CreateBuilder(); + foreach (var impl in context.typeSpec()) + { + builder.Add(EntityRegistry.CreateUnrecordedInterfaceImplementation(_currentTypeDefinition.PeekOrDefault()!, VisitTypeSpec(impl).Value)); + } + return new(builder.ToImmutable()); + } + + public GrammarResult VisitInitOpt(CILParser.InitOptContext context) + { + if (context.fieldInit() is {}) + { + // TODO: Change fieldSerInit to return a parsed System.Object value to construct the constant row entry. + // TODO-SRM: AddConstant does not support providing an arbitrary byte array as a constant value. + // Propose MetadataBuilder.AddConstant(EntityHandle parent, PrimitiveTypeCode type, BlobBuilder value) overload? + throw new NotImplementedException(); + } + return GrammarResult.SentinelValue.Result; + } + public GrammarResult VisitInstr(CILParser.InstrContext context) + { + var instrContext = context.GetRuleContext(0); + ILOpCode opcode = ((GrammarResult.Literal)instrContext.Accept(this)).Value; + switch (instrContext.RuleIndex) + { + case CILParser.RULE_instr_brtarget: + { + ParserRuleContext argument = context.GetRuleContext(1); + if (argument is CILParser.IdContext id) + { + string label = VisitId(id).Value; + if (!_currentMethod!.Labels.TryGetValue(label, out var handle)) + { + _currentMethod.Labels.Add(label, handle = _currentMethod.Definition.MethodBody.DefineLabel()); + } + _currentMethod.Definition.MethodBody.Branch(opcode, handle); + } + if (argument is CILParser.Int32Context int32) + { + int offset = VisitInt32(int32).Value; + LabelHandle label = _currentMethod!.Definition.MethodBody.DefineLabel(); + _currentMethod.Definition.MethodBody.Branch(opcode, label); + _currentMethod.Definition.MethodBody.MarkLabel(label, _currentMethod.Definition.MethodBody.Offset + offset); + } + } + break; + case CILParser.RULE_instr_field: + { + _currentMethod!.Definition.MethodBody.OpCode(opcode); + if (context.mdtoken() is CILParser.MdtokenContext mdtoken) + { + _currentMethod.Definition.MethodBody.Token(VisitMdtoken(mdtoken).Value.Handle); + } + else + { + var fieldRef = VisitFieldRef(context.fieldRef()).Value; + if (fieldRef is EntityRegistry.MemberReferenceEntity memberRef) + { + memberRef.RecordBlobToWriteResolvedHandle(_currentMethod.Definition.MethodBody.CodeBuilder.ReserveBytes(4)); + } + else + { + _currentMethod.Definition.MethodBody.Token(fieldRef.Handle); + } + } + } + break; + case CILParser.RULE_instr_i: + { + int arg = VisitInt32(context.int32()).Value; + if (opcode == ILOpCode.Ldc_i4 || opcode == ILOpCode.Ldc_i4_s) + { + _currentMethod!.Definition.MethodBody.LoadConstantI4(arg); + } + else + { + _currentMethod!.Definition.MethodBody.OpCode(opcode); + _currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)arg); + } + } + break; + case CILParser.RULE_instr_i8: + Debug.Assert(opcode == ILOpCode.Ldc_i8); + _currentMethod!.Definition.MethodBody.LoadConstantI8(VisitInt64(context.int64()).Value); + break; + case CILParser.RULE_instr_method: + { + if (opcode == ILOpCode.Callvirt || opcode == ILOpCode.Newobj) + { + _expectInstance = true; + } + _currentMethod!.Definition.MethodBody.OpCode(opcode); + var methodRef = VisitMethodRef(context.methodRef()).Value; + if (methodRef is EntityRegistry.MemberReferenceEntity memberRef) + { + memberRef.RecordBlobToWriteResolvedHandle(_currentMethod.Definition.MethodBody.CodeBuilder.ReserveBytes(4)); + } + else + { + _currentMethod.Definition.MethodBody.Token(methodRef.Handle); + } + // Reset the instance flag for the next instruction. + if (opcode == ILOpCode.Callvirt || opcode == ILOpCode.Newobj) + { + _expectInstance = false; + } + } + break; + case CILParser.RULE_instr_none: + _currentMethod!.Definition.MethodBody.OpCode(opcode); + break; + case CILParser.RULE_instr_r: + { + double value; + ParserRuleContext argument = context.GetRuleContext(1); + if (argument is CILParser.Float64Context float64) + { + value = VisitFloat64(float64).Value; + } + else if (argument is CILParser.Int64Context int64) + { + long intValue = VisitInt64(int64).Value; + value = BitConverter.Int64BitsToDouble(intValue); + } + else if (argument is CILParser.BytesContext bytesContext) + { + var bytes = VisitBytes(bytesContext).Value.ToArray(); + value = bytes switch + { + { Length: >= 8 } => BitConverter.ToDouble(bytes, 0), + { Length: >= 4 } => BitConverter.ToSingle(bytes, 0), + // TODO: Report diagnostic on too-short byte array + _ => 0.0d + }; + } + else + { + throw new UnreachableException(); + } + if (opcode == ILOpCode.Ldc_r4) + { + _currentMethod!.Definition.MethodBody.LoadConstantR4((float)value); + } + else + { + _currentMethod!.Definition.MethodBody.LoadConstantR8(value); + } + } + break; + case CILParser.RULE_instr_sig: + { + BlobBuilder signature = new(); + byte callConv = VisitCallConv(context.callConv()).Value; + signature.WriteByte(callConv); + var args = VisitSigArgs(context.sigArgs()).Value; + signature.WriteCompressedInteger(args.Length); + // Write return type + VisitType(context.type()).Value.WriteContentTo(signature); + // Write arg signatures + foreach (var arg in args) + { + arg.SignatureBlob.WriteContentTo(signature); + } + _currentMethod!.Definition.MethodBody.Token(_entityRegistry.GetOrCreateStandaloneSignature(signature).Handle); + } + break; + case CILParser.RULE_instr_string: + Debug.Assert(opcode == ILOpCode.Ldstr); + string str; + if (context.bytes() is CILParser.BytesContext rawBytes) + { + ReadOnlySpan bytes = VisitBytes(rawBytes).Value.AsSpan(); + ReadOnlySpan bytesAsChars = MemoryMarshal.Cast(bytes); + if (!BitConverter.IsLittleEndian) + { + for (int i = 0; i < bytesAsChars.Length; i++) + { + BinaryPrimitives.ReverseEndianness(bytesAsChars[i]); + } + } + str = bytesAsChars.ToString(); + } + else + { + var userString = context.compQstring(); + Debug.Assert(userString is not null); + str = VisitCompQstring(userString!).Value; + if (context.ANSI() is not null) + { + // Emit the string not as a UTF-16 string (as per the spec), but directly as an ANSI string. + // Although the string is marked as ANSI, this always used the UTF-8 code page + // so we can emit this as UTF-8 bytes. + int byteCount = Encoding.UTF8.GetByteCount(str); + // Ensure we have an even number of bytes. + if ((byteCount % 1) != 0) + { + byteCount++; + } + + Span utf8Bytes = new byte[byteCount]; + Encoding.UTF8.GetBytes(str, utf8Bytes); + + str = new string(MemoryMarshal.Cast(utf8Bytes)); + } + } + _currentMethod!.Definition.MethodBody.LoadString(_metadataBuilder.GetOrAddUserString(str)); + break; + case CILParser.RULE_instr_switch: + { + var labels = new List<(LabelHandle Label, int? Offset)>(); + foreach (var label in context.labels().children) + { + if (label is CILParser.IdContext id) + { + string labelName = VisitId(id).Value; + if (!_currentMethod!.Labels.TryGetValue(labelName, out var handle)) + { + _currentMethod.Labels.Add(labelName, handle = _currentMethod.Definition.MethodBody.DefineLabel()); + } + labels.Add((handle, null)); + } + else if (label is CILParser.Int32Context int32) + { + int offset = VisitInt32(int32).Value; + LabelHandle labelHandle = _currentMethod!.Definition.MethodBody.DefineLabel(); + labels.Add((labelHandle, offset)); + } + } + var switchEncoder = _currentMethod!.Definition.MethodBody.Switch(labels.Count); + foreach (var label in labels) + { + switchEncoder.Branch(label.Label); + } + // Now that we've emitted the switch instruction, we can go back and mark the offset-based target labels + foreach (var label in labels) + { + if (label.Offset is int offset) + { + _currentMethod.Definition.MethodBody.MarkLabel(label.Label, _currentMethod.Definition.MethodBody.Offset + offset); + } + } + } + break; + case CILParser.RULE_instr_tok: + var tok = VisitOwnerType(context.ownerType()).Value; + _currentMethod!.Definition.MethodBody.OpCode(opcode); + _currentMethod.Definition.MethodBody.Token(tok.Handle); + break; + case CILParser.RULE_instr_type: + { + var arg = VisitTypeSpec(context.typeSpec()).Value; + _currentMethod!.Definition.MethodBody.OpCode(opcode); + _currentMethod.Definition.MethodBody.Token(arg.Handle); + } + break; + case CILParser.RULE_instr_var: + { + string instrName = opcode.ToString(); + bool isShortForm = instrName.EndsWith("_s"); + _currentMethod!.Definition.MethodBody.OpCode(opcode); + if (context.int32() is CILParser.Int32Context int32) + { + int value = VisitInt32(int32).Value; + if (isShortForm) + { + // Emit a byte instead of the int for the short form + _currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)value); + } + else + { + _currentMethod.Definition.MethodBody.CodeBuilder.WriteInt32(value); + } + } + else + { + Debug.Assert(context.id() is not null); + string varName = VisitId(context.id()!).Value; + int? index = null; + if (instrName.Contains("arg")) + { + if (_currentMethod!.ArgumentNames.TryGetValue(varName, out var argIndex)) + { + index = argIndex; + + if (_currentMethod.Definition.SignatureHeader.IsInstance) + { + index++; + } + } + else + { + // TODO: Report diagnostic + } + } + else + { + for (int i = _currentMethod!.LocalsScopes.Count - 1; i >= 0 ; i--) + { + if (_currentMethod.LocalsScopes[i].TryGetValue(varName, out var localIndex)) + { + index = localIndex; + break; + } + } + if (index is null) + { + // TODO: diagnostic + } + } + + index ??= -1; + + if (isShortForm) + { + // Emit a byte instead of the int for the short form + _currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)index.Value); + } + else + { + _currentMethod.Definition.MethodBody.CodeBuilder.WriteInt32(index.Value); + } + } + } + break; + } + return GrammarResult.SentinelValue.Result; + } + + public GrammarResult.Literal VisitInstr_brtarget(CILParser.Instr_brtargetContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_field(CILParser.Instr_fieldContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_i(CILParser.Instr_iContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_i8(CILParser.Instr_i8Context context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_method(CILParser.Instr_methodContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_none(CILParser.Instr_noneContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_r(CILParser.Instr_rContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_sig(CILParser.Instr_sigContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_string(CILParser.Instr_stringContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_switch(CILParser.Instr_switchContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_tok(CILParser.Instr_tokContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_type(CILParser.Instr_typeContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + public GrammarResult.Literal VisitInstr_var(CILParser.Instr_varContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol)); + private static ILOpCode ParseOpCodeFromToken(IToken token) + { + return (ILOpCode)Enum.Parse(typeof(ILOpCode), token.Text.Replace('.', '_'), ignoreCase: true); + } + + GrammarResult ICILVisitor.VisitInstr(CILParser.InstrContext context) => VisitInstr(context); + GrammarResult ICILVisitor.VisitInstr_brtarget(CILParser.Instr_brtargetContext context) => VisitInstr_brtarget(context); + GrammarResult ICILVisitor.VisitInstr_field(CILParser.Instr_fieldContext context) => VisitInstr_field(context); + GrammarResult ICILVisitor.VisitInstr_i(CILParser.Instr_iContext context) => VisitInstr_i(context); + GrammarResult ICILVisitor.VisitInstr_i8(CILParser.Instr_i8Context context) => VisitInstr_i8(context); + GrammarResult ICILVisitor.VisitInstr_method(CILParser.Instr_methodContext context) => VisitInstr_method(context); + GrammarResult ICILVisitor.VisitInstr_none(CILParser.Instr_noneContext context) => VisitInstr_none(context); + GrammarResult ICILVisitor.VisitInstr_r(CILParser.Instr_rContext context) => VisitInstr_r(context); + GrammarResult ICILVisitor.VisitInstr_sig(CILParser.Instr_sigContext context) => VisitInstr_sig(context); + GrammarResult ICILVisitor.VisitInstr_string(CILParser.Instr_stringContext context) => VisitInstr_string(context); + GrammarResult ICILVisitor.VisitInstr_switch(CILParser.Instr_switchContext context) => VisitInstr_switch(context); + GrammarResult ICILVisitor.VisitInstr_tok(CILParser.Instr_tokContext context) => VisitInstr_tok(context); + GrammarResult ICILVisitor.VisitInstr_type(CILParser.Instr_typeContext context) => VisitInstr_type(context); + GrammarResult ICILVisitor.VisitInstr_var(CILParser.Instr_varContext context) => VisitInstr_var(context); + + private static bool ParseIntegerValue(ReadOnlySpan value, out long result) + { + NumberStyles parseStyle = NumberStyles.None; + bool negate = false; + if (value.StartsWith("-".AsSpan())) + { + negate = true; + } + + if (value.StartsWith("0x".AsSpan())) + { + parseStyle = NumberStyles.AllowHexSpecifier; + value = value.Slice(2); + } + else if (value.StartsWith("0".AsSpan())) + { + // Octal support isn't built-in, so we'll do it manually. + result = 0; + for (int i = 0; i < value.Length; i++, result *= 8) + { + int digitValue = value[i] - '0'; + if (digitValue < 0 || digitValue > 7) + { + // COMPAT: native ilasm skips invalid digits silently + continue; + } + result += digitValue; + } + return true; + } + + bool success = long.TryParse(value.ToString(), parseStyle, CultureInfo.InvariantCulture, out result); + if (!success) + { + return false; + } + + result *= negate ? -1 : 1; + return true; + } + + GrammarResult ICILVisitor.VisitInt32(CILParser.Int32Context context) + { + return VisitInt32(context); + } + + public GrammarResult.Literal VisitInt32(CILParser.Int32Context context) + { + IToken node = context.INT32().Symbol; + + ReadOnlySpan value = node.Text.AsSpan(); + + if (!ParseIntegerValue(value, out long num)) + { + _diagnostics.Add(new Diagnostic( + DiagnosticIds.LiteralOutOfRange, + DiagnosticSeverity.Error, + string.Format(DiagnosticMessageTemplates.LiteralOutOfRange, node.Text), + Location.From(node, _documents))); + return new GrammarResult.Literal(0); + } + + return new GrammarResult.Literal((int)num); + } + + + GrammarResult ICILVisitor.VisitInt64(CILParser.Int64Context context) + { + return VisitInt64(context); + } + + public GrammarResult.Literal VisitInt64(CILParser.Int64Context context) + { + IToken node = context.GetChild(0).Symbol; + + ReadOnlySpan value = node.Text.AsSpan(); + + if (!ParseIntegerValue(value, out long num)) + { + _diagnostics.Add(new Diagnostic( + DiagnosticIds.LiteralOutOfRange, + DiagnosticSeverity.Error, + string.Format(DiagnosticMessageTemplates.LiteralOutOfRange, node.Text), + Location.From(node, _documents))); + return new GrammarResult.Literal(0); + } + + return new GrammarResult.Literal(num); + } + + GrammarResult ICILVisitor.VisitIntOrWildcard(CILParser.IntOrWildcardContext context) => VisitIntOrWildcard(context); + public GrammarResult.Literal VisitIntOrWildcard(CILParser.IntOrWildcardContext context) => context.int32() is {} int32 ? new(VisitInt32(int32).Value) : new(null); + public GrammarResult VisitLabels(CILParser.LabelsContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + public GrammarResult VisitLanguageDecl(CILParser.LanguageDeclContext context) => throw new NotImplementedException("TODO: Symbols"); + + public GrammarResult VisitManifestResDecl(CILParser.ManifestResDeclContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitManifestResDecls(CILParser.ManifestResDeclsContext context) => VisitManifestResDecls(context); + public GrammarResult.Literal<(EntityRegistry.EntityBase? implementation, uint offset, ImmutableArray attributes)> VisitManifestResDecls(CILParser.ManifestResDeclsContext context) + { + EntityRegistry.EntityBase? implementation = null; + uint offset = 0; + var attributes = ImmutableArray.CreateBuilder(); + // COMPAT: Priority order for implementation is the following + // AssemblyRef, File, nil + foreach (var decl in context.manifestResDecl()) + { + if (decl.customAttrDecl() is CILParser.CustomAttrDeclContext customAttrDecl) + { + if (VisitCustomAttrDecl(customAttrDecl).Value is { } attr) + { + attributes.Add(attr); + } + } + string kind = decl.GetChild(0).GetText(); + if (kind == ".file" && implementation is not EntityRegistry.AssemblyReferenceEntity) + { + var file = _entityRegistry.FindFile(VisitDottedName(decl.dottedName()).Value); + if (file is null) + { + // TODO: Report diagnostic + } + else + { + implementation = file; + offset = (uint)VisitInt32(decl.int32()).Value; + } + } + else if (kind == ".assembly") + { + var asm = _entityRegistry.FindAssemblyReference(VisitDottedName(decl.dottedName()).Value); + if (asm is null) + { + // TODO: Report diagnostic + } + else + { + implementation = asm; + } + } + } + + return new((implementation, offset, attributes.ToImmutable())); + } + GrammarResult ICILVisitor.VisitManifestResHead(CILParser.ManifestResHeadContext context) => VisitManifestResHead(context); + public GrammarResult.Literal<(string name, string alias, ManifestResourceAttributes attr)> VisitManifestResHead(CILParser.ManifestResHeadContext context) + { + var dottedNames = context.dottedName(); + string name = VisitDottedName(dottedNames[0]).Value; + string alias = dottedNames.Length == 2 ? VisitDottedName(dottedNames[1]).Value : name; + ManifestResourceAttributes attr = 0; + foreach (var attrContext in context.manresAttr()) + { + attr |= VisitManresAttr(attrContext).Value; + } + + return new((name, alias, attr)); + } + + GrammarResult ICILVisitor.VisitManresAttr(CILParser.ManresAttrContext context) => VisitManresAttr(context); + public GrammarResult.Flag VisitManresAttr(CILParser.ManresAttrContext context) + { + return context.GetText() switch + { + "public" => new(ManifestResourceAttributes.Public), + "private" => new(ManifestResourceAttributes.Private), + _ => throw new UnreachableException() + }; + } + + GrammarResult ICILVisitor.VisitMarshalBlob(CILParser.MarshalBlobContext context) => VisitMarshalBlob(context); + public GrammarResult.FormattedBlob VisitMarshalBlob(CILParser.MarshalBlobContext context) + { + if (context.hexbytes() is CILParser.HexbytesContext hexBytes) + { + var bytes = VisitHexbytes(hexBytes).Value; + var blob = new BlobBuilder(bytes.Length); + blob.WriteBytes(bytes); + return new(blob); + } + + return VisitNativeType(context.nativeType()); + } + + GrammarResult ICILVisitor.VisitMarshalClause(CILParser.MarshalClauseContext context) => VisitMarshalClause(context); + public GrammarResult.FormattedBlob VisitMarshalClause(CILParser.MarshalClauseContext context) => VisitMarshalBlob(context.marshalBlob()); + + GrammarResult ICILVisitor.VisitMdtoken(ILAssembler.CILParser.MdtokenContext context) => VisitMdtoken(context); + public GrammarResult.Literal VisitMdtoken(CILParser.MdtokenContext context) + { + return new(_entityRegistry.ResolveHandleToEntity(MetadataTokens.EntityHandle(VisitInt32(context.int32()).Value))); + } + + GrammarResult ICILVisitor.VisitMemberRef(CILParser.MemberRefContext context) => VisitMemberRef(context); + public GrammarResult.Literal VisitMemberRef(CILParser.MemberRefContext context) + { + if (context.mdtoken() is CILParser.MdtokenContext mdToken) + { + return VisitMdtoken(mdToken); + } + + if (context.methodRef() is CILParser.MethodRefContext methodRef) + { + return VisitMethodRef(methodRef); + } + if (context.fieldRef() is CILParser.FieldRefContext fieldRef) + { + return VisitFieldRef(fieldRef); + } + + throw new UnreachableException(); + } + + GrammarResult ICILVisitor.VisitMethAttr(CILParser.MethAttrContext context) => VisitMethAttr(context); + public GrammarResult.Flag VisitMethAttr(CILParser.MethAttrContext context) + { + if (context.int32() is CILParser.Int32Context int32) + { + return new((MethodAttributes)VisitInt32(int32).Value, ShouldAppend: false); + } + string attribute = context.GetText(); + return attribute switch + { + "static" => new(MethodAttributes.Static), + "public" => new(MethodAttributes.Public, MethodAttributes.MemberAccessMask), + "private" => new(MethodAttributes.Private, MethodAttributes.MemberAccessMask), + "family" => new(MethodAttributes.Family, MethodAttributes.MemberAccessMask), + "final" => new(MethodAttributes.Final), + "specialname" => new(MethodAttributes.SpecialName), + "virtual" => new(MethodAttributes.Virtual), + "strict" => new(MethodAttributes.CheckAccessOnOverride), + "abstract" => new(MethodAttributes.Abstract), + "assembly" => new(MethodAttributes.Assembly, MethodAttributes.MemberAccessMask), + "famandassem" => new(MethodAttributes.FamANDAssem, MethodAttributes.MemberAccessMask), + "famorassem" => new(MethodAttributes.FamORAssem, MethodAttributes.MemberAccessMask), + "privatescope" => new(MethodAttributes.PrivateScope, MethodAttributes.MemberAccessMask), + "hidebysig" => new(MethodAttributes.HideBySig), + "newslot" => new(MethodAttributes.NewSlot), + "rtspecialname" => new(0), // COMPAT: Rtspecialname is ignored + "unmanagedexp" => new(MethodAttributes.UnmanagedExport), + "reqsecobj" => new(MethodAttributes.RequireSecObject), + _ => throw new UnreachableException(), + }; + } + + public GrammarResult VisitMethodDecl(CILParser.MethodDeclContext context) + { + Debug.Assert(_currentMethod is not null); + var currentMethod = _currentMethod!; + if (context.EMITBYTE() is not null) + { + currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)VisitInt32(context.GetChild(0)).Value); + } + else if (context.ENTRYPOINT() is not null) + { + _entityRegistry.EntryPoint = currentMethod.Definition; + } + else if (context.ZEROINIT() is not null) + { + currentMethod.Definition.BodyAttributes = MethodBodyAttributes.InitLocals; + } + else if (context.MAXSTACK() is not null) + { + currentMethod.Definition.MaxStack = VisitInt32(context.GetChild(0)).Value; + } + else if (context.LOCALS() is not null) + { + if (context.ChildCount == 3) + { + // init keyword specified + currentMethod.Definition.BodyAttributes = MethodBodyAttributes.InitLocals; + } + var localsScope = currentMethod.LocalsScopes.Count != 0 ? currentMethod.LocalsScopes[currentMethod.LocalsScopes.Count - 1] : new(); + var newLocals = VisitSigArgs(context.sigArgs()).Value; + foreach (var loc in newLocals) + { + // BREAK-COMPAT: We don't allow specifying a local's slot via the [in], [out], or [opt] parameter attributes, or the custom int override. + // This only worked in ilasm due to how ilasm reused fields. + // We're only going to support allowing this tool to determine the slot numbers. + // This blocks two different locals in two different scopes from resuing the same slot + // but that is a very rare scenario (even using more than one .locals block in a method in IL is quite rare) + + // If the local is named, add it to our name-lookup dictionary. + // Otherwise, it will only be accessible via its index. + if (loc.Name is not null) + { + localsScope.Add(loc.Name, currentMethod.AllLocals.Count); + } + currentMethod.AllLocals.Add(loc); + } + } + else if (context.ChildCount == 2 && context.GetChild(0) is CILParser.IdContext labelId) + { + string labelName = VisitId(labelId).Value; + if (!currentMethod.Labels.TryGetValue(labelName, out var label)) + { + label = currentMethod.Definition.MethodBody.DefineLabel(); + } + currentMethod.Definition.MethodBody.MarkLabel(label); + } + else if (context.EXPORT() is not null) + { + // TODO: Need custom ManagedPEBuilder subclass to write the exports directory. + } + else if (context.VTENTRY() is not null) + { + // TODO: Need custom ManagedPEBuilder subclass to write the exports directory. + } + else if (context.OVERRIDE() is not null) + { + BlobBuilder signature = currentMethod.Definition.MethodSignature!; + if (context.callConv() is {} callConv) + { + // We have an explicitly specified signature, so we need to parse it. + signature = new(); + var callConvByte = VisitCallConv(callConv).Value; + var arity = VisitGenArity(context.genArity()).Value; + if (arity > 0) + { + callConvByte |= (byte)SignatureAttributes.Generic; + } + signature.WriteByte(callConvByte); + if (arity > 0) + { + signature.WriteCompressedInteger(arity); + } + var args = VisitSigArgs(context.sigArgs()).Value; + signature.WriteCompressedInteger(args.Length); + VisitType(context.type()).Value.WriteContentTo(signature); + foreach (var arg in args) + { + arg.SignatureBlob.WriteContentTo(signature); + } + } + + var ownerType = VisitTypeSpec(context.typeSpec()).Value; + var methodName = VisitMethodName(context.methodName()).Value; + var methodRef = _entityRegistry.CreateLazilyRecordedMemberReference(ownerType, methodName, signature); + _currentTypeDefinition.PeekOrDefault()!.MethodImplementations.Add(EntityRegistry.CreateUnrecordedMethodImplementation(currentMethod.Definition, methodRef)); + } + else if (context.PARAM() is not null) + { + // BREAK-COMPAT: We require attributes on parameters, generic parameters, and constraints + // to be specified directly after the .param directive, not at any point later in the method. + // This matches the IL outputted by ILDASM, ILSpy, and other tools in the ecosystem. + // Attributes not specified directly after the .param directive are applied to the method itself. + var customAttrDeclarations = context.customAttrDecl(); + if (context.TYPE() is not null) + { + // Type parameters + EntityRegistry.GenericParameterEntity? param = null; + if (context.int32() is { } int32) + { + int index = VisitInt32(int32[0]).Value; + if (index < 0 || index >= currentMethod.Definition.GenericParameters.Count) + { + // TODO: Report generic parameter index out of range + return GrammarResult.SentinelValue.Result; + } + param = currentMethod.Definition.GenericParameters[index]; + } + else + { + string name = VisitDottedName(context.dottedName()).Value; + foreach (var genericParam in currentMethod.Definition.GenericParameters) + { + if (genericParam.Name == name) + { + param = genericParam; + break; + } + } + if (param is null) + { + // TODO: Report unknown generic parameter + return GrammarResult.SentinelValue.Result; + } + } + foreach (var attr in customAttrDeclarations ?? Array.Empty()) + { + var customAttrDecl = VisitCustomAttrDecl(attr).Value; + customAttrDecl?.Owner = param; + } + } + else if (context.CONSTRAINT() is not null) + { + // constraints + EntityRegistry.GenericParameterEntity? param = null; + if (context.int32() is { } int32) + { + int index = VisitInt32(int32[0]).Value; + if (index < 0 || index >= currentMethod.Definition.GenericParameters.Count) + { + // TODO: Report generic parameter index out of range + return GrammarResult.SentinelValue.Result; + } + param = currentMethod.Definition.GenericParameters[index]; + } + else + { + string name = VisitDottedName(context.dottedName()).Value; + foreach (var genericParam in currentMethod.Definition.GenericParameters) + { + if (genericParam.Name == name) + { + param = genericParam; + break; + } + } + if (param is null) + { + // TODO: Report unknown generic parameter + return GrammarResult.SentinelValue.Result; + } + } + EntityRegistry.GenericParameterConstraintEntity? constraint = null; + var baseType = VisitTypeSpec(context.typeSpec()).Value; + foreach (var constraintEntity in param.Constraints) + { + if (constraintEntity.BaseType == baseType) + { + constraint = constraintEntity; + break; + } + } + if (constraint is null) + { + constraint = EntityRegistry.CreateGenericConstraint(baseType); + constraint.Owner = param; + param.Constraints.Add(constraint); + currentMethod.Definition.GenericParameterConstraints.Add(constraint); + } + foreach (var attr in customAttrDeclarations ?? Array.Empty()) + { + var customAttrDecl = VisitCustomAttrDecl(attr).Value; + customAttrDecl?.Owner = constraint; + } + } + else + { + // Adding attibutes to parameters. + int index = VisitInt32(context.int32()[0]).Value; + if (index < 0 || index >= currentMethod.Definition.Parameters.Count) + { + // TODO: Report parameter index out of range + return GrammarResult.SentinelValue.Result; + } + + // TODO: Visit initOpt to get the Constant table entry if a constant value is provided. + var param = currentMethod.Definition.Parameters[index]; + foreach (var attr in customAttrDeclarations ?? Array.Empty()) + { + var customAttrDecl = VisitCustomAttrDecl(attr).Value; + if (customAttrDecl is not null) + { + customAttrDecl.Owner = param; + param.HasCustomAttributes = true; + } + } + } + } + else if (context.secDecl() is {} secDecl) + { + var declarativeSecurity = VisitSecDecl(secDecl).Value; + declarativeSecurity?.Parent = currentMethod.Definition; + } + else if (context.customAttrDecl() is {} customAttr) + { + foreach (var attr in customAttr) + { + var customAttrDecl = VisitCustomAttrDecl(attr).Value; + customAttrDecl?.Owner = currentMethod.Definition; + } + } + else + { + _ = context.children[0].Accept(this); + } + return GrammarResult.SentinelValue.Result; + } + public GrammarResult VisitMethodDecls(CILParser.MethodDeclsContext context) + { + foreach (var decl in context.methodDecl()) + { + VisitMethodDecl(decl); + } + return GrammarResult.SentinelValue.Result; + } + + GrammarResult ICILVisitor.VisitMethodHead(CILParser.MethodHeadContext context) => VisitMethodHead(context); + public GrammarResult.Literal VisitMethodHead(CILParser.MethodHeadContext context) + { + string name = VisitMethodName(context.methodName()).Value; + var containingType = _currentTypeDefinition.PeekOrDefault() ?? _entityRegistry.ModuleType; + var methodDefinition = EntityRegistry.CreateUnrecordedMethodDefinition(containingType, name); + + BlobBuilder methodSignature = new(); + byte sigHeader = VisitCallConv(context.callConv()).Value; + + // Set the current method for type parameter and signature parsing + // so we can resolve generic parameters correctly. + _currentMethod = new(methodDefinition); + var typeParameters = VisitTyparsClause(context.typarsClause()).Value; + if (typeParameters.Length != 0) + { + sigHeader |= (byte)SignatureAttributes.Generic; + } + methodDefinition.MethodAttributes = context.methAttr().Aggregate((MethodAttributes)0, (acc, attr) => acc | VisitMethAttr(attr)); + + if (methodDefinition.MethodAttributes.HasFlag(MethodAttributes.Abstract) && !methodDefinition.ContainingType.Attributes.HasFlag(TypeAttributes.Abstract)) + { + // TODO:Emit error + } + + (EntityRegistry.ModuleReferenceEntity Module, string? EntryPoint, MethodImportAttributes Attributes)? pInvokeInformation = null; + foreach (var pInvokeInfo in context.pinvImpl()) + { + var (moduleName, entryPoint, attributes) = VisitPinvImpl(pInvokeInfo).Value; + if (moduleName is null) + { + // TODO: Emit error + continue; + } + pInvokeInformation = (_entityRegistry.GetOrCreateModuleReference(moduleName, _ => { }), entryPoint, attributes); + } + methodDefinition.MethodImportInformation = pInvokeInformation; + + SignatureHeader parsedHeader = new(sigHeader); + if (methodDefinition.MethodAttributes.HasFlag(MethodAttributes.Static) && (parsedHeader.IsInstance || parsedHeader.HasExplicitThis)) + { + // Error on static + instance. + } + if (parsedHeader.HasExplicitThis && !parsedHeader.IsInstance) + { + // Warn on explicit-this + non-instance + parsedHeader = new(sigHeader |= (byte)SignatureAttributes.Instance); + } + methodSignature.WriteByte(sigHeader); + if (typeParameters.Length != 0) + { + methodSignature.WriteCompressedInteger(typeParameters.Length); + } + for (int i = 0; i < typeParameters.Length; i++) + { + EntityRegistry.GenericParameterEntity? param = typeParameters[i]; + param.Owner = methodDefinition; + param.Index = i; + methodDefinition.GenericParameters.Add(param); + foreach (var constraint in param.Constraints) + { + constraint.Owner = param; + methodDefinition.GenericParameterConstraints.Add(constraint); + } + } + + var args = VisitSigArgs(context.sigArgs()).Value; + methodSignature.WriteCompressedInteger(args.Length); + + SignatureArg returnValue = new(VisitParamAttr(context.paramAttr()).Value, VisitType(context.type()).Value, VisitMarshalClause(context.marshalClause()).Value, null); + + returnValue.SignatureBlob.WriteContentTo(methodSignature); + methodDefinition.Parameters.Add(EntityRegistry.CreateParameter(returnValue.Attributes, returnValue.Name, returnValue.MarshallingDescriptor, 0)); + for (int i = 0; i < args.Length; i++) + { + SignatureArg? arg = args[i]; + arg.SignatureBlob.WriteContentTo(methodSignature); + methodDefinition.Parameters.Add(EntityRegistry.CreateParameter(arg.Attributes, arg.Name, arg.MarshallingDescriptor, i + 1)); + } + // We've parsed all signature information. We can reset the current method now (the caller will handle setting/unsetting it for the method body). + _currentMethod = null; + methodDefinition.SignatureHeader = parsedHeader; + methodDefinition.MethodSignature = methodSignature; + + methodDefinition.ImplementationAttributes = context.implAttr().Aggregate((MethodImplAttributes)0, (acc, attr) => acc | VisitImplAttr(attr)); + if (!EntityRegistry.TryAddMethodDefinitionToContainingType(methodDefinition)) + { + // TODO: Report duplicate method + } + + return new(methodDefinition); + } + + GrammarResult ICILVisitor.VisitMethodName(CILParser.MethodNameContext context) => VisitMethodName(context); + public GrammarResult.String VisitMethodName(CILParser.MethodNameContext context) + { + IParseTree child = context.GetChild(0); + if (child is ITerminalNode terminal) + { + return new(terminal.Symbol.Text); + } + Debug.Assert(child is CILParser.DottedNameContext); + return (GrammarResult.String)child.Accept(this); + } + + private bool _expectInstance; + private Subsystem _subsystem = Subsystem.WindowsCui; + private CorFlags _corflags = CorFlags.ILOnly; + private int _alignment = 0x200; + private long _imageBase = 0x00400000; + private long _stackReserve; + + GrammarResult ICILVisitor.VisitMethodRef(CILParser.MethodRefContext context) => VisitMethodRef(context); + public GrammarResult.Literal VisitMethodRef(CILParser.MethodRefContext context) + { + if (context.mdtoken() is CILParser.MdtokenContext token) + { + return new(VisitMdtoken(token).Value); + } + if (context.dottedName() is CILParser.DottedNameContext) + { + // TODO: typedef + throw new NotImplementedException(); + } + BlobBuilder methodRefSignature = new(); + byte callConv = VisitCallConv(context.callConv()).Value; + EntityRegistry.TypeEntity owner = _currentTypeDefinition.PeekOrDefault() ?? _entityRegistry.ModuleType; + if (context.typeSpec() is CILParser.TypeSpecContext typeSpec) + { + owner = VisitTypeSpec(typeSpec).Value; + } + string name = VisitMethodName(context.methodName()).Value; + BlobBuilder? methodSpecSignature = null; + int numGenericParameters = 0; + if (context.typeArgs() is CILParser.TypeArgsContext typeArgs) + { + var types = typeArgs.type(); + numGenericParameters = types.Length; + if (types.Length != 0) + { + methodSpecSignature = new(); + methodSpecSignature.WriteByte((byte)SignatureKind.MethodSpecification); + VisitTypeArgs(typeArgs).Value.WriteContentTo(methodSpecSignature); + } + } + else if (context.genArityNotEmpty() is CILParser.GenArityNotEmptyContext genArityNotEmpty) + { + numGenericParameters = VisitGenArityNotEmpty(genArityNotEmpty).Value; + } + if (numGenericParameters != 0) + { + callConv |= (byte)SignatureAttributes.Generic; + } + if (_expectInstance && (callConv & (byte)SignatureAttributes.Instance) == 0) + { + // TODO: Warn for missing instance call-conv + callConv |= (byte)SignatureAttributes.Instance; + } + methodRefSignature.WriteByte(callConv); + if (numGenericParameters != 0) + { + methodRefSignature.WriteCompressedInteger(numGenericParameters); + } + var args = VisitSigArgs(context.sigArgs()).Value; + methodRefSignature.WriteCompressedInteger(args.Length); + // Write return type + VisitType(context.type()).Value.WriteContentTo(methodRefSignature); + // Write arg signatures + foreach (var arg in args) + { + arg.SignatureBlob.WriteContentTo(methodRefSignature); + } + + var memberRef = _entityRegistry.CreateLazilyRecordedMemberReference(owner, name, methodRefSignature); + + if (methodSpecSignature is not null) + { + return new(_entityRegistry.GetOrCreateMethodSpecification(memberRef, methodSpecSignature)); + } + + return new(memberRef); + } + public GrammarResult VisitModuleHead(CILParser.ModuleHeadContext context) + { + if (context.ChildCount > 2) + { + _ = _entityRegistry.GetOrCreateModuleReference(VisitDottedName(context.dottedName()).Value, _ => { }); + return GrammarResult.SentinelValue.Result; + } + + _entityRegistry.Module.Name = VisitDottedName(context.dottedName()).Value; + return GrammarResult.SentinelValue.Result; + } + public GrammarResult VisitMscorlib(CILParser.MscorlibContext context) => GrammarResult.SentinelValue.Result; + + GrammarResult ICILVisitor.VisitNameSpaceHead(CILParser.NameSpaceHeadContext context) => VisitNameSpaceHead(context); + + public static GrammarResult.String VisitNameSpaceHead(CILParser.NameSpaceHeadContext context) => VisitDottedName(context.dottedName()); + + GrammarResult ICILVisitor.VisitNameValPair(CILParser.NameValPairContext context) => VisitNameValPair(context); + public GrammarResult.Literal> VisitNameValPair(CILParser.NameValPairContext context) + { + return new(new(VisitCompQstring(context.compQstring()).Value, VisitCaValue(context.caValue()).Value)); + } + + GrammarResult ICILVisitor.VisitNameValPairs(CILParser.NameValPairsContext context) => VisitNameValPairs(context); + public GrammarResult.Sequence> VisitNameValPairs(CILParser.NameValPairsContext context) => new(context.nameValPair().Select(pair => VisitNameValPair(pair).Value).ToImmutableArray()); + + GrammarResult ICILVisitor.VisitNativeType(CILParser.NativeTypeContext context) => VisitNativeType(context); + public GrammarResult.FormattedBlob VisitNativeType(CILParser.NativeTypeContext context) + { + if (context.nativeTypeArrayPointerInfo() is not CILParser.NativeTypeArrayPointerInfoContext[] arrayPointerInfo) + { + return VisitNativeTypeElement(context.nativeTypeElement()); + } + var prefix = new BlobBuilder(arrayPointerInfo.Length); + var elementType = VisitNativeTypeElement(context.nativeTypeElement()).Value; + var suffix = new BlobBuilder(); + + for (int i = arrayPointerInfo.Length - 1; i >= 0; i--) + { + if (arrayPointerInfo[i] is CILParser.PointerNativeTypeContext) + { + // TODO: warn on deprecated native type + const int NATIVE_TYPE_PTR = 0x10; + prefix.WriteByte(NATIVE_TYPE_PTR); + } + else + { + prefix.WriteByte((byte)UnmanagedType.LPArray); + if (elementType.Count == 0) + { + // We need to have an element type for arrays, + // so write the invalid NATIVE_TYPE_MAX value so we have something parsable. + const int NATIVE_TYPE_MAX = 0x50; + elementType.WriteByte(NATIVE_TYPE_MAX); + } + } + } + + for (int i = 0; i < arrayPointerInfo.Length; i++) + { + if (arrayPointerInfo[i] is CILParser.PointerArrayTypeSizeContext size) + { + suffix.WriteCompressedInteger(VisitInt32(size.int32()).Value); + } + else if (arrayPointerInfo[i] is CILParser.PointerArrayTypeSizeParamIndexContext sizeParamIndex) + { + var ints = sizeParamIndex.int32(); + suffix.WriteCompressedInteger(VisitInt32(ints[1]).Value); + suffix.WriteCompressedInteger(VisitInt32(ints[2]).Value); + suffix.WriteCompressedInteger(1); // Write that the paramIndex parameter was specified + } + else if (arrayPointerInfo[i] is CILParser.PointerArrayTypeParamIndexContext paramIndex) + { + suffix.WriteCompressedInteger(0); + suffix.WriteCompressedInteger(VisitInt32(paramIndex.int32()).Value); + suffix.WriteCompressedInteger(0); // Write that the paramIndex parameter was not specified + } + } + + prefix.LinkSuffix(elementType); + prefix.LinkSuffix(suffix); + return new(prefix); + } + + GrammarResult ICILVisitor.VisitNativeTypeElement(CILParser.NativeTypeElementContext context) => VisitNativeTypeElement(context); + public GrammarResult.FormattedBlob VisitNativeTypeElement(CILParser.NativeTypeElementContext context) + { + var blob = new BlobBuilder(5); + if (context.dottedName() is CILParser.DottedNameContext typedef) + { + _ = typedef; + // TODO: typedef + return new(blob); + } + + if (context.marshalType is null) + { + return new(blob); + } + + switch (context.marshalType.Type) + { + case CILParser.CUSTOM: + { + blob.WriteByte((byte)UnmanagedType.CustomMarshaler); + CILParser.CompQstringContext[] strings = context.compQstring(); + if (strings.Length == 4) + { + // TODO: warn on deprecated 4-string form of custom marshaller. + blob.WriteSerializedString(VisitCompQstring(strings[0]).Value); + blob.WriteSerializedString(VisitCompQstring(strings[1]).Value); + blob.WriteSerializedString(VisitCompQstring(strings[2]).Value); + blob.WriteSerializedString(VisitCompQstring(strings[3]).Value); + } + else + { + Debug.Assert(strings.Length == 2); + blob.WriteCompressedInteger(0); + blob.WriteCompressedInteger(0); + blob.WriteSerializedString(VisitCompQstring(strings[0]).Value); + blob.WriteSerializedString(VisitCompQstring(strings[1]).Value); + } + break; + } + case CILParser.SYSSTRING: + blob.WriteByte((byte)UnmanagedType.ByValTStr); + blob.WriteCompressedInteger(VisitInt32(context.int32()).Value); + break; + case CILParser.ARRAY: + blob.WriteByte((byte)UnmanagedType.ByValArray); + blob.WriteCompressedInteger(VisitInt32(context.int32()).Value); + blob.LinkSuffix(VisitNativeType(context.nativeType()).Value); + break; + case CILParser.VARIANT: + // TODO: warn on deprecated native type + const int NATIVE_TYPE_VARIANT = 0xe; + blob.WriteByte(NATIVE_TYPE_VARIANT); + break; +#pragma warning disable CS0618 // Type or member is obsolete + case CILParser.CURRENCY: + blob.WriteByte((byte)UnmanagedType.Currency); + break; +#pragma warning restore CS0618 // Type or member is obsolete + case CILParser.SYSCHAR: + // TODO: warn on deprecated native type + const int NATIVE_TYPE_SYSCHAR = 0xd; + blob.WriteByte(NATIVE_TYPE_SYSCHAR); + break; + case CILParser.VOID: + // TODO: warn on deprecated native type + const int NATIVE_TYPE_VOID = 0x1; + blob.WriteByte(NATIVE_TYPE_VOID); + break; + case CILParser.BOOL: + blob.WriteByte((byte)UnmanagedType.Bool); + break; + case CILParser.INT8: + blob.WriteByte((byte)UnmanagedType.I1); + break; + case CILParser.INT16: + blob.WriteByte((byte)UnmanagedType.I2); + break; + case CILParser.INT32_: + blob.WriteByte((byte)UnmanagedType.I4); + break; + case CILParser.INT64_: + blob.WriteByte((byte)UnmanagedType.I8); + break; + case CILParser.FLOAT32: + blob.WriteByte((byte)UnmanagedType.R4); + break; + case CILParser.FLOAT64_: + blob.WriteByte((byte)UnmanagedType.R8); + break; + case CILParser.ERROR: + blob.WriteByte((byte)UnmanagedType.Error); + break; + case CILParser.UINT8: + blob.WriteByte((byte)UnmanagedType.U1); + break; + case CILParser.UINT16: + blob.WriteByte((byte)UnmanagedType.U2); + break; + case CILParser.UINT32: + blob.WriteByte((byte)UnmanagedType.U4); + break; + case CILParser.UINT64: + blob.WriteByte((byte)UnmanagedType.U8); + break; + case CILParser.DECIMAL: + // TODO: warn on deprecated native type + const int NATIVE_TYPE_DECIMAL = 0x11; + blob.WriteByte(NATIVE_TYPE_DECIMAL); + break; + case CILParser.DATE: + // TODO: warn on deprecated native type + const int NATIVE_TYPE_DATE = 0x12; + blob.WriteByte(NATIVE_TYPE_DATE); + break; + case CILParser.BSTR: + blob.WriteByte((byte)UnmanagedType.BStr); + break; + case CILParser.LPSTR: + blob.WriteByte((byte)UnmanagedType.LPStr); + break; + case CILParser.LPWSTR: + blob.WriteByte((byte)UnmanagedType.LPWStr); + break; + case CILParser.LPTSTR: + blob.WriteByte((byte)UnmanagedType.LPTStr); + break; + case CILParser.OBJECTREF: + // TODO: warn on deprecated native type + const int NATIVE_TYPE_OBJECTREF = 0x18; + blob.WriteByte(NATIVE_TYPE_OBJECTREF); + break; + case CILParser.IUNKNOWN: + { + blob.WriteByte((byte)UnmanagedType.IUnknown); + if (VisitIidParamIndex(context.iidParamIndex()) is { Value: int index }) + { + blob.WriteCompressedInteger(index); + } + break; + } + case CILParser.IDISPATCH: + { + blob.WriteByte((byte)UnmanagedType.IDispatch); + if (VisitIidParamIndex(context.iidParamIndex()) is { Value: int index }) + { + blob.WriteCompressedInteger(index); + } + break; + } + case CILParser.STRUCT: + blob.WriteByte((byte)UnmanagedType.Struct); + break; + case CILParser.INTERFACE: + { + blob.WriteByte((byte)UnmanagedType.Interface); + if (VisitIidParamIndex(context.iidParamIndex()) is { Value: int index }) + { + blob.WriteCompressedInteger(index); + } + break; + } + case CILParser.SAFEARRAY: + blob.WriteByte((byte)UnmanagedType.SafeArray); + blob.WriteCompressedInteger((int)VisitVariantType(context.variantType()).Value); + if (context.compQstring() is { Length: 1 } safeArrayCustomType) + { + string str = VisitCompQstring(safeArrayCustomType[0]).Value; + blob.WriteSerializedString(str); + } + else + { + blob.WriteCompressedInteger(0); + } + break; + case CILParser.INT: + blob.WriteByte((byte)UnmanagedType.SysInt); + break; + case CILParser.UINT: + blob.WriteByte((byte)UnmanagedType.SysUInt); + break; + case CILParser.NESTEDSTRUCT: + // TODO: warn on deprecated native type + const int NATIVE_TYPE_NESTEDSTRUCT = 0x21; + blob.WriteByte(NATIVE_TYPE_NESTEDSTRUCT); + break; +#pragma warning disable CS0618 // Type or member is obsolete + case CILParser.BYVALSTR: + blob.WriteByte((byte)UnmanagedType.VBByRefStr); + break; + case CILParser.ANSIBSTR: + blob.WriteByte((byte)UnmanagedType.AnsiBStr); + break; + case CILParser.TBSTR: + blob.WriteByte((byte)UnmanagedType.TBStr); + break; +#pragma warning restore CS0618 // Type or member is obsolete + case CILParser.VARIANTBOOL: + blob.WriteByte((byte)UnmanagedType.VariantBool); + break; + case CILParser.METHOD: + blob.WriteByte((byte)UnmanagedType.FunctionPtr); + break; + case CILParser.LPSTRUCT: + blob.WriteByte((byte)UnmanagedType.LPStruct); + break; +#pragma warning disable CS0618 // Type or member is obsolete + case CILParser.ANY: + blob.WriteByte((byte)UnmanagedType.AsAny); + break; +#pragma warning restore CS0618 // Type or member is obsolete + } + + return new(blob); + } + + GrammarResult ICILVisitor.VisitObjSeq(CILParser.ObjSeqContext context) => VisitObjSeq(context); + public GrammarResult.FormattedBlob VisitObjSeq(CILParser.ObjSeqContext context) + { + // We're going to add all of the elements in the sequence as prefix blobs to this blob. + BlobBuilder objSeqBlob = new(0); + foreach (var item in context.serInit()) + { + objSeqBlob.LinkPrefix(VisitSerInit(item).Value); + } + return new(objSeqBlob); + } + + GrammarResult ICILVisitor.VisitOwnerType(CILParser.OwnerTypeContext context) => VisitOwnerType(context); + public GrammarResult.Literal VisitOwnerType(CILParser.OwnerTypeContext context) + { + if (context.memberRef() is CILParser.MemberRefContext memberRef) + { + return VisitMemberRef(memberRef); + } + if (context.typeSpec() is CILParser.TypeSpecContext typeSpec) + { + return new(VisitTypeSpec(typeSpec).Value); + } + throw new UnreachableException(); + } + GrammarResult ICILVisitor.VisitParamAttr(CILParser.ParamAttrContext context) => VisitParamAttr(context); + public GrammarResult.Literal VisitParamAttr(CILParser.ParamAttrContext context) + { + ParameterAttributes attributes = 0; + foreach (var element in context.paramAttrElement()) + { + attributes |= VisitParamAttrElement(element); + } + return new(attributes); + } + + GrammarResult ICILVisitor.VisitParamAttrElement(CILParser.ParamAttrElementContext context) => VisitParamAttrElement(context); + public GrammarResult.Flag VisitParamAttrElement(CILParser.ParamAttrElementContext context) + { + if (context.int32() is CILParser.Int32Context int32) + { + return new((ParameterAttributes)(VisitInt32(int32).Value + 1), ShouldAppend: false); + } + return context switch + { + { @in: not null } => new(ParameterAttributes.In), + { @out: not null } => new(ParameterAttributes.Out), + { opt: not null } => new(ParameterAttributes.Optional), + _ => throw new UnreachableException() + }; + } + + GrammarResult ICILVisitor.VisitPinvAttr(CILParser.PinvAttrContext context) => VisitPinvAttr(context); + public GrammarResult.Flag VisitPinvAttr(CILParser.PinvAttrContext context) + { + if (context.int32() is CILParser.Int32Context int32) + { + return new((MethodImportAttributes)VisitInt32(int32).Value, ShouldAppend: false); + } + switch (context.GetText()) + { + case "nomangle": + return new(MethodImportAttributes.ExactSpelling); + case "ansi": + return new(MethodImportAttributes.CharSetAnsi); + case "unicode": + return new(MethodImportAttributes.CharSetUnicode); + case "autochar": + return new(MethodImportAttributes.CharSetAuto); + case "lasterr": + return new(MethodImportAttributes.SetLastError); + case "winapi": + return new(MethodImportAttributes.CallingConventionWinApi); + case "cdecl": + return new(MethodImportAttributes.CallingConventionCDecl); + case "stdcall": + return new(MethodImportAttributes.CallingConventionStdCall); + case "thiscall": + return new(MethodImportAttributes.CallingConventionThisCall); + case "fastcall": + return new(MethodImportAttributes.CallingConventionFastCall); + case "bestfit:on": + return new(MethodImportAttributes.BestFitMappingEnable); + case "bestfit:off": + return new(MethodImportAttributes.BestFitMappingDisable); + case "charmaperror:on": + return new(MethodImportAttributes.ThrowOnUnmappableCharEnable); + case "charmaperror:off": + return new(MethodImportAttributes.ThrowOnUnmappableCharDisable); + default: + throw new UnreachableException(); + } + } + + GrammarResult ICILVisitor.VisitPinvImpl(CILParser.PinvImplContext context) => VisitPinvImpl(context); + public GrammarResult.Literal<(string? ModuleName, string? EntryPointName, MethodImportAttributes Attributes)> VisitPinvImpl(CILParser.PinvImplContext context) + { + MethodImportAttributes attrs = MethodImportAttributes.None; + foreach (var attr in context.pinvAttr()) + { + attrs |= VisitPinvAttr(attr); + } + var names = context.compQstring(); + string? moduleName = names.Length > 0 ? VisitCompQstring(names[0]).Value : null; + string? entryPointName = names.Length > 1 ? VisitCompQstring(names[1]).Value : null; + return new((moduleName, entryPointName, attrs)); + } + + GrammarResult ICILVisitor.VisitPropAttr(CILParser.PropAttrContext context) => VisitPropAttr(context); + public static GrammarResult.Flag VisitPropAttr(CILParser.PropAttrContext context) + { + return context.GetText() switch + { + "specialname" => new(PropertyAttributes.SpecialName), + "rtspecialname" => new(0), // COMPAT: Ignore + _ => throw new UnreachableException(), + }; + } + + GrammarResult ICILVisitor.VisitPropDecl(CILParser.PropDeclContext context) => VisitPropDecl(context); + public GrammarResult.Literal<(MethodSemanticsAttributes, EntityRegistry.EntityBase)?> VisitPropDecl(CILParser.PropDeclContext context) + { + if (context.ChildCount != 2) + { + return new(null); + } + string accessor = context.GetChild(0).GetText(); + EntityRegistry.EntityBase memberReference = VisitMethodRef(context.methodRef()).Value; + MethodSemanticsAttributes methodSemanticsAttributes = accessor switch + { + ".set" => MethodSemanticsAttributes.Setter, + ".get" => MethodSemanticsAttributes.Getter, + ".other" => MethodSemanticsAttributes.Other, + _ => throw new UnreachableException(), + }; + return new((methodSemanticsAttributes, memberReference)); + } + + GrammarResult ICILVisitor.VisitPropDecls(CILParser.PropDeclsContext context) => VisitPropDecls(context); + public GrammarResult.Sequence<(MethodSemanticsAttributes, EntityRegistry.EntityBase)> VisitPropDecls(CILParser.PropDeclsContext context) + => new( + context.propDecl() + .Select(decl => VisitPropDecl(decl).Value) + .Where(decl => decl is not null) + .Select(decl => decl!.Value).ToImmutableArray()); + + GrammarResult ICILVisitor.VisitPropHead(ILAssembler.CILParser.PropHeadContext context) => VisitPropHead(context); + public GrammarResult.Literal VisitPropHead(CILParser.PropHeadContext context) + { + var propAttrs = context.propAttr().Select(VisitPropAttr).Aggregate((PropertyAttributes)0, (a, b) => a | b); + var name = VisitDottedName(context.dottedName()).Value; + + var signature = new BlobBuilder(); + byte callConv = (byte)(VisitCallConv(context.callConv()).Value | (byte)SignatureKind.Property); + signature.WriteByte(callConv); + var args = VisitSigArgs(context.sigArgs()).Value; + signature.WriteCompressedInteger(args.Length); + VisitType(context.type()).Value.WriteContentTo(signature); + foreach (var arg in args) + { + arg.SignatureBlob.WriteContentTo(signature); + } + + // TODO: Handle initOpt + _ = VisitInitOpt(context.initOpt()); + return new(new(propAttrs, signature, name)); + } + + GrammarResult ICILVisitor.VisitRepeatOpt(CILParser.RepeatOptContext context) => VisitRepeatOpt(context); + public GrammarResult.Literal VisitRepeatOpt(CILParser.RepeatOptContext context) => context.int32() is {} int32 ? new(VisitInt32(int32).Value) : new(null); + + public GrammarResult VisitScopeBlock(CILParser.ScopeBlockContext context) + { + int numLocalsScopes = _currentMethod!.LocalsScopes.Count; + _ = VisitMethodDecls(context.methodDecls()); + _currentMethod.LocalsScopes.RemoveRange(numLocalsScopes, _currentMethod.LocalsScopes.Count - numLocalsScopes); + return GrammarResult.SentinelValue.Result; + } + + GrammarResult ICILVisitor.VisitSecAction(CILParser.SecActionContext context) => VisitSecAction(context); + public static GrammarResult.Literal VisitSecAction(CILParser.SecActionContext context) + { + return context.GetText() switch + { + "request" => new(DeclarativeSecurityAction.Request), + "demand" => new(DeclarativeSecurityAction.Demand), + "assert" => new(DeclarativeSecurityAction.Assert), + "deny" => new(DeclarativeSecurityAction.Deny), + "permitonly" => new(DeclarativeSecurityAction.PermitOnly), + "linkcheck" => new(DeclarativeSecurityAction.LinkDemand), + "inheritcheck" => new(DeclarativeSecurityAction.InheritanceDemand), + "reqmin" => new(DeclarativeSecurityAction.RequestMinimum), + "reqopt" => new(DeclarativeSecurityAction.RequestOptional), + "reqrefuse" => new(DeclarativeSecurityAction.RequestRefuse), + "prejitgrant" => new(DeclarativeSecurityAction.PrejitGrant), + "prejitdeny" => new(DeclarativeSecurityAction.PrejitDeny), + "noncasdemand" => new(DeclarativeSecurityAction.NonCasDemand), + "noncaslinkdemand" => new(DeclarativeSecurityAction.NonCasLinkDemand), + "noncasinheritance" => new(DeclarativeSecurityAction.NonCasInheritanceDemand), + _ => throw new UnreachableException() + }; + } + GrammarResult ICILVisitor.VisitSecAttrBlob(CILParser.SecAttrBlobContext context) => VisitSecAttrBlob(context); + public GrammarResult.FormattedBlob VisitSecAttrBlob(CILParser.SecAttrBlobContext context) + { + var blob = new BlobBuilder(); + + string attributeName = string.Empty; + + if (context.typeSpec() is CILParser.TypeSpecContext typeSpec && VisitTypeSpec(typeSpec).Value is EntityRegistry.IHasReflectionNotation reflectionNotation) + { + attributeName = reflectionNotation.ReflectionNotation; + } + else if (context.SQSTRING() is { } sqstring) + { + attributeName = sqstring.GetText(); + } + + blob.WriteSerializedString(attributeName); + VisitCustomBlobNVPairs(context.customBlobNVPairs()).Value.WriteContentTo(blob); + + return new(blob); + } + + GrammarResult ICILVisitor.VisitSecAttrSetBlob(CILParser.SecAttrSetBlobContext context) => VisitSecAttrSetBlob(context); + public GrammarResult.FormattedBlob VisitSecAttrSetBlob(CILParser.SecAttrSetBlobContext context) + { + BlobBuilder blob = new(); + var secAttributes = context.secAttrBlob(); + blob.WriteByte((byte)'.'); + blob.WriteCompressedInteger(secAttributes.Length); + foreach (var secAttribute in secAttributes) + { + VisitSecAttrBlob(secAttribute).Value.WriteContentTo(blob); + } + return new(blob); + } + + GrammarResult ICILVisitor.VisitSecDecl(CILParser.SecDeclContext context) => VisitSecDecl(context); + public GrammarResult.Literal VisitSecDecl(CILParser.SecDeclContext context) + { + if (context.PERMISSION() is not null) + { + // TODO: Report unsupported error + // Cannot convert individual SecurityAttribute-based permissions to a PermissionSet without a runtime. + return new(null); + } + DeclarativeSecurityAction action = VisitSecAction(context.secAction()).Value; + BlobBuilder value; + if (context.secAttrSetBlob() is CILParser.SecAttrSetBlobContext setBlob) + { + value = VisitSecAttrSetBlob(setBlob).Value; + } + else if (context.bytes() is CILParser.BytesContext bytes) + { + value = new(); + value.WriteBytes(VisitBytes(bytes).Value); + } + else if (context.compQstring() is CILParser.CompQstringContext str) + { + value = new BlobBuilder(); + value.WriteUTF16(VisitCompQstring(str).Value); + value.WriteUTF16("\0"); + } + else + { + throw new UnreachableException(); + } + return new(_entityRegistry.CreateDeclarativeSecurityAttribute(action, value)); + } + + internal abstract record ExceptionClause(LabelHandle Start, LabelHandle End) + { + internal sealed record Catch(EntityRegistry.TypeEntity Type, LabelHandle Start, LabelHandle End) : ExceptionClause(Start, End); + + internal sealed record Filter(LabelHandle FilterStart, LabelHandle Start, LabelHandle End) : ExceptionClause(Start, End); + + internal sealed record Finally(LabelHandle Start, LabelHandle End) : ExceptionClause(Start, End); + + internal sealed record Fault(LabelHandle Start, LabelHandle End) : ExceptionClause(Start, End); + } + + public GrammarResult VisitSehBlock(CILParser.SehBlockContext context) + { + var (tryStart, tryEnd) = VisitTryBlock(context.tryBlock()).Value; + foreach (var clause in VisitSehClauses(context.sehClauses()).Value) + { + switch (clause) + { + case ExceptionClause.Finally finallyClause: + _currentMethod!.Definition.MethodBody.ControlFlowBuilder!.AddFinallyRegion(tryStart, tryEnd, finallyClause.Start, finallyClause.End); + break; + case ExceptionClause.Fault faultClause: + _currentMethod!.Definition.MethodBody.ControlFlowBuilder!.AddFaultRegion(tryStart, tryEnd, faultClause.Start, faultClause.End); + break; + case ExceptionClause.Catch catchClause: + _currentMethod!.Definition.MethodBody.ControlFlowBuilder!.AddCatchRegion(tryStart, tryEnd, catchClause.Start, catchClause.End, catchClause.Type.Handle); + break; + case ExceptionClause.Filter filterClause: + _currentMethod!.Definition.MethodBody.ControlFlowBuilder!.AddFilterRegion(tryStart, tryEnd, filterClause.Start, filterClause.End, filterClause.FilterStart); + break; + default: + throw new UnreachableException(); + } + } + return GrammarResult.SentinelValue.Result; + } + GrammarResult ICILVisitor.VisitSehClause(CILParser.SehClauseContext context) => VisitSehClause(context); + public GrammarResult.Literal VisitSehClause(CILParser.SehClauseContext context) + { + var (start, end) = VisitHandlerBlock(context.handlerBlock()).Value; + + if (context.finallyClause() is not null) + { + return new(new ExceptionClause.Finally(start, end)); + } + if (context.faultClause() is not null) + { + return new(new ExceptionClause.Fault(start, end)); + } + if (context.catchClause() is CILParser.CatchClauseContext catchClause) + { + return new(new ExceptionClause.Catch(VisitCatchClause(catchClause).Value, start, end)); + } + if (context.filterClause() is CILParser.FilterClauseContext filterClause) + { + return new(new ExceptionClause.Filter(VisitFilterClause(filterClause).Value, start, end)); + } + + throw new UnreachableException(); + } + + GrammarResult ICILVisitor.VisitSehClauses(CILParser.SehClausesContext context) => VisitSehClauses(context); + public GrammarResult.Sequence VisitSehClauses(CILParser.SehClausesContext context) => new(context.sehClause().Select(clause => VisitSehClause(clause).Value).ToImmutableArray()); + + GrammarResult ICILVisitor.VisitSerializType(CILParser.SerializTypeContext context) => VisitSerializType(context); + public GrammarResult.FormattedBlob VisitSerializType(CILParser.SerializTypeContext context) + { + var blob = new BlobBuilder(); + if (context.ARRAY_TYPE_NO_BOUNDS() is not null) + { + blob.WriteByte((byte)SerializationTypeCode.SZArray); + } + VisitSerializTypeElement(context.serializTypeElement()).Value.WriteContentTo(blob); + return new(blob); + } + + GrammarResult ICILVisitor.VisitSerializTypeElement(CILParser.SerializTypeElementContext context) => VisitSerializTypeElement(context); + public GrammarResult.FormattedBlob VisitSerializTypeElement(CILParser.SerializTypeElementContext context) + { + if (context.simpleType() is CILParser.SimpleTypeContext simpleType) + { + BlobBuilder blob = new(1); + blob.WriteByte((byte)VisitSimpleType(simpleType).Value); + return new(blob); + } + if (context.dottedName() is CILParser.DottedNameContext) + { + // TODO: typedef + throw new NotImplementedException(); + } + if (context.TYPE() is not null) + { + BlobBuilder blob = new BlobBuilder(1); + blob.WriteByte((byte)SerializationTypeCode.Type); + return new(blob); + } + if (context.OBJECT() is not null) + { + BlobBuilder blob = new BlobBuilder(1); + blob.WriteByte((byte)SerializationTypeCode.TaggedObject); + return new(blob); + } + if (context.ENUM() is not null) + { + BlobBuilder blob = new BlobBuilder(); + blob.WriteByte((byte)SerializationTypeCode.Enum); + if (context.SQSTRING() is ITerminalNode sqString) + { + blob.WriteSerializedString(sqString.GetText()); + } + else + { + Debug.Assert(context.className() is not null); + blob.WriteSerializedString((VisitClassName(context.className()).Value as EntityRegistry.IHasReflectionNotation)?.ReflectionNotation ?? ""); + } + return new(blob); + } + throw new UnreachableException(); + } + + GrammarResult ICILVisitor.VisitSerInit(CILParser.SerInitContext context) => VisitSerInit(context); + public GrammarResult.FormattedBlob VisitSerInit(CILParser.SerInitContext context) + { + if (context.fieldSerInit() is CILParser.FieldSerInitContext fieldSerInit) + { + return VisitFieldSerInit(fieldSerInit); + } + + if (context.serInit() is CILParser.SerInitContext serInit) + { + Debug.Assert(context.OBJECT() is not null); + BlobBuilder taggedObjectBlob = new(1); + taggedObjectBlob.WriteByte((byte)SerializationTypeCode.TaggedObject); + taggedObjectBlob.LinkSuffix(VisitSerInit(serInit).Value); + return new(taggedObjectBlob); + } + + if (context.int32() is not CILParser.Int32Context arrLength) + { + // The only cases where there is no int32 node is when the value is a string or type. + BlobBuilder blob = new(); + blob.WriteByte((byte)GetTypeCodeForToken(((ITerminalNode)context.GetChild(0)).Symbol.Type)); + if (context.className() is CILParser.ClassNameContext className) + { + blob.WriteSerializedString(VisitClassName(className).Value is EntityRegistry.IHasReflectionNotation reflection ? reflection.ReflectionNotation : string.Empty); + } + else + { + blob.WriteSerializedString(context.SQSTRING()?.Symbol.Text); + } + return new(blob); + } + + int tokenType = ((ITerminalNode)context.GetChild(0)).Symbol.Type; + + // 1 byte for ELEMENT_TYPE_SZARRAY, 1 byte for the array element type, 4 bytes for the length. + BlobBuilder arrayHeader = new(6); + arrayHeader.WriteByte((byte)SerializationTypeCode.SZArray); + arrayHeader.WriteByte((byte)GetTypeCodeForToken(tokenType)); + arrayHeader.WriteInt32(VisitInt32(arrLength).Value); + var sequenceResult = (GrammarResult.FormattedBlob)Visit(context.GetRuleContext(0)); + arrayHeader.LinkSuffix(sequenceResult.Value); + return new(arrayHeader); + } + + private static SerializationTypeCode GetTypeCodeForToken(int tokenType) + { + return tokenType switch + { + CILParser.INT8 => SerializationTypeCode.SByte, + CILParser.UINT8 => SerializationTypeCode.Byte, + CILParser.INT16 => SerializationTypeCode.Int16, + CILParser.UINT16 => SerializationTypeCode.UInt16, + CILParser.INT32_ => SerializationTypeCode.Int32, + CILParser.UINT32 => SerializationTypeCode.UInt32, + CILParser.INT64_ => SerializationTypeCode.Int64, + CILParser.UINT64 => SerializationTypeCode.UInt64, + CILParser.FLOAT32 => SerializationTypeCode.Single, + CILParser.FLOAT64_ => SerializationTypeCode.Double, + CILParser.CHAR => SerializationTypeCode.Char, + CILParser.BOOL => SerializationTypeCode.Boolean, + CILParser.STRING => SerializationTypeCode.String, + CILParser.TYPE => SerializationTypeCode.Type, + CILParser.OBJECT => SerializationTypeCode.TaggedObject, + _ => throw new UnreachableException() + }; + } + + GrammarResult ICILVisitor.VisitSigArg(CILParser.SigArgContext context) => VisitSigArg(context); + public GrammarResult.Literal VisitSigArg(CILParser.SigArgContext context) + { + if (context.ELLIPSIS() is not null) + { + return new(SignatureArg.CreateSentinelArgument()); + } + return new(new SignatureArg( + VisitParamAttr(context.paramAttr()).Value, + VisitType(context.type()).Value, + VisitMarshalClause(context.marshalClause()).Value, + context.id() is CILParser.IdContext id ? VisitId(id).Value : null)); + } + + GrammarResult ICILVisitor.VisitSigArgs(CILParser.SigArgsContext context) => VisitSigArgs(context); + public GrammarResult.Sequence VisitSigArgs(CILParser.SigArgsContext context) => new(ImmutableArray.CreateRange(context.sigArg().Select(arg => VisitSigArg(arg).Value))); + GrammarResult ICILVisitor.VisitSimpleType(CILParser.SimpleTypeContext context) => VisitSimpleType(context); + public GrammarResult.Literal VisitSimpleType(CILParser.SimpleTypeContext context) + { + return new(context.GetChild(0).Symbol.Type switch + { + CILParser.CHAR => SignatureTypeCode.Char, + CILParser.STRING => SignatureTypeCode.String, + CILParser.BOOL => SignatureTypeCode.Boolean, + CILParser.INT8 => SignatureTypeCode.SByte, + CILParser.INT16 => SignatureTypeCode.Int16, + CILParser.INT32_ => SignatureTypeCode.Int32, + CILParser.INT64_ => SignatureTypeCode.Int64, + CILParser.UINT8 => SignatureTypeCode.Byte, + CILParser.UINT16 => SignatureTypeCode.UInt16, + CILParser.UINT32 => SignatureTypeCode.UInt32, + CILParser.UINT64 => SignatureTypeCode.UInt64, + _ => throw new UnreachableException() + }); + } + + GrammarResult ICILVisitor.VisitSlashedName(CILParser.SlashedNameContext context) + { + return VisitSlashedName(context); + } + + public static GrammarResult.Literal VisitSlashedName(CILParser.SlashedNameContext context) + { + TypeName? currentTypeName = null; + foreach (var item in context.dottedName()) + { + currentTypeName = new TypeName(currentTypeName, VisitDottedName(item).Value); + } + // We'll always have at least one dottedName, so the value here will be non-null + return new(currentTypeName!); + } + + GrammarResult ICILVisitor.VisitSqstringSeq(CILParser.SqstringSeqContext context) => VisitSqstringSeq(context); + + public static GrammarResult.FormattedBlob VisitSqstringSeq(CILParser.SqstringSeqContext context) + { + var strings = ImmutableArray.CreateBuilder(context.ChildCount); + foreach (var child in context.children) + { + string? str = null; + + if (child is ITerminalNode { Symbol: { Type: CILParser.SQSTRING, Text: string stringValue } }) + { + str = stringValue; + } + + strings.Add(str); + } + return new(strings.ToImmutable().SerializeSequence()); + } + + GrammarResult ICILVisitor.VisitStackreserve(CILParser.StackreserveContext context) => VisitStackreserve(context); + public GrammarResult.Literal VisitStackreserve(CILParser.StackreserveContext context) => VisitInt64(context.int64()); + + GrammarResult ICILVisitor.VisitSubsystem(CILParser.SubsystemContext context) => VisitSubsystem(context); + public GrammarResult.Literal VisitSubsystem(CILParser.SubsystemContext context) => VisitInt32(context.int32()); + + public GrammarResult VisitTerminal(ITerminalNode node) => throw new UnreachableException(); + public GrammarResult VisitTls(CILParser.TlsContext context) + { + // TODO-SRM: System.Reflection.Metadata doesn't provide APIs to point a data declaration at a TLS slot or into the IL stream. + // We have tests for the TLS case (CoreCLR only supports it on Win-x86), but not for the IL case. + return GrammarResult.SentinelValue.Result; + } + + GrammarResult ICILVisitor.VisitTruefalse(CILParser.TruefalseContext context) => VisitTruefalse(context); + + public static GrammarResult.Literal VisitTruefalse(CILParser.TruefalseContext context) + { + return new(bool.Parse(context.GetText())); + } + + GrammarResult ICILVisitor.VisitTryBlock(CILParser.TryBlockContext context) => VisitTryBlock(context); + + public GrammarResult.Literal<(LabelHandle Start, LabelHandle End)> VisitTryBlock(CILParser.TryBlockContext context) + { + if (context.scopeBlock() is CILParser.ScopeBlockContext scopeBlock) + { + LabelHandle start = _currentMethod!.Definition.MethodBody.DefineLabel(); + _currentMethod.Definition.MethodBody.MarkLabel(start); + _ = VisitScopeBlock(scopeBlock); + LabelHandle end = _currentMethod.Definition.MethodBody.DefineLabel(); + _currentMethod.Definition.MethodBody.MarkLabel(end); + return new((start, end)); + } + if (context.id() is CILParser.IdContext[] ids) + { + var start = _currentMethod!.Labels.TryGetValue(VisitId(ids[0]).Value, out LabelHandle startLabel) ? startLabel : _currentMethod.Labels[VisitId(ids[0]).Value] = _currentMethod.Definition.MethodBody.DefineLabel(); + var end = _currentMethod!.Labels.TryGetValue(VisitId(ids[1]).Value, out LabelHandle endLabel) ? endLabel : _currentMethod.Labels[VisitId(ids[1]).Value] = _currentMethod.Definition.MethodBody.DefineLabel(); + return new((start, end)); + } + if (context.int32() is CILParser.Int32Context[] offsets) + { + var start = _currentMethod!.Definition.MethodBody.DefineLabel(); + var end = _currentMethod.Definition.MethodBody.DefineLabel(); + _currentMethod.Definition.MethodBody.MarkLabel(start, VisitInt32(offsets[0]).Value); + _currentMethod.Definition.MethodBody.MarkLabel(end, VisitInt32(offsets[1]).Value); + return new((start, end)); + } + throw new UnreachableException(); + } + + GrammarResult ICILVisitor.VisitTyBound(CILParser.TyBoundContext context) => VisitTyBound(context); + public GrammarResult.Sequence VisitTyBound(CILParser.TyBoundContext context) + { + return new(VisitTypeList(context.typeList()).Value.Select(EntityRegistry.CreateGenericConstraint).ToImmutableArray()); + } + + GrammarResult ICILVisitor.VisitTypar(CILParser.TyparContext context) => VisitTypar(context); + + public GrammarResult.Literal VisitTypar(CILParser.TyparContext context) + { + GenericParameterAttributes attributes = VisitTyparAttribs(context.typarAttribs()).Value; + EntityRegistry.GenericParameterEntity genericParameter = EntityRegistry.CreateGenericParameter(attributes, VisitDottedName(context.dottedName()).Value); + + foreach (var constraint in VisitTyBound(context.tyBound()).Value) + { + genericParameter.Constraints.Add(constraint); + } + + return new(genericParameter); + } + + GrammarResult ICILVisitor.VisitTyparAttrib(CILParser.TyparAttribContext context) => VisitTyparAttrib(context); + public GrammarResult.Flag VisitTyparAttrib(CILParser.TyparAttribContext context) + { + return context switch + { + { covariant: not null } => new(GenericParameterAttributes.Covariant), + { contravariant: not null } => new(GenericParameterAttributes.Contravariant), + { @class: not null } => new(GenericParameterAttributes.ReferenceTypeConstraint), + { valuetype: not null } => new(GenericParameterAttributes.NotNullableValueTypeConstraint), + { byrefLike: not null } => new(GenericParameterAttributes.AllowByRefLike), + { ctor: not null } => new(GenericParameterAttributes.DefaultConstructorConstraint), + { flags: CILParser.Int32Context int32 } => new((GenericParameterAttributes)VisitInt32(int32).Value), + _ => throw new UnreachableException() + }; + } + GrammarResult ICILVisitor.VisitTyparAttribs(CILParser.TyparAttribsContext context) => VisitTyparAttribs(context); + + public GrammarResult.Literal VisitTyparAttribs(CILParser.TyparAttribsContext context) => + new(context.typarAttrib() + .Select(VisitTyparAttrib) + .Aggregate( + (GenericParameterAttributes)0, (agg, attr) => agg | attr)); + + GrammarResult ICILVisitor.VisitTypars(CILParser.TyparsContext context) => VisitTypars(context); + public GrammarResult.Sequence VisitTypars(CILParser.TyparsContext context) + { + CILParser.TyparContext[] typeParameters = context.typar(); + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(typeParameters.Length); + + foreach (var typeParameter in typeParameters) + { + builder.Add(VisitTypar(typeParameter).Value); + } + return new(builder.MoveToImmutable()); + } + + GrammarResult ICILVisitor.VisitTyparsClause(CILParser.TyparsClauseContext context) => VisitTyparsClause(context); + public GrammarResult.Sequence VisitTyparsClause(CILParser.TyparsClauseContext context) => context.typars() is null ? new(ImmutableArray.Empty) : VisitTypars(context.typars()); + + GrammarResult ICILVisitor.VisitType(CILParser.TypeContext context) => VisitType(context); + public GrammarResult.FormattedBlob VisitType(CILParser.TypeContext context) + { + // These blobs will likely be very small, so use a smaller default size. + const int DefaultSignatureElementBlobSize = 10; + BlobBuilder prefix = new(DefaultSignatureElementBlobSize); + BlobBuilder suffix = new(DefaultSignatureElementBlobSize); + BlobBuilder elementType = VisitElementType(context.elementType()).Value; + + // Prefix blob writes outer modifiers first. + // Suffix blob writes inner modifiers first. + // Since all blobs are prefix blobs and only some have suffix data, + // We will go in reverse order to write the prefixes + // and then go in forward order to write the suffixes. + CILParser.TypeModifiersContext[] typeModifiers = context.typeModifiers(); + for (int i = typeModifiers.Length - 1; i >= 0; i--) + { + CILParser.TypeModifiersContext? modifier = typeModifiers[i]; + switch (modifier) + { + case CILParser.SZArrayModifierContext: + prefix.WriteByte((byte)SignatureTypeCode.SZArray); + break; + case CILParser.ArrayModifierContext: + prefix.WriteByte((byte)SignatureTypeCode.Array); + break; + case CILParser.ByRefModifierContext: + prefix.WriteByte((byte)SignatureTypeCode.ByReference); + break; + case CILParser.PtrModifierContext: + prefix.WriteByte((byte)SignatureTypeCode.Pointer); + break; + case CILParser.PinnedModifierContext: + prefix.WriteByte((byte)SignatureTypeCode.Pinned); + break; + case CILParser.RequiredModifierContext modreq: + prefix.WriteByte((byte)SignatureTypeCode.RequiredModifier); + prefix.WriteTypeEntity(VisitTypeSpec(modreq.typeSpec()).Value); + break; + case CILParser.OptionalModifierContext modopt: + prefix.WriteByte((byte)SignatureTypeCode.OptionalModifier); + prefix.WriteTypeEntity(VisitTypeSpec(modopt.typeSpec()).Value); + break; + case CILParser.GenericArgumentsModifierContext: + prefix.WriteByte((byte)SignatureTypeCode.GenericTypeInstance); + break; + } + } + + foreach (var modifier in typeModifiers) + { + switch (modifier) + { + case CILParser.ArrayModifierContext arr: + var bounds = VisitBounds(arr.bounds()).Value; + suffix.WriteCompressedInteger(bounds.Length); + int lowerBoundsDefined = 0; + int upperBoundsDefined = 0; + foreach (var bound in bounds) + { + if (bound.Lower is not null) + { + lowerBoundsDefined++; + } + if (bound.Upper is not null) + { + upperBoundsDefined++; + } + } + suffix.WriteCompressedInteger(upperBoundsDefined); + foreach (var bound in bounds) + { + suffix.WriteCompressedInteger(bound.Upper.GetValueOrDefault()); + } + suffix.WriteCompressedInteger(lowerBoundsDefined); + foreach (var bound in bounds) + { + suffix.WriteCompressedSignedInteger(bound.Lower.GetValueOrDefault()); + } + break; + case CILParser.GenericArgumentsModifierContext genericArgs: + VisitTypeArgs(genericArgs.typeArgs()).Value.WriteContentTo(suffix); + break; + } + } + + elementType.LinkSuffix(suffix); + prefix.LinkSuffix(elementType); + return new(prefix); + } + + GrammarResult ICILVisitor.VisitTypeArgs(CILParser.TypeArgsContext context) => VisitTypeArgs(context); + + public GrammarResult.FormattedBlob VisitTypeArgs(CILParser.TypeArgsContext context) + { + BlobBuilder blob = new(4); + var types = context.type(); + blob.WriteCompressedInteger(types.Length); + foreach (var type in types) + { + blob.LinkSuffix(VisitType(type).Value); + } + return new(blob); + } + + public GrammarResult VisitTypedefDecl(CILParser.TypedefDeclContext context) => throw new NotImplementedException(); + public GrammarResult VisitTypelist(CILParser.TypelistContext context) + { + foreach (var name in context.className()) + { + // We don't do anything with the class names here. + // We just go through the name resolution process to ensure that the names are valid + // and to provide TypeReference table rows. + _ = VisitClassName(name); + } + return GrammarResult.SentinelValue.Result; + } + + GrammarResult ICILVisitor.VisitTypeList(CILParser.TypeListContext context) => VisitTypeList(context); + public GrammarResult.Sequence VisitTypeList(CILParser.TypeListContext context) + { + CILParser.TypeSpecContext[] bounds = context.typeSpec(); + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(bounds.Length); + foreach (var typeSpec in bounds) + { + builder.Add(VisitTypeSpec(typeSpec).Value); + } + return new(builder.MoveToImmutable()); + } + + GrammarResult ICILVisitor.VisitTypeSpec(CILParser.TypeSpecContext context) => VisitTypeSpec(context); + public GrammarResult.Literal VisitTypeSpec(CILParser.TypeSpecContext context) + { + if (context.className() is CILParser.ClassNameContext className) + { + return new(VisitClassName(className).Value); + } + else if (context.dottedName() is CILParser.DottedNameContext dottedName) + { + string nameToResolve = VisitDottedName(dottedName).Value; + if (context.MODULE() is not null) + { + EntityRegistry.ModuleReferenceEntity? module = _entityRegistry.FindModuleReference(nameToResolve); + if (module is null) + { + // report error + return new(new EntityRegistry.FakeTypeEntity(MetadataTokens.ModuleReferenceHandle(0))); + } + return new(new EntityRegistry.FakeTypeEntity(module.Handle)); + } + else + { + return new(new EntityRegistry.FakeTypeEntity( + _entityRegistry.GetOrCreateAssemblyReference(nameToResolve, newRef => + { + // Report warning on implicit assembly reference creation. + }).Handle)); + } + } + else + { + Debug.Assert(context.type() != null); + return new(_entityRegistry.GetOrCreateTypeSpec(VisitType(context.type()).Value)); + } + } + + + GrammarResult ICILVisitor.VisitVariantType(CILParser.VariantTypeContext context) => VisitVariantType(context); + public GrammarResult.Literal VisitVariantType(CILParser.VariantTypeContext context) + { + VarEnum variant = VisitVariantTypeElement(context.variantTypeElement()).Value; + // The 0th child is the variant element type. + for (int i = 1; i < context.ChildCount; i++) + { + ITerminalNode childToken = (ITerminalNode)context.children[i]; + if (childToken.Symbol.Type == CILParser.ARRAY_TYPE_NO_BOUNDS) + { + variant |= VarEnum.VT_ARRAY; + } + else if (childToken.Symbol.Type == CILParser.VECTOR) + { + variant |= VarEnum.VT_VECTOR; + } + else + { + Debug.Assert(childToken.Symbol.Type == CILParser.REF); + variant |= VarEnum.VT_BYREF; + } + } + return new(variant); + } + + GrammarResult ICILVisitor.VisitVariantTypeElement(CILParser.VariantTypeElementContext context) => VisitVariantTypeElement(context); + public GrammarResult.Literal VisitVariantTypeElement(CILParser.VariantTypeElementContext context) + { + return new(context.GetChild(0).Symbol.Type switch + { + CILParser.VARIANT => VarEnum.VT_VARIANT, + CILParser.CURRENCY => VarEnum.VT_CY, + CILParser.VOID => VarEnum.VT_VOID, + CILParser.BOOL => VarEnum.VT_BOOL, + CILParser.INT8 => VarEnum.VT_I1, + CILParser.INT16 => VarEnum.VT_I2, + CILParser.INT32_ => VarEnum.VT_I4, + CILParser.INT64_ => VarEnum.VT_I8, + CILParser.FLOAT32 => VarEnum.VT_R4, + CILParser.FLOAT64_ => VarEnum.VT_R8, + CILParser.UINT8 => VarEnum.VT_UI1, + CILParser.UINT16 => VarEnum.VT_UI2, + CILParser.UINT32 => VarEnum.VT_UI4, + CILParser.UINT64 => VarEnum.VT_UI8, + CILParser.PTR => VarEnum.VT_PTR, + CILParser.DECIMAL => VarEnum.VT_DECIMAL, + CILParser.DATE => VarEnum.VT_DATE, + CILParser.BSTR => VarEnum.VT_BSTR, + CILParser.LPSTR => VarEnum.VT_LPSTR, + CILParser.LPWSTR => VarEnum.VT_LPWSTR, + CILParser.IUNKNOWN => VarEnum.VT_UNKNOWN, + CILParser.IDISPATCH => VarEnum.VT_DISPATCH, + CILParser.SAFEARRAY => VarEnum.VT_SAFEARRAY, + CILParser.INT => VarEnum.VT_INT, + CILParser.UINT => VarEnum.VT_UINT, + CILParser.ERROR => VarEnum.VT_ERROR, + CILParser.HRESULT => VarEnum.VT_HRESULT, + CILParser.CARRAY => VarEnum.VT_CARRAY, + CILParser.USERDEFINED => VarEnum.VT_USERDEFINED, + CILParser.RECORD => VarEnum.VT_RECORD, + CILParser.FILETIME => VarEnum.VT_FILETIME, + CILParser.BLOB => VarEnum.VT_BLOB, + CILParser.STREAM => VarEnum.VT_STREAM, + CILParser.STORAGE => VarEnum.VT_STORAGE, + CILParser.STREAMED_OBJECT => VarEnum.VT_STREAMED_OBJECT, + CILParser.STORED_OBJECT => VarEnum.VT_STORED_OBJECT, + CILParser.BLOB_OBJECT => VarEnum.VT_BLOB_OBJECT, + CILParser.CF => VarEnum.VT_CF, + CILParser.CLSID => VarEnum.VT_CLSID, + _ => throw new UnreachableException() + }); + } + + public GrammarResult VisitVtableDecl(CILParser.VtableDeclContext context) + { + // TODO: Need custom ManagedPEBuilder subclass to write the exports directory. + throw new NotImplementedException("raw vtable fixups blob not supported"); + } + + public GrammarResult VisitVtfixupAttr(CILParser.VtfixupAttrContext context) + { + // TODO: Need custom ManagedPEBuilder subclass to write the exports directory. + throw new NotImplementedException("vtable fixups not supported"); + } + public GrammarResult VisitVtfixupDecl(CILParser.VtfixupDeclContext context) + { + // TODO: Need custom ManagedPEBuilder subclass to write the exports directory. + throw new NotImplementedException("raw vtable fixups blob not supported"); + } + + GrammarResult ICILVisitor.VisitOptionalModifier(CILParser.OptionalModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitSZArrayModifier(CILParser.SZArrayModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitRequiredModifier(CILParser.RequiredModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitPtrModifier(CILParser.PtrModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitPinnedModifier(CILParser.PinnedModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitGenericArgumentsModifier(CILParser.GenericArgumentsModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitByRefModifier(CILParser.ByRefModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitArrayModifier(CILParser.ArrayModifierContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + GrammarResult ICILVisitor.VisitTypeModifiers(CILParser.TypeModifiersContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + public GrammarResult VisitNativeTypeArrayPointerInfo(CILParser.NativeTypeArrayPointerInfoContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + public GrammarResult VisitPointerArrayTypeSize(CILParser.PointerArrayTypeSizeContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + public GrammarResult VisitPointerArrayTypeParamIndex(CILParser.PointerArrayTypeParamIndexContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + public GrammarResult VisitPointerNativeType(CILParser.PointerNativeTypeContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + public GrammarResult VisitPointerArrayTypeSizeParamIndex(CILParser.PointerArrayTypeSizeParamIndexContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + public GrammarResult VisitPointerArrayTypeNoSizeData(CILParser.PointerArrayTypeNoSizeDataContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited); + } +} diff --git a/src/tools/ilasm/src/ILAssembler/ILAssembler.csproj b/src/tools/ilasm/src/ILAssembler/ILAssembler.csproj new file mode 100644 index 00000000000000..9ccf09f00d0be4 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/ILAssembler.csproj @@ -0,0 +1,20 @@ + + + + $(NetCoreAppToolCurrent) + true + enable + $(NoWarn);CS3021 + true + true + false + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/tools/ilasm/src/ILAssembler/ILCompilation.cs b/src/tools/ilasm/src/ILAssembler/ILCompilation.cs new file mode 100644 index 00000000000000..70597169cae7f8 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/ILCompilation.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Immutable; + +namespace ILAssembler; + +public class ILCompilation +{ + public ILCompilation(SourceText source) + { + } + + public ImmutableArray Diagnostics { get; } + + public ImmutableArray Emit() + { + return ImmutableArray.Empty; + } +} diff --git a/src/tools/ilasm/src/ILAssembler/InstructionEncoderExtensions.cs b/src/tools/ilasm/src/ILAssembler/InstructionEncoderExtensions.cs new file mode 100644 index 00000000000000..015559b1ccfe24 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/InstructionEncoderExtensions.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Reflection.Metadata.Ecma335; + +namespace ILAssembler; + +internal static class InstructionEncoderExtensions +{ + public static void MarkLabel(this InstructionEncoder encoder, LabelHandle label, int ilOffset) + { + // TODO-SRM: Propose a public API for this so we don't need to use UnsafeAccessor into the BCL. + MarkLabel(encoder.ControlFlowBuilder!, ilOffset, label); + } + + [UnsafeAccessor(UnsafeAccessorKind.Method)] + private static extern void MarkLabel(ControlFlowBuilder builder, int ilOffset, LabelHandle label); +} diff --git a/src/tools/ilasm/src/ILAssembler/Instructions.g4 b/src/tools/ilasm/src/ILAssembler/Instructions.g4 new file mode 100644 index 00000000000000..0919ca5c91955d --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/Instructions.g4 @@ -0,0 +1,20 @@ +/* +Licensed to the .NET Foundation under one or more agreements. +The .NET Foundation licenses this file to you under the MIT license. +*/ + +lexer grammar Instructions; + +INSTR_NONE: ('nop'|'break'|'ldarg.0'|'ldarg.1'|'ldarg.2'|'ldarg.3'|'ldloc.0'|'ldloc.1'|'ldloc.2'|'ldloc.3'|'stloc.0'|'stloc.1'|'stloc.2'|'stloc.3'|'ldnull'|'ldc.i4.m1'|'ldc.i4.0'|'ldc.i4.1'|'ldc.i4.2'|'ldc.i4.3'|'ldc.i4.4'|'ldc.i4.5'|'ldc.i4.6'|'ldc.i4.7'|'ldc.i4.8'|'dup'|'pop'|'ret'|'ldind.i1'|'ldind.u1'|'ldind.i2'|'ldind.u2'|'ldind.i4'|'ldind.u4'|'ldind.i8'|'ldind.i'|'ldind.r4'|'ldind.r8'|'ldind.ref'|'stind.ref'|'stind.i1'|'stind.i2'|'stind.i4'|'stind.i8'|'stind.r4'|'stind.r8'|'add'|'sub'|'mul'|'div'|'div.un'|'rem'|'rem.un'|'and'|'or'|'xor'|'shl'|'shr'|'shr.un'|'neg'|'not'|'conv.i1'|'conv.i2'|'conv.i4'|'conv.i8'|'conv.r4'|'conv.r8'|'conv.u4'|'conv.u8'|'conv.r.un'|'throw'|'conv.ovf.i1.un'|'conv.ovf.i2.un'|'conv.ovf.i4.un'|'conv.ovf.i8.un'|'conv.ovf.u1.un'|'conv.ovf.u2.un'|'conv.ovf.u4.un'|'conv.ovf.u8.un'|'conv.ovf.i.un'|'conv.ovf.u.un'|'ldlen'|'ldelem.i1'|'ldelem.u1'|'ldelem.i2'|'ldelem.u2'|'ldelem.i4'|'ldelem.u4'|'ldelem.i8'|'ldelem.i'|'ldelem.r4'|'ldelem.r8'|'ldelem.ref'|'stelem.i'|'stelem.i1'|'stelem.i2'|'stelem.i4'|'stelem.i8'|'stelem.r4'|'stelem.r8'|'stelem.ref'|'conv.ovf.i1'|'conv.ovf.u1'|'conv.ovf.i2'|'conv.ovf.u2'|'conv.ovf.i4'|'conv.ovf.u4'|'conv.ovf.i8'|'conv.ovf.u8'|'ckfinite'|'conv.u2'|'conv.u1'|'conv.i'|'conv.ovf.i'|'conv.ovf.u'|'add.ovf'|'add.ovf.un'|'mul.ovf'|'mul.ovf.un'|'sub.ovf'|'sub.ovf.un'|'endfinally'|'stind.i'|'conv.u'|'prefix7'|'prefix6'|'prefix5'|'prefix4'|'prefix3'|'prefix2'|'prefix1'|'prefixref'|'arglist'|'ceq'|'cgt'|'cgt.un'|'clt'|'clt.un'|'localloc'|'endfilter'|'volatile.'|'tail.'|'cpblk'|'initblk'|'rethrow'|'refanytype'|'readonly.'|'illegal'|'endmac'); +INSTR_VAR: ('ldarg.s'|'ldarga.s'|'starg.s'|'ldloc.s'|'ldloca.s'|'stloc.s'|'ldarg'|'ldarga'|'starg'|'ldloc'|'ldloca'|'stloc'); +INSTR_I: ('ldc.i4.s'|'ldc.i4'|'unaligned.'|'no.'); +INSTR_I8: ('ldc.i8'); +INSTR_R: ('ldc.r4'|'ldc.r8'); +INSTR_METHOD: ('jmp'|'call'|'callvirt'|'newobj'|'ldftn'|'ldvirtftn'); +INSTR_SIG: ('calli'); +INSTR_BRTARGET: ('br.s'|'brfalse.s'|'brtrue.s'|'beq.s'|'bge.s'|'bgt.s'|'ble.s'|'blt.s'|'bne.un.s'|'bge.un.s'|'bgt.un.s'|'ble.un.s'|'blt.un.s'|'br'|'brfalse'|'brtrue'|'beq'|'bge'|'bgt'|'ble'|'blt'|'bne.un'|'bge.un'|'bgt.un'|'ble.un'|'blt.un'|'leave'|'leave.s'); +INSTR_SWITCH: ('switch'); +INSTR_TYPE: ('cpobj'|'ldobj'|'castclass'|'isinst'|'unbox'|'stobj'|'box'|'newarr'|'ldelema'|'ldelem'|'stelem'|'unbox.any'|'refanyval'|'mkrefany'|'initobj'|'constrained.'|'sizeof'); +INSTR_STRING: ('ldstr'); +INSTR_FIELD: ('ldfld'|'ldflda'|'stfld'|'ldsfld'|'ldsflda'|'stsfld'); +INSTR_TOK: ('ldtoken'); diff --git a/src/tools/ilasm/src/ILAssembler/Location.cs b/src/tools/ilasm/src/ILAssembler/Location.cs new file mode 100644 index 00000000000000..b3f829f06f396c --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/Location.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +using System.Collections.Generic; + +namespace ILAssembler; + +public record Location(SourceSpan Span, SourceText Source) +{ + internal static Location From(Antlr4.Runtime.IToken token, IReadOnlyDictionary sourceDocuments) + { + SourceSpan span = new(token.StartIndex, token.StopIndex - token.StartIndex + 1); + return new Location(span, sourceDocuments[token.TokenSource.InputStream.SourceName]); + } +} diff --git a/src/tools/ilasm/src/ILAssembler/MetadataExtensions.cs b/src/tools/ilasm/src/ILAssembler/MetadataExtensions.cs new file mode 100644 index 00000000000000..7f030940cdabd4 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/MetadataExtensions.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; + +namespace ILAssembler; + +internal static class MetadataExtensions +{ + extension(TypeAttributes) + { + public static TypeAttributes ExtendedLayout => (TypeAttributes)0x18; + public static TypeAttributes Forwarder => (TypeAttributes)0x00200000; + } + + extension(DeclarativeSecurityAction) + { + public static DeclarativeSecurityAction Request => (DeclarativeSecurityAction)1; + public static DeclarativeSecurityAction PrejitGrant => (DeclarativeSecurityAction)0xB; + public static DeclarativeSecurityAction PrejitDeny => (DeclarativeSecurityAction)0xC; + public static DeclarativeSecurityAction NonCasDemand => (DeclarativeSecurityAction)0xD; + public static DeclarativeSecurityAction NonCasLinkDemand => (DeclarativeSecurityAction)0xE; + public static DeclarativeSecurityAction NonCasInheritanceDemand => (DeclarativeSecurityAction)0xF; + } + + extension(AssemblyFlags) + { + public static AssemblyFlags NoPlatform => (AssemblyFlags)0x70; + public static AssemblyFlags ArchitectureMask => (AssemblyFlags)0xF0; + } +} diff --git a/src/tools/ilasm/src/ILAssembler/NameHelpers.cs b/src/tools/ilasm/src/ILAssembler/NameHelpers.cs new file mode 100644 index 00000000000000..00bf24aef494e9 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/NameHelpers.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices.ComTypes; +using System.Text; + +namespace ILAssembler +{ + internal static class NameHelpers + { + public static (string Namespace, string Name) SplitDottedNameToNamespaceAndName(string dottedName) + { + int lastDotIndex = dottedName.LastIndexOf('.'); + + if (lastDotIndex > 0 && dottedName[lastDotIndex - 1] == '.') + { + // Handle cases like "a.b..ctor". + lastDotIndex -= 1; + } + + return ( + lastDotIndex != -1 + ? dottedName.Substring(0, lastDotIndex) + : string.Empty, + lastDotIndex != -1 + ? dottedName.Substring(lastDotIndex + 1) + : dottedName); + } + } +} diff --git a/src/tools/ilasm/src/ILAssembler/NamedElementList.cs b/src/tools/ilasm/src/ILAssembler/NamedElementList.cs new file mode 100644 index 00000000000000..076fe1f1be30b8 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/NamedElementList.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Collections.Generic; + +namespace ILAssembler +{ + internal interface INamed + { + string Name { get; } + } + + internal sealed class NamedElementList : IList + where T : INamed + { + private readonly List _elements = new(); + private readonly Dictionary _elementsByName = new(); + + public T this[int index] + { + get => _elements[index]; + set + { + T oldElement = _elements[index]; + _elements[index] = value; + _elementsByName.Remove(oldElement.Name); + _elementsByName[value.Name] = value; + } + } + + public T this[string name] + { + get => _elementsByName[name]; + } + + public int Count => _elements.Count; + + public bool IsReadOnly => ((ICollection)_elements).IsReadOnly; + + public void Add(T item) + { + _elements.Add(item); + _elementsByName.Add(item.Name, item); + } + + public void Clear() + { + _elements.Clear(); + _elementsByName.Clear(); + } + + public bool Contains(T item) => _elements.Contains(item); + + public bool Contains(string name) => _elementsByName.ContainsKey(name); + + public void CopyTo(T[] array, int arrayIndex) => _elements.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => _elements.GetEnumerator(); + public int IndexOf(T item) => _elements.IndexOf(item); + public void Insert(int index, T item) + { + _elements.Insert(index, item); + _elementsByName.Add(item.Name, item); + } + + public bool Remove(T item) + { + bool result = _elements.Remove(item); + if (result) + { + _elementsByName.Remove(item.Name); + } + return result; + } + + public void RemoveAt(int index) + { + T element = _elements[index]; + _elements.RemoveAt(index); + _elementsByName.Remove(element.Name); + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_elements).GetEnumerator(); + } +} diff --git a/src/tools/ilasm/src/ILAssembler/Options.cs b/src/tools/ilasm/src/ILAssembler/Options.cs new file mode 100644 index 00000000000000..ab13e4b46a09f8 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/Options.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ILAssembler +{ + public sealed class Options + { + public bool NoAutoInherit { get; set; } + } +} diff --git a/src/tools/ilasm/src/ILAssembler/PreprocessedTokenSource.cs b/src/tools/ilasm/src/ILAssembler/PreprocessedTokenSource.cs new file mode 100644 index 00000000000000..4d1201a5bb039a --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/PreprocessedTokenSource.cs @@ -0,0 +1,265 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Antlr4.Runtime; +using Antlr4.Runtime.Misc; + +namespace ILAssembler +{ + public sealed class PreprocessedTokenSource : ITokenSource + { + private readonly Stack<(ITokenSource Source, int ActiveIfDefBlocks)> _includeSourceStack = new(); + private readonly Func _loadIncludedDocument; + + private readonly Dictionary _definedVars = new(); + private readonly Stack<(string Var, bool Defined, bool IsElse)> _activeIfDefBlocks = new(); + + public PreprocessedTokenSource(ITokenSource underlyingSource, Func loadIncludedDocument) + { + _includeSourceStack.Push((underlyingSource, 0)); + _loadIncludedDocument = loadIncludedDocument; + } + + private ITokenSource CurrentTokenSource => _includeSourceStack.Peek().Source; + private int ActiveIfDefBlocksInCurrentSource => _includeSourceStack.Peek().ActiveIfDefBlocks; + + public int Line => CurrentTokenSource.Line; + + public int Column => CurrentTokenSource.Column; + + public ICharStream InputStream => CurrentTokenSource.InputStream; + + // TODO: Update SourceName to output a string describing the whole "include" stack. + public string SourceName => CurrentTokenSource.SourceName; + + public ITokenFactory TokenFactory { get => CurrentTokenSource.TokenFactory; set => CurrentTokenSource.TokenFactory = value; } + + private IToken NextTokenWithoutNestedEof(bool errorOnEof = false) + { + IToken nextToken = CurrentTokenSource.NextToken(); + + if (nextToken.Type == CILLexer.Eof) + { + // Skip the nested file EOF token. + // Native ILASM only failed to parse across include file boundaries for the following cases: + // - A comment tries to cross the file boundary. + // - The included file does not have at least one fully parsable rule. + // - A preprocessor directive cannot be parsed across file boundaries. + // As the second case is quite difficult to replicate and is due to YACC limitations, we'll only maintain the rest of the rules. + if (errorOnEof) + { + ReportPreprocessorSyntaxError(nextToken); + } + _includeSourceStack.Pop(); + if (_includeSourceStack.Count == 0) + { + // If we hit EOF of our entry file, return the EOF token. + return nextToken; + } + nextToken = CurrentTokenSource.NextToken(); + } + return nextToken; + } + + public IToken NextToken() + { + IToken nextToken = NextTokenWithoutNestedEof(errorOnEof: ActiveIfDefBlocksInCurrentSource != 0); + + if (nextToken.Type == CILLexer.PP_INCLUDE) + { + var pathToken = NextTokenWithoutNestedEof(errorOnEof: true); + if (pathToken.Type != CILLexer.QSTRING) + { + ReportPreprocessorSyntaxError(nextToken); + return pathToken; + } + var path = pathToken.Text; + // TODO: Parse out escape sequences and quotes in path. + _includeSourceStack.Push((_loadIncludedDocument(path.Substring(1, path.Length - 2)), 0)); + return NextToken(); + } + else if (nextToken.Type == CILLexer.PP_DEFINE) + { + IToken identifier = NextTokenWithoutNestedEof(errorOnEof: ActiveIfDefBlocksInCurrentSource != 0); + if (identifier.Type != CILLexer.ID) + { + ReportPreprocessorSyntaxError(identifier); + return identifier; + } + IToken valueMaybe = NextTokenWithoutNestedEof(errorOnEof: ActiveIfDefBlocksInCurrentSource != 0); + if (valueMaybe.Type == CILLexer.QSTRING) + { + // TODO: Parse out escape sequences and quotes in path. + _definedVars.Add(identifier.Text, valueMaybe.Text.Substring(1, valueMaybe.Text.Length - 2)); + return NextToken(); + } + else + { + _definedVars.Add(identifier.Text, null); + } + nextToken = valueMaybe; + } + + if (nextToken.Type == CILLexer.PP_IFDEF) + { + return ProcessIfDef(requireDefined: true); + } + else if (nextToken.Type == CILLexer.PP_IFNDEF) + { + return ProcessIfDef(requireDefined: false); + } + else if (nextToken.Type == CILLexer.PP_ELSE) + { + if (ActiveIfDefBlocksInCurrentSource == 0) + { + ReportPreprocessorSyntaxError(nextToken); + return NextTokenWithoutNestedEof(false); + } + var (identifier, expectedDefined, isElse) = _activeIfDefBlocks.Pop(); + if (isElse) + { + // Skip this #else token and set everything up such that we're still in the previous state. + _activeIfDefBlocks.Push((identifier, expectedDefined, isElse)); + ReportPreprocessorSyntaxError(nextToken); + return NextTokenWithoutNestedEof(ActiveIfDefBlocksInCurrentSource != 0); + } + return ConsumeDisabledPreprocessorBlock(identifier, expectedDefined: !expectedDefined, elseCase: true); + } + else if (nextToken.Type == CILLexer.PP_ENDIF) + { + if (ActiveIfDefBlocksInCurrentSource == 0) + { + ReportPreprocessorSyntaxError(nextToken); + return NextTokenWithoutNestedEof(false); + } + var (source, activeIfDef) = _includeSourceStack.Pop(); + _includeSourceStack.Push((source, --activeIfDef)); + return NextTokenWithoutNestedEof(activeIfDef != 0); + } + else if (nextToken.Type == CILLexer.ID && _definedVars.TryGetValue(nextToken.Text, out string? newValue) && newValue is not null) + { + // If token is an ID, we need to check for defined macro values and substitute. + IWritableToken writableToken = (IWritableToken)nextToken; + writableToken.Type = newValue.Contains('.') ? CILLexer.DOTTEDNAME : CILLexer.ID; + writableToken.Text = newValue; + } + return nextToken; + } + + private IToken ProcessIfDef(bool requireDefined) + { + IToken identifier = NextTokenWithoutNestedEof(errorOnEof: true); + if (identifier.Type != CILLexer.ID) + { + ReportPreprocessorSyntaxError(identifier); + return identifier; + } + if (_definedVars.ContainsKey(identifier.Text) != requireDefined) + { + return ConsumeDisabledPreprocessorBlock(identifier.Text, expectedDefined: requireDefined, elseCase: false); + } + else + { + _activeIfDefBlocks.Push((identifier.Text, Defined: requireDefined, IsElse: false)); + var (source, activeIfDef) = _includeSourceStack.Pop(); + _includeSourceStack.Push((source, ++activeIfDef)); + return NextToken(); + } + } + private IToken ConsumeDisabledPreprocessorBlock(string var, bool expectedDefined, bool elseCase) + { + int numNestedPreprocessorBlocks = 0; + for (IToken nextToken = NextTokenWithoutNestedEof(errorOnEof: true); nextToken.Type != CILLexer.PP_ENDIF || numNestedPreprocessorBlocks != 0; nextToken = NextTokenWithoutNestedEof(errorOnEof: true)) + { + if (nextToken.Type == CILLexer.Eof) + { + return nextToken; + } + + // If we've seen any #ifdef or #ifndef tokens, + // then we'll only check syntax of preprocessor tokens and ignore all other tokens until we've seen a matching #endif + if (numNestedPreprocessorBlocks > 0) + { + if (nextToken.Type == CILLexer.PP_IFDEF || nextToken.Type == CILLexer.PP_ENDIF) + { + IToken identifier = NextTokenWithoutNestedEof(errorOnEof: true); + if (identifier.Type != CILLexer.ID) + { + ReportPreprocessorSyntaxError(identifier); + } + numNestedPreprocessorBlocks++; + } + if (nextToken.Type == CILLexer.PP_ENDIF) + { + numNestedPreprocessorBlocks--; + } + continue; + } + + if (nextToken.Type == CILLexer.PP_ELSE) + { + if (elseCase) + { + ReportPreprocessorSyntaxError(nextToken); + return NextTokenWithoutNestedEof(errorOnEof: true); + } + + _activeIfDefBlocks.Push((var, Defined: !expectedDefined, IsElse: true)); + var (source, activeIfDef) = _includeSourceStack.Pop(); + _includeSourceStack.Push((source, ++activeIfDef)); + + return NextTokenWithoutNestedEof(errorOnEof: true); + } + if (nextToken.Type == CILLexer.PP_IFDEF || nextToken.Type == CILLexer.PP_ENDIF) + { + numNestedPreprocessorBlocks++; + } + } + + // If we're skipping an inactive else case, then we're still tracking the active ifdef block. + if (elseCase) + { + var (source, activeIfDef) = _includeSourceStack.Pop(); + _includeSourceStack.Push((source, --activeIfDef)); + } + return NextTokenWithoutNestedEof(ActiveIfDefBlocksInCurrentSource != 0); + } + + public event Action? OnPreprocessorSyntaxError; + + private void ReportPreprocessorSyntaxError(IToken token) + { + string text = token.TokenSource.InputStream.GetText(Interval.Of(token.StartIndex, token.TokenSource.InputStream.Index)); + string msg = "preprocessor syntax error at: '" + GetErrorDisplay(text) + "'"; + OnPreprocessorSyntaxError?.Invoke(token.TokenSource.SourceName, token.StartIndex, token.StopIndex - token.StartIndex, msg); + } + + private static string GetErrorDisplay(string s) + { + StringBuilder stringBuilder = new StringBuilder(); + var array = s.AsSpan(); + foreach (char c in array) + { + stringBuilder.Append(GetErrorDisplay(c)); + } + + return stringBuilder.ToString(); + } + + private static string GetErrorDisplay(int c) => c switch + { + -1 => "", + 10 => "\\n", + 9 => "\\t", + 13 => "\\r", + _ => ((char)c).ToString() + }; + } +} diff --git a/src/tools/ilasm/src/ILAssembler/SignatureArg.cs b/src/tools/ilasm/src/ILAssembler/SignatureArg.cs new file mode 100644 index 00000000000000..f3a20f59a0a3a3 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/SignatureArg.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; +using System.Reflection.Metadata; + +namespace ILAssembler +{ + internal sealed record SignatureArg(ParameterAttributes Attributes, BlobBuilder SignatureBlob, BlobBuilder MarshallingDescriptor, string? Name) + { + public bool IsSentinel { get; private init; } + + public static SignatureArg CreateSentinelArgument() + { + var sentinelBlob = new BlobBuilder(1); + sentinelBlob.WriteByte((byte)SignatureTypeCode.Sentinel); + return new SignatureArg(ParameterAttributes.None, sentinelBlob, new BlobBuilder(0), null) + { + IsSentinel = true + }; + } + } +} diff --git a/src/tools/ilasm/src/ILAssembler/SourceSpan.cs b/src/tools/ilasm/src/ILAssembler/SourceSpan.cs new file mode 100644 index 00000000000000..eb056cdd5bd20b --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/SourceSpan.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace ILAssembler; + +public record SourceSpan(int Start, int Length); diff --git a/src/tools/ilasm/src/ILAssembler/SourceText.cs b/src/tools/ilasm/src/ILAssembler/SourceText.cs new file mode 100644 index 00000000000000..e5e6ad328e3752 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/SourceText.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace ILAssembler; + +public record SourceText(string Text, string Path); diff --git a/src/tools/ilasm/src/ILAssembler/StackExtensions.cs b/src/tools/ilasm/src/ILAssembler/StackExtensions.cs new file mode 100644 index 00000000000000..6a8ff08d1994a5 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/StackExtensions.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ILAssembler +{ + internal static class StackExtensions + { + public static T? PeekOrDefault(this Stack stack) => stack.Count == 0 ? default : stack.Peek(); + } +} diff --git a/src/tools/ilasm/src/ILAssembler/TypeName.cs b/src/tools/ilasm/src/ILAssembler/TypeName.cs new file mode 100644 index 00000000000000..a4b1a11ddae119 --- /dev/null +++ b/src/tools/ilasm/src/ILAssembler/TypeName.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace ILAssembler +{ + internal sealed record TypeName(TypeName? ContainingTypeName, string DottedName); +} diff --git a/src/tools/ilasm/tests/ILAssembler.Tests/DocumentCompilerTests.cs b/src/tools/ilasm/tests/ILAssembler.Tests/DocumentCompilerTests.cs new file mode 100644 index 00000000000000..8683452f875457 --- /dev/null +++ b/src/tools/ilasm/tests/ILAssembler.Tests/DocumentCompilerTests.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace ILAssembler.Tests +{ + public class DocumentCompilerTests + { + [Fact] + public void SingleTypeNoMembers() + { + string source = """ + .class public auto ansi sealed beforefieldinit Test + { + } + """; + + using var pe = CompileAndGetReader(source, new Options()); + var reader = pe.GetMetadataReader(); + + // One for the type, one for Test. + Assert.Equal(2, reader.TypeDefinitions.Count); + var typeDef = reader.GetTypeDefinition(MetadataTokens.TypeDefinitionHandle(2)); + Assert.Equal(string.Empty, reader.GetString(typeDef.Namespace)); + Assert.Equal("Test", reader.GetString(typeDef.Name)); + } + + private static PEReader CompileAndGetReader(string source, Options options) + { + var sourceText = new SourceText(source, "test.il"); + var documentCompiler = new DocumentCompiler(); + var (diagnostics, result) = documentCompiler.Compile(sourceText, _ => + { + Assert.Fail("Expected no includes"); + return default; + }, _ => { Assert.Fail("Expected no resources"); return default; }, options); + Assert.Empty(diagnostics); + Assert.NotNull(result); + var blobBuilder = new BlobBuilder(); + result!.Serialize(blobBuilder); + return new PEReader(blobBuilder.ToImmutableArray()); + } + } +} diff --git a/src/tools/ilasm/tests/ILAssembler.Tests/ILAssembler.Tests.csproj b/src/tools/ilasm/tests/ILAssembler.Tests/ILAssembler.Tests.csproj new file mode 100644 index 00000000000000..53bf0b7dd22a92 --- /dev/null +++ b/src/tools/ilasm/tests/ILAssembler.Tests/ILAssembler.Tests.csproj @@ -0,0 +1,14 @@ + + + + $(NetCoreAppToolCurrent) + enable + enable + + false + + + + + + diff --git a/src/tools/ilasm/tests/ILAssembler.Tests/PreprocessedTokenSourceTests.cs b/src/tools/ilasm/tests/ILAssembler.Tests/PreprocessedTokenSourceTests.cs new file mode 100644 index 00000000000000..c51b3ed4c61eec --- /dev/null +++ b/src/tools/ilasm/tests/ILAssembler.Tests/PreprocessedTokenSourceTests.cs @@ -0,0 +1,374 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Antlr4.Runtime; +using Xunit; + +namespace ILAssembler.Tests +{ + public class PreprocessedTokenSourceTests + { + [Fact] + public void Define_Token_ExcludedFromStream() + { + string source = """ + #define X + A + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("A", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void IfDef_False_Tokens_RemovedFromStream() + { + string source = """ + #ifdef X + A + #endif + B + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void IfDef_True_Tokens_LeftInStream() + { + string source = """ + #define X + #ifdef X + A + #endif + B + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("A", token.Text); + }, + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void IfNDef_False_Tokens_RemovedFromStream() + { + string source = """ + #define X + #ifndef X + A + #endif + B + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void IfNDef_True_Tokens_LeftInStream() + { + string source = """ + #ifndef X + A + #endif + B + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("A", token.Text); + }, + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + + [Fact] + public void IfDef_False_Else_Tokens_RemovedFromStream() + { + string source = """ + #ifdef X + A + #else + B + #endif + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void IfDef_Else_True_Tokens_LeftInStream() + { + string source = """ + #define X + #ifdef X + A + #else + C + #endif + B + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("A", token.Text); + }, + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void IfNDef_Else_False_Tokens_RemovedFromStream() + { + string source = """ + #define X + #ifndef X + A + #else + B + #endif + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void IfNDef_Else_True_Tokens_LeftInStream() + { + string source = """ + #ifndef X + A + #else + C + #endif + B + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("A", token.Text); + }, + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void IfDef_False_Empty_Body_Leaves_Else_Block_TokensInStream() + { + string source = """ + #ifdef X + #else + B + #endif + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void IfNDef_True_Empty_Body_Removes_Else_Block_Tokens() + { + string source = """ + #ifndef X + #else + A + #endif + B + """; + + ITokenSource lexer = CreateLexerForSource(source); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, NoIncludeDirectivesCallback); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + [Fact] + public void Include_Includes_Tokens_With_Original_Source() + { + string source1 = """ + A + #include "source2.il" + B + """; + + string source2 = """ + X + """; + + ITokenSource lexer = CreateLexerForSource(source1, nameof(source1)); + PreprocessedTokenSource preprocessor = new PreprocessedTokenSource(lexer, path => + { + Assert.Equal($"{nameof(source2)}.il", path); + return CreateLexerForSource(source2, nameof(source2)); + }); + preprocessor.OnPreprocessorSyntaxError += NoLexerDiagnosticsCallback; + BufferedTokenStream stream = new(preprocessor); + stream.Fill(); + Assert.Collection(stream.GetTokens(), + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("A", token.Text); + Assert.Equal(nameof(source1), token.TokenSource.SourceName); + }, + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("X", token.Text); + Assert.Equal(nameof(source2), token.TokenSource.SourceName); + }, + token => + { + Assert.Equal(CILLexer.ID, token.Type); + Assert.Equal("B", token.Text); + Assert.Equal(nameof(source1), token.TokenSource.SourceName); + }, + token => Assert.Equal(CILLexer.Eof, token.Type)); + } + + private void NoLexerDiagnosticsCallback(string arg1, int arg2, int arg3, string arg4) + { + Assert.Fail($"A lexer diagnostic was encountered at {arg2}:{arg3}. '{arg4}'"); + } + + private static ITokenSource CreateLexerForSource(string source, string? sourceName = null) + { + return new CILLexer( + new AntlrInputStream(source) + { + name = sourceName + }); + } + + private static ITokenSource NoIncludeDirectivesCallback(string path) + { + Assert.Fail("The included-file callback was called when no #include was provided in source."); + return null!; + } + } +}