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

Resolving ILLink warnings on System.Private.Xml (Part 1) #49413

Merged
merged 5 commits into from
Mar 19, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,6 @@
<property name="Scope">member</property>
<property name="Target">M:System.Xml.Serialization.XmlReflectionImporter.GetMethodFromSchemaProvider(System.Xml.Serialization.XmlSchemaProviderAttribute,System.Type)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2070</argument>
<property name="Scope">member</property>
<property name="Target">M:System.Xml.Xsl.XslCompiledTransform.Load(System.Type)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2072</argument>
Expand All @@ -169,12 +163,6 @@
<property name="Scope">member</property>
<property name="Target">M:System.Xml.Serialization.XmlSerializationILGen.GenerateTypedSerializer(System.String,System.String,System.Xml.Serialization.XmlMapping,System.Xml.Serialization.CodeIdentifiers,System.String,System.String,System.String)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2072</argument>
<property name="Scope">member</property>
<property name="Target">M:System.Xml.Xsl.XsltOld.Processor.#ctor(System.Xml.XPath.XPathNavigator,System.Xml.Xsl.XsltArgumentList,System.Xml.XmlResolver,System.Xml.Xsl.XsltOld.Stylesheet,System.Collections.Generic.List{System.Xml.Xsl.XsltOld.TheQuery},System.Xml.Xsl.XsltOld.RootAction,System.Xml.Xsl.XsltOld.Debugger.IXsltDebugger)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2075</argument>
Expand Down Expand Up @@ -313,18 +301,6 @@
<property name="Scope">member</property>
<property name="Target">M:System.Xml.Serialization.XmlSerializationWriterILGen.WriteElement(System.Xml.Serialization.SourceInfo,System.Xml.Serialization.ElementAccessor,System.String,System.Boolean)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2075</argument>
<property name="Scope">member</property>
<property name="Target">M:System.Xml.Xsl.IlGen.XmlILModule.BakeMethods</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2075</argument>
<property name="Scope">member</property>
<property name="Target">M:System.Xml.Xsl.XsltOld.XsltCompileContext.GetExtentionMethod(System.String,System.String,System.Xml.XPath.XPathResultType[],System.Object@)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2077</argument>
Expand All @@ -349,5 +325,11 @@
<property name="Scope">member</property>
<property name="Target">M:System.Xml.Xsl.Runtime.XmlExtensionFunctionTable.Bind(System.String,System.String,System.Int32,System.Type,System.Reflection.BindingFlags)</property>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2075</argument>
<property name="Scope">member</property>
<property name="Target">M:System.Xml.Xsl.XsltOld.XsltCompileContext.GetExtentionMethod(System.String,System.String,System.Xml.XPath.XPathResultType[],System.Object@)</property>
</attribute>
</assembly>
</linker>
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ private QilExpression Compile(Compiler compiler)
}

// Create list of all early bound objects
Scripts.LinkerSafeDictionary scriptClasses = compiler.Scripts.ScriptClasses;
Scripts.TrimSafeDictionary scriptClasses = compiler.Scripts.ScriptClasses;
List<EarlyBoundInfo> ebTypes = new List<EarlyBoundInfo>(scriptClasses.Count);
foreach (string key in scriptClasses.Keys)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change here to enumerate the keys and then index into the dictionary? Why not just expose GetEnumerator from TrimSafeDictionary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is that if we keep it as it was, then we would get a warning (even when using TrimmerSafeDictionary) since the Type would be comming form method: System.Collections.Generic.KeyValuePair<Tkey, TValue>.get which wouldn't be annotated. Of course we could suppress that, but by doing it the way I'm doing it here, the value gets accessed via the index operator on TrimmerSafeDictionary, so that way the linker will not throw a warning and we won't need an additional suppression.

{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ namespace System.Xml.Xsl.Xslt
internal class Scripts
{
private readonly Compiler _compiler;
private readonly LinkerSafeDictionary _nsToType = new LinkerSafeDictionary();
private readonly TrimSafeDictionary _nsToType = new TrimSafeDictionary();
private readonly XmlExtensionFunctionTable _extFuncs = new XmlExtensionFunctionTable();

public Scripts(Compiler compiler)
{
_compiler = compiler;
}

public LinkerSafeDictionary ScriptClasses
public TrimSafeDictionary ScriptClasses
{
get { return _nsToType; }
}
Expand All @@ -44,43 +44,28 @@ public LinkerSafeDictionary ScriptClasses
return null;
}

internal class LinkerSafeDictionary : IDictionary<string, Type?>
internal class TrimSafeDictionary
{
private readonly Dictionary<string, Type?> _backingDictionary = new Dictionary<string, Type?>();

public Type? this[string key]
{
[UnconditionalSuppressMessage("TrimAnalysis", "IL2093:MissingAttributeOnBaseClass", Justification = "This implementation of IDictionary must have extra annotation attributes in order to be trim safe")]
[UnconditionalSuppressMessage("TrimAnalysis", "IL2073:MissingDynamicallyAccessedMembers",
Justification = "The getter of the dictionary is not annotated to preserve the constructor, but the sources that are adding the items to " +
"the dictionary are annotated so we can supress the message as we know the constructor will be preserved.")]
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just put the attribute on the whole property?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chatted offline, this is a problem specific to Indexers and have logged the following issue for investigation tracking: dotnet/linker#1902

get => ((IDictionary<string, Type?>)_backingDictionary)[key];
[UnconditionalSuppressMessage("TrimAnalysis", "IL2092:MissingAttributeOnBaseClass", Justification = "This implementation of IDictionary must have extra annotation attributes in order to be trim safe")]
[param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
set => ((IDictionary<string, Type?>)_backingDictionary)[key] = value;
}

public ICollection<string> Keys => ((IDictionary<string, Type?>)_backingDictionary).Keys;

public ICollection<Type?> Values => ((IDictionary<string, Type?>)_backingDictionary).Values;

public int Count => ((ICollection<KeyValuePair<string, Type?>>)_backingDictionary).Count;
Copy link
Member

@eerhardt eerhardt Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are all these casts of _backingDictionary necessary? Can't you just return _backingDictionary.Count? #Resolved

Copy link
Member Author

@joperezr joperezr Mar 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix it, not necessary, I just used the quick refactoring ctrl+'.' options in VS that added the casts:
image
#Resolved


public bool IsReadOnly => ((ICollection<KeyValuePair<string, Type?>>)_backingDictionary).IsReadOnly;

[UnconditionalSuppressMessage("TrimAnalysis", "IL2092:MissingAttributeOnBaseClass", Justification = "This implementation of IDictionary must have extra annotation attributes in order to be trim safe")]
public void Add(string key, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? value) => ((IDictionary<string, Type?>)_backingDictionary).Add(key, value);
public void Add(KeyValuePair<string, Type?> item) => ((ICollection<KeyValuePair<string, Type?>>)_backingDictionary).Add(item);
public void Clear() => ((ICollection<KeyValuePair<string, Type?>>)_backingDictionary).Clear();
public bool Contains(KeyValuePair<string, Type?> item) => ((ICollection<KeyValuePair<string, Type?>>)_backingDictionary).Contains(item);
public bool ContainsKey(string key) => ((IDictionary<string, Type?>)_backingDictionary).ContainsKey(key);
public void CopyTo(KeyValuePair<string, Type?>[] array, int arrayIndex) => ((ICollection<KeyValuePair<string, Type?>>)_backingDictionary).CopyTo(array, arrayIndex);
public IEnumerator<KeyValuePair<string, Type?>> GetEnumerator() => ((IEnumerable<KeyValuePair<string, Type?>>)_backingDictionary).GetEnumerator();
public bool Remove(string key) => ((IDictionary<string, Type?>)_backingDictionary).Remove(key);
public bool Remove(KeyValuePair<string, Type?> item) => ((ICollection<KeyValuePair<string, Type?>>)_backingDictionary).Remove(item);

public bool TryGetValue(string key, [MaybeNullWhen(false)] out Type? value) => ((IDictionary<string, Type?>)_backingDictionary).TryGetValue(key, out value);
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_backingDictionary).GetEnumerator();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -371,15 +371,10 @@ internal XsltOutput Output

_scriptExtensions = new Hashtable(_stylesheet.ScriptObjectTypes.Count);
{
foreach (DictionaryEntry entry in _stylesheet.ScriptObjectTypes)
// Scripts are not supported on stylesheets
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a test for this or something? Are we concerned about the behavior change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a functional change as far as I can tell - we should have the proper tests and get it reviewed by the area owners.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, @krwq AFAICT Script objects on xsls are not supported at all in .NET Core. I tried with several different approaches trying to create an xsl that had scripts and all entrypoints that I found trhough XslCompiledTransform would all eventually throw PNSE. I think that this has been dead code that we just originally imported from .NET Framework, but never really did anything as it throws PNSE when you actually invoke the transform operation.

Copy link
Member

@krwq krwq Mar 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC that's correct and intentional. @joperezr you can double check by creating an xslt which works on full fx and make sure it throws on 5.0 - check for all types of scripts.

if (_stylesheet.ScriptObjectTypes.Count > 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be a good idea to also simplify parsing this to save even more bytes

{
string namespaceUri = (string)entry.Key;
if (GetExtensionObject(namespaceUri) != null)
{
throw XsltException.Create(SR.Xslt_ScriptDub, namespaceUri);
}
_scriptExtensions.Add(namespaceUri, Activator.CreateInstance((Type)entry.Value!,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, null, null));
throw new PlatformNotSupportedException(SR.CompilingScriptsNotSupported);
Copy link
Member

@eerhardt eerhardt Mar 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth/possible to add a test here? I'm not asking for a bunch of work, but if it is reasonable (say an hour) it might be nice to ensure this scenario throws PNSE correctly. #Resolved

Copy link
Member Author

@joperezr joperezr Mar 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I can add a unit test for this. This is actually addressing a warning on the old xslt which is not currently covered by this PR (except for this one) but I can add a test here that ensures that using scripts on old xslt throws PNSE at the compile stage which is earlier than this code comes into play. #Resolved

}
}

Expand Down