Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #16398 - The dotnet framework has a limit of ~64K methods in a single class. #16427

Merged
merged 1 commit into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/8.0.200.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* Miscellaneous fixes to parentheses analysis. ([PR #16262](https://github.com/dotnet/fsharp/pull/16262), [PR #16391](https://github.com/dotnet/fsharp/pull/16391), [PR #16370](https://github.com/dotnet/fsharp/pull/16370), [PR #16395](https://github.com/dotnet/fsharp/pull/16395))
* Correctly handle assembly imports with public key token of 0 length. ([Issue #16359](https://github.com/dotnet/fsharp/issues/16359), [PR #16363](https://github.com/dotnet/fsharp/pull/16363))
* Fix #16398 - The dotnet framework has a limit of ~64K methods in a single class. Introduce a compile-time error if any class has over approx 64K methods in generated IL

### Added
* Raise a new error when interfaces with auto properties are implemented on constructor-less types. ([PR #16352](https://github.com/dotnet/fsharp/pull/16352))
Expand Down
20 changes: 17 additions & 3 deletions src/Compiler/AbstractIL/ilwrite.fs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ let emitBytesViaBuffer f = use bb = ByteBuffer.Create EmitBytesViaBufferCapacity
/// Alignment and padding
let align alignment n = ((n + alignment - 1) / alignment) * alignment


/// Maximum number of methods in a dotnet type
/// This differs from the spec and file formats slightly which suggests 0xfffe is the maximum
/// this value was identified empirically.
[<Literal>]
let maximumMethodsPerDotNetType = 0xfff0

//---------------------------------------------------------------------
// Concrete token representations etc. used in PE files
//---------------------------------------------------------------------
Expand Down Expand Up @@ -672,8 +679,14 @@ let GetTypeNameAsElemPair cenv n =
//=====================================================================

let rec GenTypeDefPass1 enc cenv (tdef: ILTypeDef) =
ignore (cenv.typeDefs.AddUniqueEntry "type index" (fun (TdKey (_, n)) -> n) (TdKey (enc, tdef.Name)))
GenTypeDefsPass1 (enc@[tdef.Name]) cenv (tdef.NestedTypes.AsList())
ignore (cenv.typeDefs.AddUniqueEntry "type index" (fun (TdKey (_, n)) -> n) (TdKey (enc, tdef.Name)))

// Verify that the typedef contains fewer than maximumMethodsPerDotNetType
let count = tdef.Methods.AsArray().Length
if count > maximumMethodsPerDotNetType then
errorR(Error(FSComp.SR.tooManyMethodsInDotNetTypeWritingAssembly (tdef.Name, count, maximumMethodsPerDotNetType), rangeStartup))

GenTypeDefsPass1 (enc@[tdef.Name]) cenv (tdef.NestedTypes.AsList())

and GenTypeDefsPass1 enc cenv tdefs = List.iter (GenTypeDefPass1 enc cenv) tdefs

Expand All @@ -682,7 +695,8 @@ and GenTypeDefsPass1 enc cenv tdefs = List.iter (GenTypeDefPass1 enc cenv) tdefs
//=====================================================================

let rec GetIdxForTypeDef cenv key =
try cenv.typeDefs.GetTableEntry key
try
cenv.typeDefs.GetTableEntry key
with
:? KeyNotFoundException ->
let (TdKey (enc, n) ) = key
Expand Down
3 changes: 2 additions & 1 deletion src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1738,4 +1738,5 @@ featureReuseSameFieldsInStructUnions,"Share underlying fields in a [<Struct>] di
3860,chkStaticMembersOnObjectExpressions,"Object expressions cannot implement interfaces with static abstract members or declare static members."
3861,chkTailCallAttrOnNonRec,"The TailCall attribute should only be applied to recursive functions."
3862,parsStaticMemberImcompleteSyntax,"Incomplete declaration of a static construct. Use 'static let','static do','static member' or 'static val' for declaration."
3863,parsExpectingField,"Expecting record field"
3863,parsExpectingField,"Expecting record field"
vzarytovskii marked this conversation as resolved.
Show resolved Hide resolved
3864,tooManyMethodsInDotNetTypeWritingAssembly,"The type '%s' has too many methods. Found: '%d', maximum: '%d'"
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace EmittedIL

open Microsoft.FSharp.Core
open Xunit
open FSharp.Test.Compiler

module VeryLargeClasses =

let classWithManyMethods n =
let methods =
let mutable source = ""
for i = 0 to n - 1 do
source <- source + $"""
static member Method%05x{i}() = () """
source

FSharp
$"""
namespace VeryLargeClassesTestcases

type Type1 ={methods}
"""

[<Fact>]
let ``More than 64K Methods -- should fail`` () =
classWithManyMethods (1024 * 64 + 1)
|> compile
|> shouldFail
|> withDiagnostics [
(Error 3864, Line 1, Col 1, Line 1, Col 1, "The type 'VeryLargeClassesTestcases.Type1' has too many methods. Found: '65537', maximum: '65520'")
]

[<Fact>]
let ``Exactly (0xfff0) Methods -- should succeed`` () =
FSharp
"""
module MyMain
open System
open System.Reflection
do printfn $"location: {typeof<VeryLargeClassesTestcases.Type1>.Assembly.Location}"
let asm = Assembly.LoadFrom(typeof<VeryLargeClassesTestcases.Type1>.Assembly.Location)
printfn $"asm: {asm}"
let types = asm.GetTypes()
printfn "length: {types.Length}"
"""
|> withReferences [ classWithManyMethods 0xfff0 |> asLibrary ]
|> asExe
|> compileAndRun
|> shouldSucceed

Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
<Compile Include="Conformance\UnitsOfMeasure\Parsing.fs" />
<Compile Include="Conformance\UnitsOfMeasure\TypeChecker.fs" />
<Compile Include="EmittedIL\CompilerGeneratedAttributeOnAccessors.fs" />
<Compile Include="EmittedIL\VeryLargeClasses.fs" />
<Compile Include="EmittedIL\EmptyArray.fs" />
<Compile Include="EmittedIL\Enums.fs" />
<Compile Include="EmittedIL\Literals.fs" />
Expand Down
2 changes: 1 addition & 1 deletion tests/FSharp.Test.Utilities/CompilerAssert.fs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ module rec CompilerAssertHelpers =
let name =
match nameOpt with
| Some name -> name
| _ -> tryCreateTemporaryFileName()
| _ -> tryCreateTemporaryFileNameInDirectory(outputDirectory)

let outputFilePath = Path.ChangeExtension (Path.Combine(outputDirectory.FullName, name), if isExe then ".exe" else ".dll")
disposals.Add(disposeFile outputFilePath)
Expand Down
5 changes: 2 additions & 3 deletions tests/FSharp.Test.Utilities/TestFramework.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@ let tryCreateTemporaryFileName () =
filePath

// Create a temporaryFileName -- newGuid is random --- there is no point validating the file alread exists because: threading and Path.ChangeExtension() is commonly used after this API
let tryCreateTemporaryFileNameInDirectory (directory) =
let tryCreateTemporaryFileNameInDirectory (directory: DirectoryInfo) =
let fileName = ("Temp-" + Guid.NewGuid().ToString() + ".tmp").Replace('-', '_')
let filePath = Path.Combine(directory, fileName)
let filePath = Path.Combine(directory.FullName, fileName)
filePath


[<RequireQualifiedAccess>]
module Commands =

Expand Down
4 changes: 2 additions & 2 deletions tests/service/ScriptOptionsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ let ``can generate options for different frameworks regardless of execution envi
[<TestCase([| "--targetprofile:netstandard" |])>]
[<Test>]
let ``can resolve nuget packages to right target framework for different frameworks regardless of execution environment``(flags) =
let path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)
let path = DirectoryInfo(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location))
let file = tryCreateTemporaryFileNameInDirectory(path) + ".fsx"
let scriptFullPath = Path.Combine(path, file)
let scriptFullPath = Path.Combine(path.FullName, file)
let scriptSource = """
#r "nuget: FSharp.Data, 3.3.3"
open System
Expand Down
Loading