diff --git a/src/linker/Linker.Steps/BodySubstituterStep.cs b/src/linker/Linker.Steps/BodySubstituterStep.cs new file mode 100644 index 000000000000..f6d353c41fc8 --- /dev/null +++ b/src/linker/Linker.Steps/BodySubstituterStep.cs @@ -0,0 +1,258 @@ +using System; +using System.IO; +using System.Xml.XPath; +using Mono.Cecil; +using System.Globalization; + +namespace Mono.Linker.Steps +{ + public class BodySubstituterStep : BaseStep + { + protected override void Process () + { + var files = Context.Substitutions; + if (files == null) + return; + + foreach (var file in files) { + try { + ReadSubstitutionFile (GetSubstitutions (file)); + } catch (Exception ex) when (!(ex is XmlResolutionException)) { + throw new XmlResolutionException ($"Failed to process XML substitution '{file}'", ex); + } + + } + } + + static XPathDocument GetSubstitutions (string substitutionsFile) + { + using (FileStream fs = File.OpenRead (substitutionsFile)) { + return GetSubstitutions (fs); + } + } + + static XPathDocument GetSubstitutions (Stream substitutions) + { + using (StreamReader sr = new StreamReader (substitutions)) { + return new XPathDocument (sr); + } + } + + void ReadSubstitutionFile (XPathDocument document) + { + XPathNavigator nav = document.CreateNavigator (); + + // Initial structure check + if (!nav.MoveToChild ("linker", "")) + return; + + // TODO: Add handling for feature + + ProcessAssemblies (nav.SelectChildren ("assembly", "")); + } + + void ProcessAssemblies (XPathNodeIterator iterator) + { + while (iterator.MoveNext ()) { + var name = GetAssemblyName (iterator.Current); + + var cache = Context.Resolver.AssemblyCache; + + if (!cache.TryGetValue (name.Name, out AssemblyDefinition assembly)) { + Context.LogMessage (MessageImportance.Low, $"Could not match assembly '{name.FullName}' for substitution"); + continue; + } + + ProcessAssembly (assembly, iterator); + } + } + + void ProcessAssembly (AssemblyDefinition assembly, XPathNodeIterator iterator) + { + ProcessTypes (assembly, iterator.Current.SelectChildren ("type", "")); + } + + void ProcessTypes (AssemblyDefinition assembly, XPathNodeIterator iterator) + { + while (iterator.MoveNext ()) { + XPathNavigator nav = iterator.Current; + + string fullname = GetAttribute (nav, "fullname"); + + TypeDefinition type = assembly.MainModule.GetType (fullname); + + if (type == null) { + Context.LogMessage (MessageImportance.Low, $"Could not resolve type '{fullname}' for substitution"); + continue; + } + + ProcessType (type, nav); + } + } + + void ProcessType (TypeDefinition type, XPathNavigator nav) + { + if (!nav.HasChildren) + return; + + XPathNodeIterator methods = nav.SelectChildren ("method", ""); + if (methods.Count == 0) + return; + + ProcessMethods (type, methods); + } + + void ProcessMethods (TypeDefinition type, XPathNodeIterator iterator) + { + while (iterator.MoveNext ()) + ProcessMethod (type, iterator); + } + + void ProcessMethod (TypeDefinition type, XPathNodeIterator iterator) + { + string signature = GetAttribute (iterator.Current, "signature"); + if (string.IsNullOrEmpty (signature)) + return; + + MethodDefinition method = FindMethod (type, signature); + if (method == null) { + Context.LogMessage (MessageImportance.Low, $"Could not find method '{signature}' for substitution"); + return; + } + + string action = GetAttribute (iterator.Current, "body"); + switch (action) { + case "remove": + Annotations.SetAction (method, MethodAction.ConvertToThrow); + return; + case "stub": + string value = GetAttribute (iterator.Current, "value"); + if (value != "") { + if (!TryConvertValue (value, method.ReturnType, out object res)) { + Context.LogMessage (MessageImportance.High, $"Invalid value for '{signature}' stub"); + return; + } + + Annotations.SetMethodStubValue (method, res); + } + + Annotations.SetAction (method, MethodAction.ConvertToStub); + return; + default: + Context.LogMessage (MessageImportance.High, $"Unknown body modification '{action}' for '{signature}'"); + return; + } + } + + static bool TryConvertValue (string value, TypeReference target, out object result) + { + switch (target.MetadataType) { + case MetadataType.Boolean: + if (bool.TryParse (value, out bool bvalue)) { + result = bvalue ? 1 : 0; + return true; + } + + goto case MetadataType.Int32; + + case MetadataType.Byte: + if (!byte.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out byte byteresult)) + break; + + result = (int) byteresult; + return true; + + case MetadataType.SByte: + if (!sbyte.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out sbyte sbyteresult)) + break; + + result = (int) sbyteresult; + return true; + + case MetadataType.Int16: + if (!short.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out short shortresult)) + break; + + result = (int) shortresult; + return true; + + case MetadataType.UInt16: + if (!ushort.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out ushort ushortresult)) + break; + + result = (int) ushortresult; + return true; + + case MetadataType.Int32: + case MetadataType.UInt32: + if (!int.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int iresult)) + break; + + result = iresult; + return true; + + case MetadataType.Double: + if (!double.TryParse (value, NumberStyles.Float, CultureInfo.InvariantCulture, out double dresult)) + break; + + result = dresult; + return true; + + case MetadataType.Single: + if (!float.TryParse (value, NumberStyles.Float, CultureInfo.InvariantCulture, out float fresult)) + break; + + result = fresult; + return true; + + case MetadataType.Int64: + case MetadataType.UInt64: + if (!long.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out long lresult)) + break; + + result = lresult; + return true; + + case MetadataType.Char: + if (!char.TryParse (value, out char chresult)) + break; + + result = (int) chresult; + return true; + + case MetadataType.String: + if (value is string || value == null) { + result = value; + return true; + } + + break; + } + + result = null; + return false; + } + + static MethodDefinition FindMethod (TypeDefinition type, string signature) + { + if (!type.HasMethods) + return null; + + foreach (MethodDefinition meth in type.Methods) + if (signature == ResolveFromXmlStep.GetMethodSignature (meth, includeGenericParameters: true)) + return meth; + + return null; + } + + static AssemblyNameReference GetAssemblyName (XPathNavigator nav) + { + return AssemblyNameReference.Parse (GetAttribute (nav, "fullname")); + } + + static string GetAttribute (XPathNavigator nav, string attribute) + { + return nav.GetAttribute (attribute, ""); + } + } +} diff --git a/src/linker/Linker.Steps/CodeRewriterStep.cs b/src/linker/Linker.Steps/CodeRewriterStep.cs index 5a94f7f13aff..404b7d4842b8 100644 --- a/src/linker/Linker.Steps/CodeRewriterStep.cs +++ b/src/linker/Linker.Steps/CodeRewriterStep.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -39,9 +40,6 @@ void ProcessMethod (MethodDefinition method) case MethodAction.ConvertToThrow: RewriteBodyToLinkedAway (method); break; - case MethodAction.ConvertToFalse: - RewriteBodyToFalse (method); - break; } } @@ -65,28 +63,28 @@ protected virtual void RewriteBodyToStub (MethodDefinition method) method.ClearDebugInformation(); } - protected virtual void RewriteBodyToFalse (MethodDefinition method) - { - if (!method.IsIL) - throw new NotImplementedException (); - - method.Body = CreateReturnFalseBody (method); - - method.ClearDebugInformation(); - } - MethodBody CreateThrowLinkedAwayBody (MethodDefinition method) { var body = new MethodBody (method); var il = body.GetILProcessor (); + MethodReference ctor; + + // Makes the body verifiable + if (method.IsConstructor && !method.DeclaringType.IsValueType) { + ctor = assembly.MainModule.ImportReference (Context.MarkedKnownMembers.ObjectCtor); + + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Call, ctor); + } // import the method into the current assembly - MethodReference ctor = Context.MarkedKnownMembers.NotSupportedExceptionCtorString; + ctor = Context.MarkedKnownMembers.NotSupportedExceptionCtorString; ctor = assembly.MainModule.ImportReference (ctor); il.Emit (OpCodes.Ldstr, "Linked away"); il.Emit (OpCodes.Newobj, ctor); il.Emit (OpCodes.Throw); + return body; } @@ -98,8 +96,11 @@ MethodBody CreateStubBody (MethodDefinition method) throw new NotImplementedException (); var il = body.GetILProcessor (); - if (method.IsInstanceConstructor ()) { + if (method.IsInstanceConstructor () && !method.DeclaringType.IsValueType) { var base_ctor = method.DeclaringType.BaseType.GetDefaultInstanceConstructor(); + if (base_ctor == null) + throw new NotSupportedException ($"Cannot replace constructor for '{method.DeclaringType}' when no base default constructor exists"); + base_ctor = assembly.MainModule.ImportReference (base_ctor); il.Emit (OpCodes.Ldarg_0); @@ -109,27 +110,90 @@ MethodBody CreateStubBody (MethodDefinition method) switch (method.ReturnType.MetadataType) { case MetadataType.Void: break; - case MetadataType.Boolean: - il.Emit (OpCodes.Ldc_I4_0); - break; default: - throw new NotImplementedException (method.ReturnType.FullName); + var instruction = StubMethodWithConstant (Context, method); + if (instruction != null) { + il.Append (instruction); + } else { + StubComplexBody (method, body); + } + break; } il.Emit (OpCodes.Ret); return body; } - MethodBody CreateReturnFalseBody (MethodDefinition method) + static void StubComplexBody (MethodDefinition method, MethodBody body) { - if (method.ReturnType.MetadataType != MetadataType.Boolean) - throw new NotImplementedException (); + switch (method.ReturnType.MetadataType) { + case MetadataType.MVar: + case MetadataType.ValueType: + var vd = new VariableDefinition (method.ReturnType); + body.Variables.Add (vd); + body.InitLocals = true; + + var il = body.GetILProcessor (); + il.Emit (OpCodes.Ldloca_S, vd); + il.Emit (OpCodes.Initobj, method.ReturnType); + il.Emit (OpCodes.Ldloc_0); + return; + } - var body = new MethodBody (method); - var il = body.GetILProcessor (); - il.Emit (OpCodes.Ldc_I4_0); - il.Emit (OpCodes.Ret); - return body; + throw new NotImplementedException (method.FullName); + } + + public static Instruction StubMethodWithConstant (LinkContext context, MethodDefinition method) + { + context.Annotations.TryGetMethodStubValue (method, out object value); + + switch (method.ReturnType.MetadataType) { + case MetadataType.Boolean: + if (value is int bintValue && bintValue == 1) + return Instruction.Create (OpCodes.Ldc_I4_1); + + return Instruction.Create (OpCodes.Ldc_I4_0); + + case MetadataType.String: + if (value is string svalue) + return Instruction.Create (OpCodes.Ldstr, svalue); + + return Instruction.Create (OpCodes.Ldnull); + + case MetadataType.Object: + case MetadataType.Array: + Debug.Assert (value == null); + return Instruction.Create (OpCodes.Ldnull); + + case MetadataType.Double: + if (value is double dvalue) + return Instruction.Create (OpCodes.Ldc_R8, dvalue); + + return Instruction.Create (OpCodes.Ldc_R8, 0.0); + + case MetadataType.Single: + if (value is float fvalue) + return Instruction.Create (OpCodes.Ldc_R4, fvalue); + + return Instruction.Create (OpCodes.Ldc_R4, 0.0f); + + case MetadataType.Char: + case MetadataType.Byte: + case MetadataType.SByte: + case MetadataType.Int16: + case MetadataType.UInt16: + case MetadataType.Int32: + if (value is int intValue) + return Instruction.Create (OpCodes.Ldc_I4, intValue); + + return Instruction.Create (OpCodes.Ldc_I4_0); + + case MetadataType.UInt64: + case MetadataType.Int64: + return Instruction.Create (OpCodes.Ldc_I8, 0L); + } + + return null; } } } diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index 67418a094908..44d2e9e4cad6 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -2049,6 +2049,18 @@ protected virtual void MarkAndCacheConvertToThrowExceptionCtor () throw new MarkException ($"Could not find constructor on '{nse.FullName}'"); _context.MarkedKnownMembers.NotSupportedExceptionCtorString = nseCtor; + + var objectType = BCL.FindPredefinedType ("System", "Object", _context); + if (objectType == null) + throw new NotSupportedException ("Missing predefined 'System.Object' type"); + + MarkType (objectType); + + var objectCtor = MarkMethodIf (objectType.Methods, MethodDefinitionExtensions.IsDefaultConstructor); + if (objectCtor == null) + throw new MarkException ($"Could not find constructor on '{objectType.FullName}'"); + + _context.MarkedKnownMembers.ObjectCtor = objectCtor; } bool MarkDisablePrivateReflectionAttribute () diff --git a/src/linker/Linker.Steps/RemoveFeaturesStep.cs b/src/linker/Linker.Steps/RemoveFeaturesStep.cs index 1962f38bc7d1..d5967a9d5653 100644 --- a/src/linker/Linker.Steps/RemoveFeaturesStep.cs +++ b/src/linker/Linker.Steps/RemoveFeaturesStep.cs @@ -259,7 +259,7 @@ void ExcludeMonoCollation (TypeDefinition type) { if (method.Name == "get_UseManagedCollation") { - annotations.SetAction(method, MethodAction.ConvertToFalse); + annotations.SetAction(method, MethodAction.ConvertToStub); break; } } diff --git a/src/linker/Linker.Steps/RemoveUnreachableBlocksStep.cs b/src/linker/Linker.Steps/RemoveUnreachableBlocksStep.cs new file mode 100644 index 000000000000..d7c7f1ec55ab --- /dev/null +++ b/src/linker/Linker.Steps/RemoveUnreachableBlocksStep.cs @@ -0,0 +1,904 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Collections; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Collections.Generic; +using System.Collections.Generic; + +namespace Mono.Linker.Steps +{ + // + // This steps evaluates simple properties or methods for constant expressions and + // then uses this information to remove unreachable conditional blocks. It does + // not do any inlining-like code changes. + // + public class RemoveUnreachableBlocksStep : BaseStep + { + Dictionary constExprMethods; + MethodDefinition IntPtrSize, UIntPtrSize; + + protected override void Process () + { + var assemblies = Context.Annotations.GetAssemblies ().ToArray (); + + constExprMethods = new Dictionary (); + foreach (var assembly in assemblies) { + FindConstantExpressionsMethods (assembly.MainModule.Types); + } + + if (constExprMethods.Count == 0) + return; + + int constExprMethodsCount; + do { + // + // Body rewriting can produce more methods with constant expression + // + constExprMethodsCount = constExprMethods.Count; + + foreach (var assembly in assemblies) { + if (Annotations.GetAction (assembly) != AssemblyAction.Link) + continue; + + RewriteBodies (assembly.MainModule.Types); + } + } while (constExprMethodsCount < constExprMethods.Count); + } + + void FindConstantExpressionsMethods (Collection types) + { + foreach (var type in types) { + if (type.IsInterface) + continue; + + if (!type.HasMethods) + continue; + + foreach (var method in type.Methods) { + if (!method.HasBody) + continue; + + if (method.ReturnType.MetadataType == MetadataType.Void) + continue; + + switch (Annotations.GetAction (method)) { + case MethodAction.ConvertToThrow: + continue; + case MethodAction.ConvertToStub: + var instruction = CodeRewriterStep.StubMethodWithConstant (Context, method); + if (instruction != null) + constExprMethods [method] = instruction; + + continue; + } + + if (method.IsIntrinsic ()) + continue; + + if (constExprMethods.ContainsKey (method)) + continue; + + var analyzer = new ConstantExpressionMethodAnalyzer (method); + if (analyzer.Analyze ()) { + constExprMethods [method] = analyzer.Result; + } + } + + if (type.HasNestedTypes) + FindConstantExpressionsMethods (type.NestedTypes); + } + } + + void RewriteBodies (Collection types) + { + foreach (var type in types) { + if (type.IsInterface) + continue; + + if (!type.HasMethods) + continue; + + foreach (var method in type.Methods) { + if (!method.HasBody) + continue; + + RewriteBody (method); + } + + if (type.HasNestedTypes) + RewriteBodies (type.NestedTypes); + } + } + + void RewriteBody (MethodDefinition method) + { + var reducer = new BodyReducer (method.Body); + + // + // Temporary inlines any calls which return contant expression + // + if (!TryInlineBodyDependencies (ref reducer)) + return; + + // + // This is the main step which evaluates if inlined calls can + // produce folded branches. When it finds them the unreachable + // branch is replaced with nops. + // + if (!reducer.RewriteBody ()) + return; + + Context.LogMessage (MessageImportance.Low, $"Reduced '{reducer.InstructionsReplaced}' instructions in conditional branches for [{method.DeclaringType.Module.Assembly.Name}] method {method.FullName}"); + + if (method.ReturnType.MetadataType == MetadataType.Void) + return; + + // + // Re-run the analyzer in case body change rewrote it to constant expression + // + var analyzer = new ConstantExpressionMethodAnalyzer (method, reducer.FoldedInstructions); + if (analyzer.Analyze ()) { + constExprMethods [method] = analyzer.Result; + } + } + + bool TryInlineBodyDependencies (ref BodyReducer reducer) + { + bool changed = false; + var instructions = reducer.Body.Instructions; + Instruction targetResult; + + for (int i = 0; i < instructions.Count; ++i) { + var instr = instructions [i]; + switch (instr.OpCode.Code) { + + case Code.Call: + var target = (MethodReference)instr.Operand; + var md = target.Resolve (); + if (md == null) + break; + + if (!md.IsStatic) + break; + + if (!constExprMethods.TryGetValue (md, out targetResult)) + break; + + if (md.HasParameters) + break; + + reducer.Rewrite (i, targetResult); + changed = true; + break; + + case Code.Sizeof: + // + // sizeof (IntPtr) and sizeof (UIntPtr) are just aliases for IntPtr.Size and UIntPtr.Size + // which are simple static properties commonly overwritten. Instead of forcing C# code style + // we handle both via static Size property + // + MethodDefinition sizeOfImpl = null; + + var operand = (TypeReference) instr.Operand; + if (operand.MetadataType == MetadataType.UIntPtr) { + sizeOfImpl = UIntPtrSize ?? (UIntPtrSize = FindSizeMethod (operand.Resolve ())); + } + + if (operand.MetadataType == MetadataType.IntPtr) { + sizeOfImpl = IntPtrSize ?? (IntPtrSize = FindSizeMethod (operand.Resolve ())); + } + + if (sizeOfImpl != null && constExprMethods.TryGetValue (sizeOfImpl, out targetResult)) { + reducer.Rewrite (i, targetResult); + changed = true; + } + + break; + } + } + + return changed; + } + + static MethodDefinition FindSizeMethod (TypeDefinition type) + { + if (type == null) + return null; + + return type.Methods.First (l => !l.HasParameters && l.IsStatic && l.Name == "get_Size"); + } + + struct BodyReducer + { + Dictionary mapping; + + // + // Sorted list of body instruction indexes which were + // replaced pass-through nop + // + List conditionInstrsToRemove; + + public BodyReducer (MethodBody body) + { + Body = body; + FoldedInstructions = null; + mapping = null; + conditionInstrsToRemove = null; + InstructionsReplaced = 0; + } + + public MethodBody Body { get; } + + public int InstructionsReplaced { get; set; } + + public Collection FoldedInstructions { get; private set; } + + public void Rewrite (int index, Instruction newInstruction) + { + if (FoldedInstructions == null) { + FoldedInstructions = new Collection (Body.Instructions); + mapping = new Dictionary (); + } + + // Tracks mapping for replaced instructions for easier + // branch targets resolution later + mapping [Body.Instructions [index]] = index; + + FoldedInstructions [index] = newInstruction; + } + + void RewriteConditionToNop (int index) + { + if (conditionInstrsToRemove == null) + conditionInstrsToRemove = new List (); + + conditionInstrsToRemove.Add (index); + RewriteToNop (index); + } + + void RewriteToNop (int index) + { + Rewrite (index, Instruction.Create (OpCodes.Nop)); + } + + public bool RewriteBody () + { + if (FoldedInstructions == null) + return false; + + if (!RemoveConditions ()) + return false; + + var reachableMap = GetReachableInstructionsMap (); + if (reachableMap == null) + return false; + + bool changed = false; + var instrs = Body.Instructions; + for (int i = 0; i < instrs.Count; ++i) { + if (reachableMap [i]) + continue; + + if (instrs [i].OpCode.Code == Code.Nop) + continue; + + instrs [i] = Instruction.Create (OpCodes.Nop); + changed = true; + InstructionsReplaced++; + } + + // + // Process list of conditional jump which should be removed. They cannot be + // replaced with nops as they alter the stack + // + if (conditionInstrsToRemove != null) { + int bodyExpansion = 0; + + foreach (int instrIndex in conditionInstrsToRemove) { + var index = instrIndex + bodyExpansion; + var instr = instrs [index]; + + if (instr.OpCode == OpCodes.Nop) + continue; + + switch (instr.OpCode.StackBehaviourPop) { + case StackBehaviour.Pop1_pop1: + + InstructionsReplaced += 2; + + // + // One of the operands is most likely constant and could just be removed instead of additional pop + // + if (index > 0 && IsSideEffectFreeLoad (instrs [index - 1])) { + instrs [index - 1] = Instruction.Create (OpCodes.Pop); + instrs [index] = Instruction.Create (OpCodes.Nop); + } else { + instrs [index] = Instruction.Create (OpCodes.Pop); + instrs.Insert (index, Instruction.Create (OpCodes.Pop)); + + // + // conditionInstrsToRemove is always sorted and instead of + // increasing remaining indexes we introduce index delta value + // + bodyExpansion++; + } + break; + case StackBehaviour.Popi: + instrs [index] = Instruction.Create (OpCodes.Pop); + InstructionsReplaced++; + break; + default: + // Should never be reachedc + throw new NotImplementedException (); + } + } + + changed = true; + } + + return changed; + } + + bool RemoveConditions () + { + bool changed = false; + object left, right; + + // + // Finds any branchable instruction and checks if the operand or operands + // can be evaluated as constant result. + // + // The logic does not remove any instructions but replaces them with nops for + // easier processing later (makes the mapping straigh-forward). + // + for (int i = 0; i < FoldedInstructions.Count; ++i) { + var instr = FoldedInstructions [i]; + var opcode = instr.OpCode; + + if (opcode.FlowControl == FlowControl.Cond_Branch) { + if (opcode.StackBehaviourPop == StackBehaviour.Pop1_pop1) { + if (!GetOperandsConstantValues (i, out left, out right)) + continue; + + if (left is int lint && right is int rint) { + RewriteToNop (i - 2); + RewriteToNop (i - 1); + + if (IsComparisonAlwaysTrue (opcode, lint, rint)) { + Rewrite (i, Instruction.Create (OpCodes.Br, (Instruction)instr.Operand)); + } else { + RewriteConditionToNop (i); + } + + changed = true; + continue; + } + + continue; + } + + if (opcode.StackBehaviourPop == StackBehaviour.Popi) { + if (i > 0 && GetConstantValue (FoldedInstructions [i - 1], out var operand)) { + if (operand is int opint) { + RewriteToNop (i - 1); + + if (IsConstantBranch (opcode, opint)) { + Rewrite (i, Instruction.Create (OpCodes.Br, (Instruction)instr.Operand)); + } else { + RewriteConditionToNop (i); + } + + changed = true; + continue; + } + + if (operand is null && (opcode.Code == Code.Brfalse || opcode.Code == Code.Brfalse_S)) { + RewriteToNop (i - 1); + Rewrite (i, Instruction.Create (OpCodes.Br, (Instruction)instr.Operand)); + changed = true; + continue; + } + } + + // Common pattern generated by C# compiler in debug mode + if (i > 3 && GetConstantValue (FoldedInstructions [i - 3], out operand) && operand is int opint2 && IsPairedStlocLdloc (FoldedInstructions [i - 2], FoldedInstructions [i - 1])) { + RewriteToNop (i - 3); + RewriteToNop (i - 2); + RewriteToNop (i - 1); + + if (IsConstantBranch (opcode, opint2)) { + Rewrite (i, Instruction.Create (OpCodes.Br, (Instruction)instr.Operand)); + } else { + RewriteConditionToNop (i); + } + + changed = true; + continue; + } + + continue; + } + + throw new NotImplementedException (); + } + + // Mode special for csc in debug mode + switch (instr.OpCode.Code) { + case Code.Ceq: + case Code.Clt: + case Code.Cgt: + if (!GetOperandsConstantValues (i, out left, out right)) + continue; + + if (left is int lint && right is int rint) { + RewriteToNop (i - 2); + RewriteToNop (i - 1); + + if (IsComparisonAlwaysTrue (instr.OpCode, lint, rint)) { + Rewrite (i, Instruction.Create (OpCodes.Ldc_I4_1)); + } else { + Rewrite (i, Instruction.Create (OpCodes.Ldc_I4_0)); + } + + changed = true; + } + + break; + + case Code.Cgt_Un: + if (!GetOperandsConstantValues (i, out left, out right)) + continue; + + if (left == null && right == null) { + Rewrite (i, Instruction.Create (OpCodes.Ldc_I4_0)); + } + + changed = true; + break; + } + } + + return changed; + } + + BitArray GetReachableInstructionsMap () + { + var reachable = new BitArray (FoldedInstructions.Count); + + Stack conditional = null; + + // + // Mark all handlers all the time as we are not interested in optimizing handlers + // + if (Body.HasExceptionHandlers) { + conditional = new Stack (); + foreach (var handler in Body.ExceptionHandlers) { + conditional.Push (GetInstructionIndex (handler.HandlerStart)); + + if (handler.FilterStart != null) + conditional.Push (GetInstructionIndex (handler.FilterStart)); + } + } + + Instruction target; + int i = 0; + while (true) { + while (i < FoldedInstructions.Count) { + if (reachable [i]) + break; + + reachable [i] = true; + var instr = FoldedInstructions [i++]; + + switch (instr.OpCode.FlowControl) { + case FlowControl.Branch: + target = (Instruction)instr.Operand; + i = GetInstructionIndex (target); + continue; + + case FlowControl.Cond_Branch: + if (conditional == null) + conditional = new Stack (); + + switch (instr.Operand) { + case Instruction starget: + conditional.Push (GetInstructionIndex (starget)); + continue; + case Instruction[] mtargets: + foreach (var t in mtargets) + conditional.Push (GetInstructionIndex (t)); + continue; + default: + throw new NotImplementedException (); + } + + case FlowControl.Next: + case FlowControl.Call: + case FlowControl.Meta: + continue; + + case FlowControl.Return: + case FlowControl.Throw: + break; + + default: + throw new NotImplementedException (); + } + + break; + } + + if (conditional?.Count > 0) { + i = conditional.Pop (); + continue; + } + + return reachable; + } + } + + // + // Returns index of instruction in folded instruction body + // + int GetInstructionIndex (Instruction instruction) + { + int idx; + if (mapping.TryGetValue (instruction, out idx)) + return idx; + + idx = FoldedInstructions.IndexOf (instruction); + Debug.Assert (idx >= 0); + return idx; + } + + bool GetOperandsConstantValues (int index, out object left, out object right) + { + left = default; + right = default; + + if (index < 2) + return false; + + return GetConstantValue (FoldedInstructions [index - 2], out left) && + GetConstantValue (FoldedInstructions [index - 1], out right); + } + + static bool GetConstantValue (Instruction instruction, out object value) + { + switch (instruction.OpCode.Code) { + case Code.Ldc_I4_0: + value = 0; + return true; + case Code.Ldc_I4_1: + value = 1; + return true; + case Code.Ldc_I4_2: + value = 2; + return true; + case Code.Ldc_I4_3: + value = 3; + return true; + case Code.Ldc_I4_4: + value = 4; + return true; + case Code.Ldc_I4_5: + value = 5; + return true; + case Code.Ldc_I4_6: + value = 6; + return true; + case Code.Ldc_I4_7: + value = 7; + return true; + case Code.Ldc_I4_8: + value = 8; + return true; + case Code.Ldc_I4_M1: + value = -1; + return true; + case Code.Ldc_I4: + value = (int)instruction.Operand; + return true; + case Code.Ldc_I4_S: + value = (int)(sbyte)instruction.Operand; + return true; + case Code.Ldc_I8: + value = (long)instruction.Operand; + return true; + case Code.Ldnull: + value = null; + return true; + default: + value = null; + return false; + } + } + + static bool IsPairedStlocLdloc (Instruction first, Instruction second) + { + switch (first.OpCode.Code) { + case Code.Stloc_0: + return second.OpCode.Code == Code.Ldloc_0; + case Code.Stloc_1: + return second.OpCode.Code == Code.Ldloc_1; + case Code.Stloc_2: + return second.OpCode.Code == Code.Ldloc_2; + case Code.Stloc_3: + return second.OpCode.Code == Code.Ldloc_3; + case Code.Stloc_S: + case Code.Stloc: + if (second.OpCode.Code == Code.Ldloc_S || second.OpCode.Code == Code.Ldloc) + return ((VariableDefinition)first.Operand).Index == ((VariableDefinition)second.Operand).Index; + + break; + } + + return false; + } + + static bool IsSideEffectFreeLoad (Instruction instr) + { + switch (instr.OpCode.Code) { + case Code.Ldarg: + case Code.Ldloc: + case Code.Ldloc_0: + case Code.Ldloc_1: + case Code.Ldloc_2: + case Code.Ldloc_3: + case Code.Ldloc_S: + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4: + case Code.Ldc_I4_S: + case Code.Ldc_I4_M1: + case Code.Ldc_I8: + case Code.Ldc_R4: + case Code.Ldc_R8: + case Code.Ldnull: + case Code.Ldstr: + return true; + } + + return false; + } + + static bool IsComparisonAlwaysTrue (OpCode opCode, int left, int right) + { + switch (opCode.Code) { + case Code.Beq: + case Code.Beq_S: + case Code.Ceq: + return left == right; + case Code.Bne_Un: + case Code.Bne_Un_S: + return left != right; + case Code.Bge: + case Code.Bge_S: + case Code.Bge_Un: + case Code.Bge_Un_S: + return left >= right; + case Code.Bgt: + case Code.Bgt_S: + case Code.Bgt_Un: + case Code.Bgt_Un_S: + case Code.Cgt: + return left > right; + case Code.Ble: + case Code.Ble_S: + case Code.Ble_Un: + case Code.Ble_Un_S: + return left <= right; + case Code.Blt: + case Code.Blt_S: + case Code.Blt_Un: + case Code.Blt_Un_S: + case Code.Clt: + return left < right; + } + + throw new NotImplementedException (opCode.ToString ()); + } + + static bool IsConstantBranch (OpCode opCode, int operand) + { + switch (opCode.Code) { + case Code.Brfalse: + case Code.Brfalse_S: + return operand == 0; + case Code.Brtrue: + case Code.Brtrue_S: + return operand != 0; + } + + throw new NotImplementedException (opCode.ToString ()); + } + } + + struct ConstantExpressionMethodAnalyzer + { + readonly MethodDefinition method; + readonly Collection instructions; + + Stack stack_instr; + Dictionary locals; + + public ConstantExpressionMethodAnalyzer (MethodDefinition method) + { + this.method = method; + instructions = method.Body.Instructions; + stack_instr = null; + locals = null; + Result = null; + } + + public ConstantExpressionMethodAnalyzer (MethodDefinition method, Collection instructions) + { + this.method = method; + this.instructions = instructions; + stack_instr = null; + locals = null; + Result = null; + } + + public Instruction Result { get; private set; } + + public bool Analyze () + { + var body = method.Body; + if (body.HasExceptionHandlers) + return false; + + VariableReference vr; + Instruction jmpTarget = null; + + foreach (var instr in instructions) { + if (jmpTarget != null) { + if (instr != jmpTarget) + continue; + + jmpTarget = null; + } + + switch (instr.OpCode.Code) { + case Code.Nop: + continue; + case Code.Pop: + stack_instr.Pop (); + continue; + + case Code.Br_S: + case Code.Br: + jmpTarget = (Instruction)instr.Operand; + continue; + + case Code.Ldc_I4: + case Code.Ldc_I4_S: + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4_M1: + case Code.Ldc_I8: + case Code.Ldnull: + case Code.Ldstr: + case Code.Ldtoken: + PushOnStack (instr); + continue; + + case Code.Ldloc_0: + PushOnStack (locals [0]); + continue; + case Code.Ldloc_1: + PushOnStack (locals [1]); + continue; + case Code.Ldloc_2: + PushOnStack (locals [2]); + continue; + case Code.Ldloc_3: + PushOnStack (locals [3]); + continue; + case Code.Ldloc: + case Code.Ldloc_S: + vr = (VariableReference)instr.Operand; + PushOnStack (locals [vr.Index]); + continue; + + case Code.Stloc_0: + StoreToLocals (0); + continue; + case Code.Stloc_1: + StoreToLocals (1); + continue; + case Code.Stloc_2: + StoreToLocals (2); + continue; + case Code.Stloc_3: + StoreToLocals (3); + continue; + case Code.Stloc_S: + case Code.Stloc: + vr = (VariableReference)instr.Operand; + StoreToLocals (vr.Index); + continue; + + // TODO: handle simple conversions + //case Code.Conv_I: + + case Code.Ret: + if (ConvertStackToResult ()) + return true; + + break; + } + + return false; + } + + return false; + } + + bool ConvertStackToResult () + { + if (stack_instr == null) + return false; + + if (stack_instr.Count != 1) + return false; + + var instr = stack_instr.Pop (); + + switch (instr.OpCode.Code) { + case Code.Ldc_I4_0: + case Code.Ldc_I4_1: + case Code.Ldc_I4_2: + case Code.Ldc_I4_3: + case Code.Ldc_I4_4: + case Code.Ldc_I4_5: + case Code.Ldc_I4_6: + case Code.Ldc_I4_7: + case Code.Ldc_I4_8: + case Code.Ldc_I4: + case Code.Ldc_I4_S: + case Code.Ldc_I4_M1: + case Code.Ldc_I8: + case Code.Ldnull: + case Code.Ldstr: + Result = instr; + return true; + } + + return false; + } + + void PushOnStack (Instruction instruction) + { + if (stack_instr == null) + stack_instr = new Stack (); + + stack_instr.Push (instruction); + } + + void StoreToLocals (int index) + { + if (locals == null) + locals = new Dictionary (); + + locals [index] = stack_instr.Pop (); + } + } + } +} diff --git a/src/linker/Linker.Steps/ResolveFromXmlStep.cs b/src/linker/Linker.Steps/ResolveFromXmlStep.cs index 83c72205d481..bb4febed88ba 100644 --- a/src/linker/Linker.Steps/ResolveFromXmlStep.cs +++ b/src/linker/Linker.Steps/ResolveFromXmlStep.cs @@ -462,18 +462,23 @@ protected static MethodDefinition GetMethod (TypeDefinition type, string signatu { if (type.HasMethods) foreach (MethodDefinition meth in type.Methods) - if (signature == GetMethodSignature (meth)) + if (signature == GetMethodSignature (meth, false)) return meth; return null; } - static string GetMethodSignature (MethodDefinition meth) + public static string GetMethodSignature (MethodDefinition meth, bool includeGenericParameters) { StringBuilder sb = new StringBuilder (); sb.Append (meth.ReturnType.FullName); sb.Append (" "); sb.Append (meth.Name); + if (includeGenericParameters && meth.HasGenericParameters) { + sb.Append ("`"); + sb.Append (meth.GenericParameters.Count); + } + sb.Append ("("); if (meth.HasParameters) { for (int i = 0; i < meth.Parameters.Count; i++) { diff --git a/src/linker/Linker/Annotations.cs b/src/linker/Linker/Annotations.cs index 8530ab04f887..0c8ab85b40d4 100644 --- a/src/linker/Linker/Annotations.cs +++ b/src/linker/Linker/Annotations.cs @@ -39,6 +39,7 @@ public partial class AnnotationStore { protected readonly Dictionary assembly_actions = new Dictionary (); protected readonly Dictionary method_actions = new Dictionary (); + protected readonly Dictionary method_stub_values = new Dictionary (); protected readonly HashSet marked = new HashSet (); protected readonly HashSet processed = new HashSet (); protected readonly Dictionary preserved_types = new Dictionary (); @@ -119,6 +120,11 @@ public void SetAction (MethodDefinition method, MethodAction action) method_actions [method] = action; } + public void SetMethodStubValue (MethodDefinition method, object value) + { + method_stub_values [method] = value; + } + public void Mark (IMetadataTokenProvider provider) { marked.Add (provider); @@ -233,6 +239,11 @@ public bool TryGetPreserve (TypeDefinition type, out TypePreserve preserve) return preserved_types.TryGetValue (type, out preserve); } + public bool TryGetMethodStubValue (MethodDefinition method, out object value) + { + return method_stub_values.TryGetValue (method, out value); + } + public HashSet GetResourcesToRemove (AssemblyDefinition assembly) { HashSet resources; diff --git a/src/linker/Linker/Driver.cs b/src/linker/Linker/Driver.cs index 8990ee583145..e1cf45a38372 100644 --- a/src/linker/Linker/Driver.cs +++ b/src/linker/Linker/Driver.cs @@ -211,6 +211,10 @@ public void Run (ILogger customLogger = null) context.StripResources = bool.Parse (GetParam ()); continue; + case "--substitutions": + context.AddSubstitutionFile (GetParam ()); + continue; + case "--exclude-feature": var name = GetParam (); foreach (var feature in name.Split (',')) { @@ -377,6 +381,8 @@ public void Run (ILogger customLogger = null) p.AddStepAfter (typeof (PreserveDependencyLookupStep), new PreserveCalendarsStep (assemblies)); } + p.AddStepBefore (typeof (MarkStep), new BodySubstituterStep ()); + if (removeCAS) p.AddStepBefore (typeof (MarkStep), new RemoveSecurityStep ()); @@ -394,6 +400,8 @@ public void Run (ILogger customLogger = null) context.ExcludedFeatures = excluded; } + p.AddStepBefore (typeof (MarkStep), new RemoveUnreachableBlocksStep ()); + if (disabled_optimizations.Count > 0) { foreach (var item in disabled_optimizations) { switch (item) { @@ -605,7 +613,8 @@ static void Usage (string msg) Console.WriteLine (" --keep-facades Keep assemblies with type-forwarders (short -t). Defaults to false"); Console.WriteLine (" --keep-dep-attributes Keep attributes used for manual dependency tracking. Defaults to false"); Console.WriteLine (" --new-mvid Generate a new guid for each linked assembly (short -g). Defaults to true"); - Console.WriteLine (" --skip-unresolved Ignore unresolved types, methods, and assemblies. Defaults to false"); + Console.WriteLine (" --skip-unresolved Ignore unresolved types, methods, and assemblies. Defaults to false"); + Console.WriteLine (" --substitutions Configuration file with methods substitution rules"); Console.WriteLine (" --strip-resources Remove XML descriptor resources for linked assemblies. Defaults to true"); Console.WriteLine (" --strip-security Remove metadata and code related to Code Access Security. Defaults to true"); Console.WriteLine (" --used-attrs-only Any attribute is removed if the attribute type is not used. Defaults to false"); diff --git a/src/linker/Linker/KnownMembers.cs b/src/linker/Linker/KnownMembers.cs index f1e204bb0537..02dcf6469ad4 100644 --- a/src/linker/Linker/KnownMembers.cs +++ b/src/linker/Linker/KnownMembers.cs @@ -6,6 +6,7 @@ public class KnownMembers { public MethodDefinition NotSupportedExceptionCtorString { get; set; } public MethodDefinition DisablePrivateReflectionAttributeCtor { get; set; } + public MethodDefinition ObjectCtor { get; set; } public static bool IsNotSupportedExceptionCtorString (MethodDefinition method) { diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index 2801c96573e5..a94a42c4c331 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -117,6 +117,8 @@ public bool IgnoreUnresolved public bool StripResources { get; set; } + public List Substitutions { get; private set; } + public System.Collections.IDictionary Actions { get { return _actions; } } @@ -195,6 +197,20 @@ public LinkContext (Pipeline pipeline, AssemblyResolver resolver, ReaderParamete DisabledOptimizations |= CodeOptimizations.ClearInitLocals; } + public void AddSubstitutionFile (string file) + { + if (Substitutions == null) { + Substitutions = new List (); + Substitutions.Add (file); + return; + } + + if (Substitutions.Contains (file)) + return; + + Substitutions.Add (file); + } + public TypeDefinition GetType (string fullName) { int pos = fullName.IndexOf (","); diff --git a/src/linker/Linker/MethodAction.cs b/src/linker/Linker/MethodAction.cs index babe6d2c8a72..a3550e78396b 100644 --- a/src/linker/Linker/MethodAction.cs +++ b/src/linker/Linker/MethodAction.cs @@ -34,6 +34,5 @@ public enum MethodAction { ForceParse, ConvertToStub, ConvertToThrow, - ConvertToFalse } } diff --git a/src/linker/Linker/MethodDefinitionExtensions.cs b/src/linker/Linker/MethodDefinitionExtensions.cs index 078ce0136683..32f2a17a2530 100644 --- a/src/linker/Linker/MethodDefinitionExtensions.cs +++ b/src/linker/Linker/MethodDefinitionExtensions.cs @@ -14,6 +14,20 @@ public static bool IsInstanceConstructor (this MethodDefinition method) return method.IsConstructor && !method.IsStatic; } + public static bool IsIntrinsic (this MethodDefinition method) + { + if (!method.HasCustomAttributes) + return false; + + foreach (var ca in method.CustomAttributes) { + var caType = ca.AttributeType; + if (caType.Name == "IntrinsicAttribute" && caType.Namespace == "System.Runtime.CompilerServices") + return true; + } + + return false; + } + public static bool IsStaticConstructor (this MethodDefinition method) { return method.IsConstructor && method.IsStatic; diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupLinkerSubstitutionFileAttribute.cs b/test/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupLinkerSubstitutionFileAttribute.cs new file mode 100644 index 000000000000..6a52e50214bd --- /dev/null +++ b/test/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupLinkerSubstitutionFileAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Metadata { + [AttributeUsage (AttributeTargets.Class, AllowMultiple = true)] + public class SetupLinkerSubstitutionFileAttribute : BaseMetadataAttribute { + public SetupLinkerSubstitutionFileAttribute (string relativePathToFile, string destinationFileName = null) + { + if (string.IsNullOrEmpty (relativePathToFile)) + throw new ArgumentException ("Value cannot be null or empty.", nameof (relativePathToFile)); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/RemoveBody.cs b/test/Mono.Linker.Tests.Cases/Substitutions/RemoveBody.cs new file mode 100644 index 000000000000..cd9f5285cdd5 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/RemoveBody.cs @@ -0,0 +1,65 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions +{ + [SetupLinkerSubstitutionFile ("RemoveBody.xml")] + public class RemoveBody + { + public static void Main () + { + new RemoveBody (); + new NestedType (5); + + TestMethod_1 (); + TestMethod_2 (); + } + + struct NestedType + { + [Kept] + [ExpectedInstructionSequence (new [] { + "ldstr", + "newobj", + "throw" + })] + public NestedType (int arg) + { + } + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldarg.0", + "call", + "ldstr", + "newobj", + "throw" + })] + public RemoveBody () + { + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldstr", + "newobj", + "throw" + })] + static void TestMethod_1 () + { + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldstr", + "newobj", + "throw" + })] + [ExpectLocalsModified] + static T TestMethod_2 () + { + return default (T); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/RemoveBody.xml b/test/Mono.Linker.Tests.Cases/Substitutions/RemoveBody.xml new file mode 100644 index 000000000000..cac95acfaa07 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/RemoveBody.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.cs b/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.cs new file mode 100644 index 000000000000..a685ed32b945 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.cs @@ -0,0 +1,178 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions { + [SetupLinkerSubstitutionFile ("StubBody.xml")] + public class StubBody { + public static void Main () + { + new StubBody (); + new NestedType (5); + + TestMethod_1 (); + TestMethod_2 (); + TestMethod_3 (); + TestMethod_4 (); + TestMethod_5 (); + TestMethod_6 (); + TestMethod_7 (); + TestMethod_8 (5); + TestMethod_9 (); + TestMethod_10 (); + TestMethod_11 (); + TestMethod_12 (); + } + + struct NestedType { + [Kept] + [ExpectedInstructionSequence (new [] { + "ret", + })] + public NestedType (int arg) + { + throw new NotImplementedException (); + } + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldarg.0", + "call", + "ret", + })] + public StubBody () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldnull", + "ret", + })] + static string TestMethod_1 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.0", + "ret", + })] + static byte TestMethod_2 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.0", + "ret", + })] + static char TestMethod_3 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldloca.s", + "initobj", + "ldloc.0", + "ret" + })] + [ExpectLocalsModified] + static decimal TestMethod_4 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.0", + "ret", + })] + static bool TestMethod_5 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ret", + })] + static void TestMethod_6 () + { + TestMethod_5 (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.r8", + "ret", + })] + [ExpectLocalsModified] + static double TestMethod_7 () + { + double d = 1.1; + return d; + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldloca.s", + "initobj", + "ldloc.0", + "ret" + })] + [ExpectLocalsModified] + static T TestMethod_8 (T t) + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.r4", + "ret", + })] + [ExpectLocalsModified] + static float TestMethod_9 () + { + float f = 1.1f; + return f; + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i8", + "ret", + })] + static ulong TestMethod_10 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldnull", + "ret", + })] + static long [] TestMethod_11 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldnull", + "ret", + })] + static object TestMethod_12 () + { + throw new NotImplementedException (); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.xml b/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.xml new file mode 100644 index 000000000000..dde512798e88 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/StubBody.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyInvalidSyntax.cs b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyInvalidSyntax.cs new file mode 100644 index 000000000000..a5c302d10ecd --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyInvalidSyntax.cs @@ -0,0 +1,106 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions +{ + [SetupLinkerSubstitutionFile ("StubBodyInvalidSyntax.xml")] + public class StubBodyInvalidSyntax + { + public static void Main() + { + new StubBodyInvalidSyntax (); + + TestMethod_1 (); + TestMethod_2 (); + TestMethod_3 (); + TestMethod_4 (); + TestMethod_5 (); + TestMethod_6 (); + TestMethod_7 (); + TestMethod_8 (5); + TestMethod_9 (); + TestMethod_10 (); + TestMethod_11 (); + TestMethod_12 (); + } + + [Kept] + public StubBodyInvalidSyntax() + { + throw new NotImplementedException (); + } + + [Kept] + static sbyte TestMethod_1 () + { + throw new NotImplementedException (); + } + + [Kept] + static byte TestMethod_2 () + { + return 3; + } + + [Kept] + static char TestMethod_3 () + { + return 'a'; + } + + [Kept] + static decimal TestMethod_4 () + { + return 9.2m; + } + + [Kept] + static bool TestMethod_5() + { + return true; + } + + [Kept] + static void TestMethod_6() + { + TestMethod_5 (); + } + + [Kept] + static double TestMethod_7() + { + throw new NotImplementedException (); + } + + [Kept] + static int TestMethod_8 (T t) + { + throw new NotImplementedException (); + } + + [Kept] + static float TestMethod_9() + { + throw new NotImplementedException (); + } + + [Kept] + static ulong TestMethod_10() + { + throw new NotImplementedException (); + } + + [Kept] + static long[] TestMethod_11() + { + throw new NotImplementedException (); + } + + [Kept] + static object TestMethod_12() + { + throw new NotImplementedException (); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyInvalidSyntax.xml b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyInvalidSyntax.xml new file mode 100644 index 000000000000..332ed698a380 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyInvalidSyntax.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.cs b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.cs new file mode 100644 index 000000000000..40ec1b7c43b9 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.cs @@ -0,0 +1,124 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions +{ + [SetupLinkerSubstitutionFile ("StubBodyWithValue.xml")] + public class StubBodyWithValue + { + public static void Main() + { + TestMethod_1 (); + TestMethod_2 (); + TestMethod_3 (); + TestMethod_4 (); + TestMethod_5 (); + TestMethod_6 (); + TestMethod_7 (); + TestMethod_8 (); + TestMethod_9 (); + TestMethod_10 (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldstr", + "ret", + })] + static string TestMethod_1 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4", + "ret", + })] + static byte TestMethod_2 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4", + "ret", + })] + static char TestMethod_3 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4", + "ret" + })] + static sbyte TestMethod_4 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.1", + "ret", + })] + static bool TestMethod_5() + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.1", + "ret", + })] + static bool TestMethod_6() + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.r8", + "ret", + })] + static double TestMethod_7() + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4", + "ret" + })] + static int TestMethod_8 () + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.r4", + "ret", + })] + static float TestMethod_9() + { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i8", + "ret", + })] + static ulong TestMethod_10() + { + throw new NotImplementedException (); + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.xml b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.xml new file mode 100644 index 000000000000..36191c79d66d --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/StubBodyWithValue.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs new file mode 100644 index 000000000000..0dd3e9c263a5 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.cs @@ -0,0 +1,39 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.UnreachableBlock +{ + [SetupLinkerSubstitutionFile ("BodiesWithSubstitutions.xml")] + [SetupCSharpCompilerToUse ("csc")] + public class BodiesWithSubstitutions + { + static int field; + + public static void Main() + { + TestProperty_int_1 (); + } + + [Kept] + [ExpectBodyModified] + static void TestProperty_int_1 () + { + if (Property != 3) + NeverReached_1 (); + } + + [Kept] + static int Property { + [Kept] + [ExpectBodyModified] + [ExpectLocalsModified] + get { + return field; + } + } + + static void NeverReached_1 () + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.xml b/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.xml new file mode 100644 index 000000000000..c34957411985 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/BodiesWithSubstitutions.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs new file mode 100644 index 000000000000..9a92ab742f40 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/ComplexConditions.cs @@ -0,0 +1,35 @@ +using System; +using System.Reflection.Emit; +using Mono.Linker.Tests.Cases.Expectations.Assertions; + +namespace Mono.Linker.Tests.Cases.UnreachableBlock +{ + public class ComplexConditions + { + public static void Main() + { + Test_1 (null); + } + + [Kept] + [ExpectBodyModified] + static void Test_1 (object type) + { + if (type is Type || (IsDynamicCodeSupported && type is TypeBuilder)) + Reached_1 (); + } + + [Kept] + static bool IsDynamicCodeSupported { + [Kept] + get { + return true; + } + } + + [Kept] + static void Reached_1 () + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs new file mode 100644 index 000000000000..9ef8c1f0116c --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/MultiStageRemoval.cs @@ -0,0 +1,79 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.UnreachableBlock +{ + public class MultiStageRemoval + { + public static void Main() + { + TestMethod_1 (); + TestMethod_2 (); + } + + [Kept] + [ExpectBodyModified] + static void TestMethod_1 () + { + if (TestProperty_int () == 0) + NeverReached_1 (); + } + + [Kept] + [ExpectBodyModified] + static void TestMethod_2 () + { + if (TestProperty_bool_twice () >= 0) + NeverReached_2 (); + } + + [Kept] + [ExpectBodyModified] + static int TestProperty_int () + { + if (Prop > 5) { + return 11; + } + + return 0; + } + + [Kept] + [ExpectBodyModified] + static int TestProperty_bool_twice () + { + if (PropBool) { + return -5; + } + + if (TestProperty_bool_twice () == 4) + return -1; + + return 0; + } + + [Kept] + static int Prop { + [Kept] + get { + return 9; + } + } + + [Kept] + static bool PropBool { + [Kept] + get { + return true; + } + } + + static void NeverReached_1 () + { + } + + static void NeverReached_2 () + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs new file mode 100644 index 000000000000..f95a67a14974 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SimpleConditionalProperty.cs @@ -0,0 +1,147 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.UnreachableBlock +{ +// [SetupCSharpCompilerToUse ("csc")] + public class SimpleConditionalProperty + { + public static void Main() + { + TestProperty_int_1 (); + TestProperty_int_2 (); + TestProperty_int_3 (); + TestProperty_bool_1 (); + TestProperty_bool_2 (); + TestProperty_bool_3 (); + TestProperty_enum_1 (); + TestProperty_null_1 (); + } + + [Kept] + [ExpectBodyModified] + static void TestProperty_int_1 () + { + if (Prop != 3) + NeverReached_1 (); + } + + [Kept] + [ExpectBodyModified] + static void TestProperty_int_2 () + { + if (3 == Prop) { + + } else { + NeverReached_1 (); + } + } + + [Kept] + [ExpectBodyModified] + static int TestProperty_int_3 () + { + if (Prop > 5 && TestProperty_int_3 () == 0) { + NeverReached_1 (); + } + + return 0; + } + + [Kept] + [ExpectBodyModified] + static void TestProperty_bool_1 () + { + if (!PropBool) { + + } else { + NeverReached_1 (); + } + } + + [Kept] + [ExpectBodyModified] + static void TestProperty_bool_2 () + { + if (PropBool) { + NeverReached_1 (); + } + } + + [Kept] + [ExpectBodyModified] + static void TestProperty_bool_3 () + { + if (PropBool != PropBool) { + NeverReached_1 (); + } + } + + [Kept] + [ExpectBodyModified] + static void TestProperty_enum_1 () + { + while (PropEnum == TestEnum.C) { + NeverReached_1 (); + } + } + + [Kept] + [ExpectBodyModified] + static void TestProperty_null_1 () + { + if (PropNull != null) + NeverReached_1 (); + } + + [Kept] + static int Prop { + [Kept] + get { + int i = 3; + return i; + } + } + + [Kept] + static bool PropBool { + [Kept] + get { + return false; + } + } + + [Kept] + static TestEnum PropEnum { + [Kept] + get { + return TestEnum.B; + } + } + + [Kept] + static string PropNull { + [Kept] + get { + return null; + } + } + + static void NeverReached_1 () + { + } + + [Kept] + [KeptMember ("value__")] + [KeptBaseType (typeof (Enum))] + enum TestEnum { + [Kept] + A = 0, + [Kept] + B = 1, + [Kept] + C = 2 + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/SizeOfInConditions.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SizeOfInConditions.cs new file mode 100644 index 000000000000..5bc7bbe90de4 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SizeOfInConditions.cs @@ -0,0 +1,47 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.UnreachableBlock { +#if ILLINK + [SetupLinkerSubstitutionFile ("SizeOfInConditions.netcore.xml")] +#else + [SetupLinkerSubstitutionFile ("SizeOfInConditions.net_4_x.xml")] +#endif + [SetupCompileArgument ("/unsafe")] + public unsafe class SizeOfInConditions { + public static void Main () + { + TestIntPtr (); + TestUIntPtr (); + } + + [Kept] + [ExpectBodyModified] + static void TestIntPtr () + { + if (sizeof (IntPtr) != 4) { + } else { + Reached_1 (); + } + } + + [Kept] + [ExpectBodyModified] + static void TestUIntPtr () + { + if (sizeof (UIntPtr) != 8) { + } else { + Reached_2 (); + } + } + + static void Reached_1 () + { + } + + static void Reached_2 () + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/SizeOfInConditions.net_4_x.xml b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SizeOfInConditions.net_4_x.xml new file mode 100644 index 000000000000..bff4c3b814cd --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SizeOfInConditions.net_4_x.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/SizeOfInConditions.netcore.xml b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SizeOfInConditions.netcore.xml new file mode 100644 index 000000000000..5b53cc7b83a7 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/SizeOfInConditions.netcore.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs new file mode 100644 index 000000000000..defc213d42ca --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/UnreachableBlock/TryFinallyBlocks.cs @@ -0,0 +1,36 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; + +namespace Mono.Linker.Tests.Cases.UnreachableBlock +{ + public class TryFinallyBlocks + { + public static void Main () + { + TestSimpleTry (); + } + + [Kept] + static void TestSimpleTry () + { + if (Prop != 3) + Reached_1 (); + } + + [Kept] + static int Prop { + [Kept] + get { + try { + return 3; + } finally { + + } + } + } + + [Kept] + static void Reached_1 () + { + } + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests/TestCases/TestDatabase.cs b/test/Mono.Linker.Tests/TestCases/TestDatabase.cs index 893b55508d87..726516d211d6 100644 --- a/test/Mono.Linker.Tests/TestCases/TestDatabase.cs +++ b/test/Mono.Linker.Tests/TestCases/TestDatabase.cs @@ -141,6 +141,16 @@ public static IEnumerable CodegenAnnotationTests () return NUnitCasesBySuiteName ("CodegenAnnotation"); } + public static IEnumerable UnreachableBlockTests () + { + return NUnitCasesBySuiteName ("UnreachableBlock"); + } + + public static IEnumerable SubstitutionsTests () + { + return NUnitCasesBySuiteName ("Substitutions"); + } + public static TestCaseCollector CreateCollector () { string rootSourceDirectory; diff --git a/test/Mono.Linker.Tests/TestCases/TestSuites.cs b/test/Mono.Linker.Tests/TestCases/TestSuites.cs index b3aa7ad50498..c7d906c36c18 100644 --- a/test/Mono.Linker.Tests/TestCases/TestSuites.cs +++ b/test/Mono.Linker.Tests/TestCases/TestSuites.cs @@ -169,6 +169,18 @@ public void UnreachableBodyTests (TestCase testCase) Run (testCase); } + [TestCaseSource (typeof (TestDatabase), nameof (TestDatabase.UnreachableBlockTests))] + public void UnreachableBlockTests (TestCase testCase) + { + Run (testCase); + } + + [TestCaseSource (typeof (TestDatabase), nameof (TestDatabase.SubstitutionsTests))] + public void SubstitutionsTests (TestCase testCase) + { + Run (testCase); + } + protected virtual void Run (TestCase testCase) { var runner = new TestRunner (new ObjectFactory ()); diff --git a/test/Mono.Linker.Tests/TestCasesRunner/LinkerArgumentBuilder.cs b/test/Mono.Linker.Tests/TestCasesRunner/LinkerArgumentBuilder.cs index 98f93d782700..3a3f2a05568b 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/LinkerArgumentBuilder.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/LinkerArgumentBuilder.cs @@ -111,6 +111,12 @@ public virtual void AddStripResources (bool stripResources) } } + public virtual void AddSubstitutions (string file) + { + Append ("--substitutions"); + Append (file); + } + public string [] ToArgs () { return _arguments.ToArray (); @@ -171,6 +177,9 @@ public virtual void ProcessOptions (TestCaseLinkerOptions options) AddStripResources (options.StripResources); + foreach (var substitutions in options.Substitutions) + AddSubstitutions (substitutions); + // Unity uses different argument format and needs to be able to translate to their format. In order to make that easier // we keep the information in flag + values format for as long as we can so that this information doesn't have to be parsed out of a single string foreach (var additionalArgument in options.AdditionalArguments) diff --git a/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs index f13b4eafc348..5becea9adac5 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs @@ -17,5 +17,7 @@ public class TestCaseLinkerOptions public bool StripResources; public List> AdditionalArguments = new List> (); + + public List Substitutions = new List (); } } \ No newline at end of file diff --git a/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadaProvider.cs b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadaProvider.cs index 8aeaf9c36b35..1b2383a5fe1c 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadaProvider.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadaProvider.cs @@ -25,7 +25,7 @@ public TestCaseMetadaProvider (TestCase testCase, AssemblyDefinition fullTestCas throw new InvalidOperationException ($"Could not find the type definition for {_testCase.Name} in {_testCase.SourceFile}"); } - public virtual TestCaseLinkerOptions GetLinkerOptions () + public virtual TestCaseLinkerOptions GetLinkerOptions (NPath inputPath) { var tclo = new TestCaseLinkerOptions { Il8n = GetOptionAttributeValue (nameof (Il8nAttribute), "none"), @@ -36,7 +36,7 @@ public virtual TestCaseLinkerOptions GetLinkerOptions () CoreAssembliesAction = GetOptionAttributeValue (nameof (SetupLinkerCoreActionAttribute), null), UserAssembliesAction = GetOptionAttributeValue (nameof (SetupLinkerUserActionAttribute), null), SkipUnresolved = GetOptionAttributeValue (nameof (SkipUnresolvedAttribute), false), - StripResources = GetOptionAttributeValue (nameof (StripResourcesAttribute), true) + StripResources = GetOptionAttributeValue (nameof (StripResourcesAttribute), true), }; foreach (var assemblyAction in _testCaseTypeDefinition.CustomAttributes.Where (attr => attr.AttributeType.Name == nameof (SetupLinkerActionAttribute))) @@ -45,6 +45,12 @@ public virtual TestCaseLinkerOptions GetLinkerOptions () tclo.AssembliesAction.Add (new KeyValuePair ((string)ca [0].Value, (string)ca [1].Value)); } + foreach (var subsFile in _testCaseTypeDefinition.CustomAttributes.Where (attr => attr.AttributeType.Name == nameof (SetupLinkerSubstitutionFileAttribute))) { + var ca = subsFile.ConstructorArguments; + var file = (string)ca [0].Value; + tclo.Substitutions.Add (Path.Combine (inputPath, file)); + } + foreach (var additionalArgumentAttr in _testCaseTypeDefinition.CustomAttributes.Where (attr => attr.AttributeType.Name == nameof (SetupLinkerArgumentAttribute))) { var ca = additionalArgumentAttr.ConstructorArguments; @@ -136,6 +142,13 @@ public virtual IEnumerable GetResponseFiles () .Select (GetSourceAndRelativeDestinationValue); } + public virtual IEnumerable GetSubstitutionFiles () + { + return _testCaseTypeDefinition.CustomAttributes + .Where (attr => attr.AttributeType.Name == nameof (SetupLinkerSubstitutionFileAttribute)) + .Select (GetSourceAndRelativeDestinationValue); + } + public virtual IEnumerable GetExtraLinkerSearchDirectories () { #if NETCOREAPP diff --git a/test/Mono.Linker.Tests/TestCasesRunner/TestCaseSandbox.cs b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseSandbox.cs index 5ec502878a21..d1866881e7fb 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/TestCaseSandbox.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/TestCaseSandbox.cs @@ -98,6 +98,10 @@ public virtual void Populate (TestCaseMetadaProvider metadataProvider) res.Source.FileMustExist ().Copy (InputDirectory.Combine (res.DestinationFileName)); } + foreach (var res in metadataProvider.GetSubstitutionFiles ()) { + res.Source.FileMustExist ().Copy (InputDirectory.Combine (res.DestinationFileName)); + } + foreach (var compileRefInfo in metadataProvider.GetSetupCompileAssembliesBefore ()) { var destination = BeforeReferenceSourceDirectoryFor (compileRefInfo.OutputName).EnsureDirectoryExists (); diff --git a/test/Mono.Linker.Tests/TestCasesRunner/TestRunner.cs b/test/Mono.Linker.Tests/TestCasesRunner/TestRunner.cs index b9a1cc71b2e0..9fbec7c1ec1e 100644 --- a/test/Mono.Linker.Tests/TestCasesRunner/TestRunner.cs +++ b/test/Mono.Linker.Tests/TestCasesRunner/TestRunner.cs @@ -104,14 +104,14 @@ private LinkedTestCaseResult Link (TestCase testCase, TestCaseSandbox sandbox, M protected virtual void AddLinkOptions (TestCaseSandbox sandbox, ManagedCompilationResult compilationResult, LinkerArgumentBuilder builder, TestCaseMetadaProvider metadataProvider) { - var caseDefinedOptions = metadataProvider.GetLinkerOptions (); + var caseDefinedOptions = metadataProvider.GetLinkerOptions (sandbox.InputDirectory); builder.AddOutputDirectory (sandbox.OutputDirectory); foreach (var linkXmlFile in sandbox.LinkXmlFiles) builder.AddLinkXmlFile (linkXmlFile); - foreach (var linkXmlFile in sandbox.ResponseFiles) - builder.AddResponseFile (linkXmlFile); + foreach (var rspFile in sandbox.ResponseFiles) + builder.AddResponseFile (rspFile); builder.AddSearchDirectory (sandbox.InputDirectory); foreach (var extraSearchDir in metadataProvider.GetExtraLinkerSearchDirectories ())