Skip to content

Commit fb9ef5b

Browse files
authored
Handle PInvoke analysis similar to other methods (#2091)
* PInvoke fix * FB * FB * FB2
1 parent b5dac59 commit fb9ef5b

File tree

4 files changed

+259
-100
lines changed

4 files changed

+259
-100
lines changed

src/linker/Linker.Dataflow/MethodBodyScanner.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -526,13 +526,13 @@ public void Scan (MethodBody methodBody)
526526
}
527527

528528
// Pop arguments
529-
PopUnknown (currentStack, signature.Parameters.Count, methodBody, operation.Offset);
529+
if (signature.Parameters.Count > 0)
530+
PopUnknown (currentStack, signature.Parameters.Count, methodBody, operation.Offset);
530531

531532
// Pop function pointer
532533
PopUnknown (currentStack, 1, methodBody, operation.Offset);
533534

534-
// Push return value
535-
if (signature.ReturnType.MetadataType != MetadataType.Void)
535+
if (GetReturnTypeWithoutModifiers (signature.ReturnType).MetadataType != MetadataType.Void)
536536
PushUnknown (currentStack);
537537
}
538538
break;
@@ -567,7 +567,9 @@ public void Scan (MethodBody methodBody)
567567
break;
568568

569569
case Code.Ret: {
570-
bool hasReturnValue = methodBody.Method.ReturnType.MetadataType != MetadataType.Void;
570+
571+
bool hasReturnValue = GetReturnTypeWithoutModifiers (methodBody.Method.ReturnType).MetadataType != MetadataType.Void;
572+
571573
if (currentStack.Count != (hasReturnValue ? 1 : 0)) {
572574
WarnAboutInvalidILInMethod (methodBody, operation.Offset);
573575
}
@@ -892,7 +894,7 @@ private void HandleCall (
892894
else
893895
methodReturnValue = newObjValue;
894896
} else {
895-
if (calledMethod.ReturnType.MetadataType != MetadataType.Void) {
897+
if (GetReturnTypeWithoutModifiers (calledMethod.ReturnType).MetadataType != MetadataType.Void) {
896898
methodReturnValue = UnknownValue.Instance;
897899
}
898900
}
@@ -908,6 +910,14 @@ private void HandleCall (
908910
}
909911
}
910912

913+
protected static TypeReference GetReturnTypeWithoutModifiers (TypeReference returnType)
914+
{
915+
while (returnType is IModifierType) {
916+
returnType = ((IModifierType) returnType).ElementType;
917+
}
918+
return returnType;
919+
}
920+
911921
// Array types that are dynamically accessed should resolve to System.Array instead of its element type - which is what Cecil resolves to.
912922
// Any data flow annotations placed on a type parameter which receives an array type apply to the array itself. None of the members in its
913923
// element type should be marked.

src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ public static bool RequiresReflectionMethodBodyScannerForCallSite (LinkContext c
3737
if (methodDefinition == null)
3838
return false;
3939

40-
return
41-
GetIntrinsicIdForMethod (methodDefinition) > IntrinsicId.RequiresReflectionBodyScanner_Sentinel ||
40+
return GetIntrinsicIdForMethod (methodDefinition) > IntrinsicId.RequiresReflectionBodyScanner_Sentinel ||
4241
context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (methodDefinition) ||
43-
context.Annotations.HasLinkerAttribute<RequiresUnreferencedCodeAttribute> (methodDefinition);
42+
context.Annotations.HasLinkerAttribute<RequiresUnreferencedCodeAttribute> (methodDefinition)
43+
|| methodDefinition.IsPInvokeImpl;
4444
}
4545

4646
public static bool RequiresReflectionMethodBodyScannerForMethodBody (FlowAnnotations flowAnnotations, MethodDefinition methodDefinition)
@@ -75,7 +75,7 @@ public void ScanAndProcessReturnValue (MethodBody methodBody)
7575
{
7676
Scan (methodBody);
7777

78-
if (methodBody.Method.ReturnType.MetadataType != MetadataType.Void) {
78+
if (GetReturnTypeWithoutModifiers (methodBody.Method.ReturnType).MetadataType != MetadataType.Void) {
7979
var method = methodBody.Method;
8080
var requiredMemberTypes = _context.Annotations.FlowAnnotations.GetReturnParameterAnnotation (method);
8181
if (requiredMemberTypes != 0) {
@@ -1729,6 +1729,20 @@ methodParams[argsParam] is ArrayValue arrayValue &&
17291729
break;
17301730

17311731
default:
1732+
1733+
if (calledMethodDefinition.IsPInvokeImpl) {
1734+
// Is the PInvoke dangerous?
1735+
bool comDangerousMethod = IsComInterop (calledMethodDefinition.MethodReturnType, calledMethodDefinition.ReturnType);
1736+
foreach (ParameterDefinition pd in calledMethodDefinition.Parameters) {
1737+
comDangerousMethod |= IsComInterop (pd, pd.ParameterType);
1738+
}
1739+
1740+
if (comDangerousMethod) {
1741+
reflectionContext.AnalyzingPattern ();
1742+
reflectionContext.RecordUnrecognizedPattern (2050, $"P/invoke method '{calledMethodDefinition.GetDisplayName ()}' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.");
1743+
}
1744+
}
1745+
17321746
if (requiresDataFlowAnalysis) {
17331747
reflectionContext.AnalyzingPattern ();
17341748
for (int parameterIndex = 0; parameterIndex < methodParams.Count; parameterIndex++) {
@@ -1755,7 +1769,7 @@ methodParams[argsParam] is ArrayValue arrayValue &&
17551769

17561770
// To get good reporting of errors we need to track the origin of the value for all method calls
17571771
// but except Newobj as those are special.
1758-
if (calledMethodDefinition.ReturnType.MetadataType != MetadataType.Void) {
1772+
if (GetReturnTypeWithoutModifiers (calledMethodDefinition.ReturnType).MetadataType != MetadataType.Void) {
17591773
methodReturnValue = CreateMethodReturnValue (calledMethodDefinition, returnValueDynamicallyAccessedMemberTypes);
17601774

17611775
return true;
@@ -1771,7 +1785,7 @@ methodParams[argsParam] is ArrayValue arrayValue &&
17711785
// didn't set the return value (and the method has a return value), we will set it to be an
17721786
// unknown value with the return type of the method.
17731787
if (methodReturnValue == null) {
1774-
if (calledMethod.ReturnType.MetadataType != MetadataType.Void) {
1788+
if (GetReturnTypeWithoutModifiers (calledMethod.ReturnType).MetadataType != MetadataType.Void) {
17751789
methodReturnValue = CreateMethodReturnValue (calledMethodDefinition, returnValueDynamicallyAccessedMemberTypes);
17761790
}
17771791
}
@@ -1794,6 +1808,64 @@ methodParams[argsParam] is ArrayValue arrayValue &&
17941808
return true;
17951809
}
17961810

1811+
bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference parameterType)
1812+
{
1813+
// This is best effort. One can likely find ways how to get COM without triggering these alarms.
1814+
// AsAny marshalling of a struct with an object-typed field would be one, for example.
1815+
1816+
// This logic roughly corresponds to MarshalInfo::MarshalInfo in CoreCLR,
1817+
// not trying to handle invalid cases and distinctions that are not interesting wrt
1818+
// "is this COM?" question.
1819+
1820+
NativeType nativeType = NativeType.None;
1821+
if (marshalInfoProvider.HasMarshalInfo) {
1822+
nativeType = marshalInfoProvider.MarshalInfo.NativeType;
1823+
}
1824+
1825+
if (nativeType == NativeType.IUnknown || nativeType == NativeType.IDispatch || nativeType == NativeType.IntF) {
1826+
// This is COM by definition
1827+
return true;
1828+
}
1829+
1830+
if (nativeType == NativeType.None) {
1831+
// Resolve will look at the element type
1832+
var parameterTypeDef = _context.TryResolve (parameterType);
1833+
1834+
if (parameterTypeDef != null) {
1835+
if (parameterTypeDef.IsTypeOf ("System", "Array")) {
1836+
// System.Array marshals as IUnknown by default
1837+
return true;
1838+
} else if (parameterTypeDef.IsTypeOf ("System", "String") ||
1839+
parameterTypeDef.IsTypeOf ("System.Text", "StringBuilder")) {
1840+
// String and StringBuilder are special cased by interop
1841+
return false;
1842+
}
1843+
1844+
if (parameterTypeDef.IsValueType) {
1845+
// Value types don't marshal as COM
1846+
return false;
1847+
} else if (parameterTypeDef.IsInterface) {
1848+
// Interface types marshal as COM by default
1849+
return true;
1850+
} else if (parameterTypeDef.IsMulticastDelegate ()) {
1851+
// Delegates are special cased by interop
1852+
return false;
1853+
} else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle")) {
1854+
// Subclasses of CriticalHandle are special cased by interop
1855+
return false;
1856+
} else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "SafeHandle")) {
1857+
// Subclasses of SafeHandle are special cased by interop
1858+
return false;
1859+
} else if (!parameterTypeDef.IsSequentialLayout && !parameterTypeDef.IsExplicitLayout) {
1860+
// Rest of classes that don't have layout marshal as COM
1861+
return true;
1862+
}
1863+
}
1864+
}
1865+
1866+
return false;
1867+
}
1868+
17971869
bool AnalyzeGenericInstatiationTypeArray (ValueNode arrayParam, ref ReflectionPatternContext reflectionContext, MethodReference calledMethod, IList<GenericParameter> genericParameters)
17981870
{
17991871
bool hasRequirements = false;

src/linker/Linker.Steps/MarkStep.cs

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3042,28 +3042,13 @@ void ProcessInteropMethod (MethodDefinition method)
30423042

30433043
TypeDefinition returnTypeDefinition = _context.TryResolve (method.ReturnType);
30443044

3045-
bool didWarnAboutCom = false;
3046-
30473045
const bool includeStaticFields = false;
30483046
if (returnTypeDefinition != null) {
30493047
if (!returnTypeDefinition.IsImport) {
30503048
// What we keep here is correct most of the time, but not every time. Fine for now.
30513049
MarkDefaultConstructor (returnTypeDefinition, new DependencyInfo (DependencyKind.InteropMethodDependency, method));
30523050
MarkFields (returnTypeDefinition, includeStaticFields, new DependencyInfo (DependencyKind.InteropMethodDependency, method));
30533051
}
3054-
3055-
// Best-effort attempt to find COM marshalling.
3056-
// It might seem reasonable to allow out parameters, but once we get a foreign object back (an RCW), it's going to look
3057-
// like a regular managed class/interface, but every method invocation that takes a class/interface implies COM
3058-
// marshalling. We can't detect that once we have an RCW.
3059-
if (method.IsPInvokeImpl) {
3060-
if (IsComInterop (method.MethodReturnType, method.ReturnType) && !didWarnAboutCom) {
3061-
_context.LogWarning (
3062-
$"P/invoke method '{method.GetDisplayName ()}' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.",
3063-
2050, method, subcategory: MessageSubCategory.TrimAnalysis);
3064-
didWarnAboutCom = true;
3065-
}
3066-
}
30673052
}
30683053

30693054
if (method.HasThis && !method.DeclaringType.IsImport) {
@@ -3085,75 +3070,8 @@ void ProcessInteropMethod (MethodDefinition method)
30853070
MarkDefaultConstructor (paramTypeDefinition, new DependencyInfo (DependencyKind.InteropMethodDependency, method));
30863071
}
30873072
}
3088-
3089-
// Best-effort attempt to find COM marshalling.
3090-
// It might seem reasonable to allow out parameters, but once we get a foreign object back (an RCW), it's going to look
3091-
// like a regular managed class/interface, but every method invocation that takes a class/interface implies COM
3092-
// marshalling. We can't detect that once we have an RCW.
3093-
if (method.IsPInvokeImpl) {
3094-
if (IsComInterop (pd, paramTypeReference) && !didWarnAboutCom) {
3095-
_context.LogWarning ($"P/invoke method '{method.GetDisplayName ()}' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.", 2050, method);
3096-
didWarnAboutCom = true;
3097-
}
3098-
}
3099-
}
3100-
}
3101-
}
3102-
3103-
bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference parameterType)
3104-
{
3105-
// This is best effort. One can likely find ways how to get COM without triggering these alarms.
3106-
// AsAny marshalling of a struct with an object-typed field would be one, for example.
3107-
3108-
// This logic roughly corresponds to MarshalInfo::MarshalInfo in CoreCLR,
3109-
// not trying to handle invalid cases and distinctions that are not interesting wrt
3110-
// "is this COM?" question.
3111-
3112-
NativeType nativeType = NativeType.None;
3113-
if (marshalInfoProvider.HasMarshalInfo) {
3114-
nativeType = marshalInfoProvider.MarshalInfo.NativeType;
3115-
}
3116-
3117-
if (nativeType == NativeType.IUnknown || nativeType == NativeType.IDispatch || nativeType == NativeType.IntF) {
3118-
// This is COM by definition
3119-
return true;
3120-
}
3121-
3122-
if (nativeType == NativeType.None) {
3123-
if (parameterType.IsTypeOf ("System", "Array")) {
3124-
// System.Array marshals as IUnknown by default
3125-
return true;
3126-
} else if (parameterType.IsTypeOf ("System", "String") ||
3127-
parameterType.IsTypeOf ("System.Text", "StringBuilder")) {
3128-
// String and StringBuilder are special cased by interop
3129-
return false;
3130-
}
3131-
3132-
var parameterTypeDef = _context.TryResolve (parameterType);
3133-
if (parameterTypeDef != null) {
3134-
if (parameterTypeDef.IsValueType) {
3135-
// Value types don't marshal as COM
3136-
return false;
3137-
} else if (parameterTypeDef.IsInterface) {
3138-
// Interface types marshal as COM by default
3139-
return true;
3140-
} else if (parameterTypeDef.IsMulticastDelegate ()) {
3141-
// Delegates are special cased by interop
3142-
return false;
3143-
} else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle")) {
3144-
// Subclasses of CriticalHandle are special cased by interop
3145-
return false;
3146-
} else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "SafeHandle")) {
3147-
// Subclasses of SafeHandle are special cased by interop
3148-
return false;
3149-
} else if (!parameterTypeDef.IsSequentialLayout && !parameterTypeDef.IsExplicitLayout) {
3150-
// Rest of classes that don't have layout marshal as COM
3151-
return true;
3152-
}
31533073
}
31543074
}
3155-
3156-
return false;
31573075
}
31583076

31593077
protected virtual bool ShouldParseMethodBody (MethodDefinition method)

0 commit comments

Comments
 (0)