Throw an exception when passing strings by-value as out parameters. (#21513)
authorJeremy Koritzinsky <jkoritzinsky@gmail.com>
Wed, 6 Feb 2019 21:05:28 +0000 (13:05 -0800)
committerGitHub <noreply@github.com>
Wed, 6 Feb 2019 21:05:28 +0000 (13:05 -0800)
* Throw an exception when passing strings by-value as out parameters.

* Fix encoding

* Don't use override in this PR.

* Clean up Marshal_In

Marshal_In was copied back into existence from Marshal_InOut. Clean it up a bit.

* Remove extraneous whitespace.

* Fix failing test.

* Remove out attribute in COM string tests.

* Add back attribute and check for exception thow in COM tests.

* Add block comment to explain the implementation of Reverse_LPWStr_OutAttr in the NETServer.

* Only throw in a CLR->Native marshalling situation.

* Fix asserts from changed code-paths used in ILWSTRMarshaler.

* Add comment and explicitly load in a null value (instead of leaving it uninitialized).

* Apply suggestions from code review

Co-Authored-By: jkoritzinsky <jkoritzinsky@gmail.com>
Co-authored-by: Jan Vorlicek <janvorli@microsoft.com>
src/dlls/mscorrc/mscorrc.rc
src/dlls/mscorrc/resource.h
src/vm/ilmarshalers.cpp
src/vm/ilmarshalers.h
tests/src/Interop/COM/NETClients/Primitives/StringTests.cs
tests/src/Interop/COM/NETServer/StringTesting.cs
tests/src/Interop/StringMarshalling/LPSTR/LPSTRTest.cs
tests/src/Interop/StringMarshalling/LPTSTR/LPTSTRTest.cs
tests/src/Interop/StringMarshalling/LPTSTR/LPTSTRTestNative.cpp
tests/src/Interop/StringMarshalling/LPTSTR/LPTSTRTestPInvokeDef.cs

index 355c902..a0616a2 100644 (file)
@@ -657,6 +657,7 @@ BEGIN
     IDS_EE_BADMARSHAL_BADMETADATA           "Invalid marshaling metadata."
     IDS_EE_BADMARSHAL_CUSTOMMARSHALER       "Custom marshalers are only allowed on classes, strings, arrays, and boxed value types."
     IDS_EE_BADMARSHAL_GENERICS_RESTRICTION  "Generic types cannot be marshaled."
+    IDS_EE_BADMARSHAL_STRING_OUT            "Cannot marshal a string by-value with the [Out] attribute."
 
     IDS_EE_BADMARSHALPARAM_STRINGBUILDER    "Invalid managed/unmanaged type combination (StringBuilders must be paired with LPStr, LPWStr, or LPTStr)."
     IDS_EE_BADMARSHALPARAM_NO_LPTSTR        "Invalid managed/unmanaged type combination (Strings cannot be paired with LPTStr for parameters and return types of methods in interfaces exposed to COM)."
index 500ee74..6d137f7 100644 (file)
 
 #define IDS_EE_NDIRECT_GETPROCADDR_WIN_DLL         0x2644
 #define IDS_EE_NDIRECT_GETPROCADDR_UNIX_SO         0x2645
-
-
+#define IDS_EE_BADMARSHAL_STRING_OUT               0x2646
index 7abfc01..1a253ec 100644 (file)
@@ -252,25 +252,60 @@ LocalDesc ILWSTRMarshaler::GetManagedType()
 void ILWSTRMarshaler::EmitConvertSpaceCLRToNative(ILCodeStream* pslILEmit)
 {
     LIMITED_METHOD_CONTRACT;
-    UNREACHABLE_MSG("Should be in-only and all other paths are covered by the EmitConvertSpaceAndContents* paths");
+    UNREACHABLE_MSG("All paths to this function are covered by the EmitConvertSpaceAndContents* paths");
 }
 
 void ILWSTRMarshaler::EmitConvertContentsCLRToNative(ILCodeStream* pslILEmit)
 {
-    LIMITED_METHOD_CONTRACT;
-    UNREACHABLE_MSG("Should be in-only and all other paths are covered by the EmitConvertSpaceAndContents* paths");
+    STANDARD_VM_CONTRACT;
+
+    // This code path should only be called by an out marshalling. Other codepaths that convert a string to native
+    // should all go through EmitConvertSpaceAndContentsCLRToNative
+    _ASSERTE(IsOut(m_dwMarshalFlags) && !IsCLRToNative(m_dwMarshalFlags) && !IsByref(m_dwMarshalFlags));
+    
+    ILCodeLabel* pNullRefLabel = pslILEmit->NewCodeLabel();
+
+    EmitLoadManagedValue(pslILEmit);
+    pslILEmit->EmitBRFALSE(pNullRefLabel);
+
+    EmitLoadManagedValue(pslILEmit);
+    EmitLoadNativeValue(pslILEmit);
+
+    EmitLoadManagedValue(pslILEmit);
+    EmitCheckManagedStringLength(pslILEmit);
+    
+    // static void System.String.InternalCopy(String src, IntPtr dest,int len)
+    pslILEmit->EmitCALL(METHOD__STRING__INTERNAL_COPY, 3, 0);
+    pslILEmit->EmitLabel(pNullRefLabel);
 }
 
 void ILWSTRMarshaler::EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit)
 {
-    LIMITED_METHOD_CONTRACT;
-    UNREACHABLE_MSG("Should be in-only and all other paths are covered by the EmitConvertSpaceAndContents* paths");
+    STANDARD_VM_CONTRACT;
+    // We currently don't marshal strings from the native to the CLR side in a Reverse-PInvoke unless
+    // the parameter is explicitly annotated as an [In] parameter.
+    pslILEmit->EmitLDNULL();
+    EmitStoreManagedValue(pslILEmit);
 }    
 
 void ILWSTRMarshaler::EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit)
 {
-    LIMITED_METHOD_CONTRACT;
-    UNREACHABLE_MSG("Should be in-only and all other paths are covered by the EmitConvertSpaceAndContents* paths");
+    STANDARD_VM_CONTRACT;
+    
+    ILCodeLabel* pIsNullLabel = pslILEmit->NewCodeLabel();
+        
+    EmitLoadNativeValue(pslILEmit);
+    pslILEmit->EmitBRFALSE(pIsNullLabel);
+
+    EmitLoadNativeValue(pslILEmit);
+    pslILEmit->EmitDUP();
+    EmitCheckNativeStringLength(pslILEmit);
+    pslILEmit->EmitPOP();       // pop num chars
+    
+    pslILEmit->EmitNEWOBJ(METHOD__STRING__CTOR_CHARPTR, 1);
+    EmitStoreManagedValue(pslILEmit);
+    
+    pslILEmit->EmitLabel(pIsNullLabel);
 }    
 
 bool ILWSTRMarshaler::NeedsClearNative()
@@ -447,27 +482,6 @@ void ILWSTRMarshaler::EmitCheckNativeStringLength(ILCodeStream* pslILEmit)
     pslILEmit->EmitCALL(METHOD__STUBHELPERS__CHECK_STRING_LENGTH, 1, 0);
 }
 
-void ILWSTRMarshaler::EmitConvertSpaceAndContentsNativeToCLR(ILCodeStream* pslILEmit)
-{
-    STANDARD_VM_CONTRACT;
-
-    ILCodeLabel* pIsNullLabelByref = pslILEmit->NewCodeLabel();
-        
-    EmitLoadNativeValue(pslILEmit);
-    pslILEmit->EmitBRFALSE(pIsNullLabelByref);
-
-    EmitLoadNativeValue(pslILEmit);
-    pslILEmit->EmitDUP();
-    EmitCheckNativeStringLength(pslILEmit);
-    pslILEmit->EmitPOP();       // pop num chars
-    
-    pslILEmit->EmitNEWOBJ(METHOD__STRING__CTOR_CHARPTR, 1);
-    EmitStoreManagedValue(pslILEmit);
-    
-    pslILEmit->EmitLabel(pIsNullLabelByref);
-}
-
-
 LocalDesc ILOptimizedAllocMarshaler::GetNativeType()
 {
     LIMITED_METHOD_CONTRACT;
index 1c58c0c..3f2c16b 100644 (file)
@@ -1946,7 +1946,7 @@ class ILWSTRMarshaler : public ILMarshaler
 public:
     enum
     {
-        c_fInOnly               = TRUE,
+        c_fInOnly               = FALSE,
         c_nativeSize            = sizeof(void *),
         c_CLRSize               = sizeof(OBJECTREF),
     };
@@ -1961,6 +1961,18 @@ public:
     }
 #endif // _DEBUG
 
+    
+    virtual bool SupportsArgumentMarshal(DWORD dwMarshalFlags, UINT* pErrorResID)
+    {
+        if (IsOut(dwMarshalFlags) && !IsByref(dwMarshalFlags) && IsCLRToNative(dwMarshalFlags))
+        {
+            *pErrorResID = IDS_EE_BADMARSHAL_STRING_OUT;
+            return false;
+        }
+
+        return true;
+    }
+
 protected:
     virtual LocalDesc GetNativeType();
     virtual LocalDesc GetManagedType();
@@ -1972,7 +1984,6 @@ protected:
 
     virtual void EmitConvertSpaceNativeToCLR(ILCodeStream* pslILEmit);
     virtual void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit);
-    virtual void EmitConvertSpaceAndContentsNativeToCLR(ILCodeStream* pslILEmit);
 
     virtual bool NeedsClearNative();
     virtual void EmitClearNative(ILCodeStream* pslILEmit);
index 46cd5d9..0c1212c 100644 (file)
@@ -191,8 +191,7 @@ namespace NetClient
                 Assert.AreEqual(expected, actual);
 
                 actual = local;
-                this.server.Reverse_LPWStr_OutAttr(local, actual); // No-op for strings
-                Assert.AreEqual(local, actual);
+                Assert.Throws<MarshalDirectiveException>( () => this.server.Reverse_LPWStr_OutAttr(local, actual));
             }
 
             foreach (var s in reversableStrings)
index 3a510f5..c47a155 100644 (file)
@@ -126,6 +126,9 @@ public class StringTesting : Server.Contract.IStringTesting
         b = Reverse(a);
     }
 
+    // This behavior is the "desired" behavior for a string passed by-value with an [Out] attribute.
+    // However, block calling a COM or P/Invoke stub with an "[Out] string" parameter since that would allow users to
+    // edit an immutable string value in place. So, in the NetClient.Primitives.StringTests tests, we expect a MarshalDirectiveException.
     public void Reverse_LPWStr_OutAttr([MarshalAs(UnmanagedType.LPWStr)] string a, [Out][MarshalAs(UnmanagedType.LPWStr)] string b)
     {
         b = Reverse(a);
@@ -188,4 +191,4 @@ public class StringTesting : Server.Contract.IStringTesting
     {
         b = Reverse(a);
     }
-}
\ No newline at end of file
+}
index 74a2087..2ea8b33 100644 (file)
@@ -61,7 +61,6 @@ class Test
             strRet = "\0\0\0";
             return strRet;
         }
-        s = "Managed";
         strRet = "Return\0Return\0";
         return strRet;
     }
@@ -193,6 +192,7 @@ class Test
 
         #region ReversePinvoke
         DelMarshal_InOut d1 = new DelMarshal_InOut(Call_DelMarshal_InOut);
+
         if (!PInvokeDef.RPinvoke_DelMarshal_InOut(d1, "ň"))
         {
             ReportFailure("Method RPinvoke_DelMarshal_InOut[Managed Side],Return value is false");
index f7684a3..0cc7b26 100644 (file)
Binary files a/tests/src/Interop/StringMarshalling/LPTSTR/LPTSTRTest.cs and b/tests/src/Interop/StringMarshalling/LPTSTR/LPTSTRTest.cs differ
index 72df3d5..5ecf88a 100644 (file)
@@ -34,6 +34,20 @@ extern "C" LPWSTR ReturnErrString()
 }
 
 //Test Method1
+extern "C" DLL_EXPORT LPWSTR Marshal_In(/*[In]*/LPWSTR s)
+{
+    //Check the Input
+    size_t len = TP_slen(s);
+
+    if((len != lenstrManaged)||(TP_wmemcmp(s,(WCHAR*)strManaged,len)!=0))
+    {
+        printf("Error in Function Marshal_In(Native Client)\n");
+        return ReturnErrString();
+    }
+
+    //Return
+    return ReturnString();
+}
 
 //Test Method2
 extern "C" DLL_EXPORT LPWSTR Marshal_InOut(/*[In,Out]*/LPWSTR s)
index 88c3e74..548c511 100644 (file)
@@ -33,6 +33,10 @@ namespace NativeDefs
 
         [DllImport(NativeBinaryName)]
         [return: MarshalAs(UnmanagedType.LPTStr)]
+        public static extern string Marshal_In([In][MarshalAs(UnmanagedType.LPTStr)]string s);
+
+        [DllImport(NativeBinaryName)]
+        [return: MarshalAs(UnmanagedType.LPTStr)]
         public static extern string Marshal_InOut([In, Out][MarshalAs(UnmanagedType.LPTStr)]string s);
 
         [DllImport(NativeBinaryName)]