diff --git a/docs/guides/managed/managed-code-analysis.md b/docs/guides/managed/managed-code-analysis.md index e21eba91dc..52fe21be53 100644 --- a/docs/guides/managed/managed-code-analysis.md +++ b/docs/guides/managed/managed-code-analysis.md @@ -650,7 +650,8 @@ unchanged signature. Comparison retains exact byte-based pairs for malformed signatures, including empty, truncated, or invalid headers, but reports them as unknown with a `signature-malformed` limitation when no change is observed. This applies to historical Evidence without signature-resolution attestation as -well. Runtime-provided methods are counted separately in +well. Runtime-provided methods have the Runtime code type or the InternalCall +implementation flag, as core library FCalls do. They are counted separately in `native_implementations.runtime_provided`; they have no file-backed native body for this workflow to map and do not increase `native_body_unresolved`. Historical verification Evidence without this separate count leaves it unknown (`null`). diff --git a/src/dotnet/ManagedMethodBodyReader.ts b/src/dotnet/ManagedMethodBodyReader.ts index f94727751a..b51122533a 100644 --- a/src/dotnet/ManagedMethodBodyReader.ts +++ b/src/dotnet/ManagedMethodBodyReader.ts @@ -128,10 +128,11 @@ export const methodBody = ( const native = context.implementation.codeType !== "il" || context.implementation.managedKind !== "managed" || - context.implementation.pinvokeFlag; + context.implementation.pinvokeFlag || + context.implementation.internalCall; if (native) { const issue = - "CIL decoding is unavailable for native, runtime, unmanaged, or P/Invoke implementations"; + "CIL decoding is unavailable for native, runtime, InternalCall, unmanaged, or P/Invoke implementations"; return { body: emptyMethodBody(rva, "partial", issue), tokenInstructions: [], diff --git a/src/dotnet/ManagedMethodDefinitionRows.ts b/src/dotnet/ManagedMethodDefinitionRows.ts index 0c6c3db4d4..0563ab3fa5 100644 --- a/src/dotnet/ManagedMethodDefinitionRows.ts +++ b/src/dotnet/ManagedMethodDefinitionRows.ts @@ -6,6 +6,8 @@ export interface ManagedMethodImplementationFlags { readonly codeType: "il" | "native" | "optil" | "runtime"; readonly managedKind: "managed" | "unmanaged"; readonly pinvokeFlag: boolean; + /** MethodImplOptions.InternalCall (0x1000): the runtime implements the body. */ + readonly internalCall: boolean; } const METHOD_CODE_TYPES = ["il", "native", "optil", "runtime"] as const; @@ -42,6 +44,7 @@ export const readMethodDefinitionRow = ( codeType: METHOD_CODE_TYPES[(implFlags & 0x0003) as 0 | 1 | 2 | 3], managedKind: (implFlags & 0x0004) === 0 ? "managed" : "unmanaged", pinvokeFlag: (flags & 0x2000) !== 0, + internalCall: (implFlags & 0x1000) !== 0, }, }; }; diff --git a/src/dotnet/ManagedNativeBoundaryHelpers.ts b/src/dotnet/ManagedNativeBoundaryHelpers.ts index b38612d6d7..e88517cdc2 100644 --- a/src/dotnet/ManagedNativeBoundaryHelpers.ts +++ b/src/dotnet/ManagedNativeBoundaryHelpers.ts @@ -321,9 +321,15 @@ export const nativeImplementations = ( const implementations: NativeImplementation[] = []; for (const method of methods) { if (method.kind !== "method") continue; - const { codeType, managedKind, pinvokeFlag } = method.implementation; + const { codeType, managedKind, pinvokeFlag, internalCall } = + method.implementation; const pinvokeDeclared = pinvokeTokens.has(method.token) || pinvokeFlag; - if (!pinvokeDeclared && codeType === "il" && managedKind === "managed") + if ( + !pinvokeDeclared && + !internalCall && + codeType === "il" && + managedKind === "managed" + ) continue; implementations.push({ token: method.token, @@ -335,7 +341,12 @@ export const nativeImplementations = ( code_type: codeType, managed_kind: managedKind, pinvoke_declared: pinvokeDeclared, - boundary_kind: boundaryKind(pinvokeDeclared, codeType, managedKind), + boundary_kind: boundaryKind( + pinvokeDeclared, + codeType, + managedKind, + internalCall, + ), body_interpretation: method.rva === 0 ? "not-file-backed" : "native-or-runtime", }); @@ -347,10 +358,11 @@ const boundaryKind = ( pinvokeDeclared: boolean, codeType: ManagedMethodImplementationFlags["codeType"], managedKind: ManagedMethodImplementationFlags["managedKind"], + internalCall: boolean, ): NativeImplementation["boundary_kind"] => { if (pinvokeDeclared) return "pinvoke"; if (codeType === "native") return "native-body"; - if (codeType === "runtime") return "runtime-provided"; + if (codeType === "runtime" || internalCall) return "runtime-provided"; if (managedKind === "unmanaged") return "unmanaged-method"; return "mixed-or-unknown"; }; diff --git a/src/dotnet/ManagedNativeBoundaryInspector.test.ts b/src/dotnet/ManagedNativeBoundaryInspector.test.ts index 384d0c4d6d..2e51551768 100644 --- a/src/dotnet/ManagedNativeBoundaryInspector.test.ts +++ b/src/dotnet/ManagedNativeBoundaryInspector.test.ts @@ -524,6 +524,40 @@ it.each([ }, ); +it.each([ + { zeroRva: true, status: "absent", interpretation: "not-file-backed" }, + { zeroRva: false, status: "partial", interpretation: "native-or-runtime" }, +])( + "publishes an InternalCall method as a runtime-provided boundary consistently with body admission (zero RVA: $zeroRva)", + ({ zeroRva, status, interpretation }) => { + // MethodImplOptions.InternalCall: the runtime implements the body. Core + // library FCalls carry this bit with IL code type and no RVA. A malformed + // row can keep IL flags and a file-backed RVA, which the runtime rejects. + const { members, boundaries } = inspectImplementationFixture({ + implFlags: 0x1000, + zeroRva, + }); + expect(boundaries.native_implementations).toMatchObject([ + { + token: members.methods[0]?.token, + impl_flags: 0x1000, + code_type: "il", + managed_kind: "managed", + pinvoke_declared: false, + boundary_kind: "runtime-provided", + body_interpretation: interpretation, + }, + ]); + expect(boundaries.summary.native_implementation_count).toBe(1); + expect(members.methods[0]?.body).toMatchObject({ + status, + il_sha256: null, + }); + if (!zeroRva) expect(members.methods[0]?.body.issue).toMatch(/CIL/u); + else expect(members.methods[0]?.body.issue).toBeNull(); + }, +); + it.each([ { name: "PInvoke flag on managed IL",