Skip to content

Optimizer inlines protected base-field access into a non-family method -> FieldAccessException (--optimize+) #19963

@T-Gro

Description

@T-Gro

Summary

Accessing a C# protected base field from a derived F# member compiles fine, but the optimizer inlines the field load into a non-family method, producing illegal IL that throws System.FieldAccessException at runtime. This happens under --optimize+, which is the default for fsc (Release builds). No closure is required.

Repro

C# library cslib:

public class Base { protected string Field = "hello"; }

F#:

type Inherited() =
    inherit Base()
    member x.Run() = x.Field          // legal: protected field is accessible in a derived member

[<EntryPoint>]
let main _ =
    if (Inherited()).Run() = "hello" then 0 else 1
fsc --optimize+ -r:cslib.dll Program.fs     # default for Release

Actual

Unhandled exception. System.FieldAccessException:
  Attempt by method 'Program.main(System.String[])' to access field 'Base.Field' failed.
   at Program.main(String[] _arg1)
flag result
--optimize- OK
--optimize+ (default) FieldAccessException

Reproduces under both --realsig+ and --realsig-.

Cause

The optimizer inlines Run's body into main, copying ldfld string [cslib]Base::Field into main. Run is on Inherited (has family access to Base); main is not in Base's family, so the relocated ldfld is illegal and the CLR rejects it.

The optimizer already refuses to relocate protected access — but only for method/base calls, not field loads. The free-variable flag UsesMethodLocalConstructs is set for protected TOp.ILCall (TypedTreeOps.Remapping.fs:1221) and consumed by the relocation guards (Optimizer.fs:3097/3896/3978/4171), but a protected field load never sets it (TOp.ValFieldGet/ILAsm paths). Contrast — the method form is safe under the same flags:

member x.Run() = x.Method()   // protected method --optimize+  ->  OK
member x.Run() = x.Field      // protected field  --optimize+  ->  FieldAccessException

Fix direction

Make a family/protected ldfld/ldflda/stfld set UsesMethodLocalConstructs, so the four existing optimizer relocation guards pin it in-family exactly as they already do for protected method calls. Near-zero baseline churn.

Related

Same root container-placement family as #5302 (protected base access from a closure); fixing this flag is a prerequisite for any closure-based enablement there.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions