diff options
author | Matt Ward <matt.ward@xamarin.com> | 2015-11-24 14:35:58 +0300 |
---|---|---|
committer | Matt Ward <matt.ward@xamarin.com> | 2015-11-24 15:15:11 +0300 |
commit | 174f1335283a16918b1cfcac1a5f5594122d3464 (patch) | |
tree | 8cb9028820f973fc591a6bdfeb5f735627fec67d /main/src/addins/TextTemplating | |
parent | c06c4c2a5e30aede8c115e094a7f93d18ed88672 (diff) |
[T4] Fix null ref when using parameterized T4 templates.
This also fixes bug #36093 - [ASP.NET Project Wizard] Global.asax
could not be written.
https://bugzilla.xamarin.com/show_bug.cgi?id=36093
The simplest way to reproduce the exception is to create a T4 template
with the content shown below and then try to save the T4 template:
<#@ template language="C#" #>
<#@ parameter type="System.Int32" name="TimesToRepeat" #>
The TemplatingEngine.GenerateIndentedClassCode uses reflection when
running on Mono to initialize internal members of the CodeGenerator
class since the implementation of the CSharpCodeProvider is
incomplete on Mono so the CodeDomProvider CreateCodeFromMember method
cannot be called. The CodeGenerator in Mono 4.2 has changed and no
longer has an InitOutput method that the TemplatingEngine was using
to initialize the internal members. This missing method was causing a
null reference exception.
Now if the InitOutput method is missing the TemplatingEngine will
instead directly initialize the internal fields of the CodeGenerator
class. The CSharpCodeProvider's GenerateCodeFromMember is still
not implemented with Mono 4.2 so reflection is still required.
Also added a unit test for the GenerateIndentedClassCode method.
Diffstat (limited to 'main/src/addins/TextTemplating')
3 files changed, 135 insertions, 3 deletions
diff --git a/main/src/addins/TextTemplating/Mono.TextTemplating.Tests/GenerateIndentedClassCodeTests.cs b/main/src/addins/TextTemplating/Mono.TextTemplating.Tests/GenerateIndentedClassCodeTests.cs new file mode 100644 index 0000000000..0810e2e783 --- /dev/null +++ b/main/src/addins/TextTemplating/Mono.TextTemplating.Tests/GenerateIndentedClassCodeTests.cs @@ -0,0 +1,108 @@ +// +// GenerateIndentedClassCodeTests.cs +// +// Author: +// Matt Ward <matt.ward@xamarin.com> +// +// Copyright (c) 2015 Xamarin Inc. (http://xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.CodeDom; +using System.CodeDom.Compiler; +using NUnit.Framework; +using System.IO; + +namespace Mono.TextTemplating.Tests +{ + [TestFixture] + public class GenerateIndentedClassCodeTests + { + [Test] + public void FieldAndPropertyGenerated () + { + var provider = CodeDomProvider.CreateProvider ("C#"); + var field = CreateBoolField (); + var property = CreateBoolProperty (); + + string output = TemplatingEngine.GenerateIndentedClassCode (provider, field, property); + output = FixOutput (output); + string expectedOutput = FixOutput (MethodAndFieldGeneratedOutput); + + Assert.AreEqual (expectedOutput, output); + } + + static CodeTypeMember CreateVoidMethod () + { + var meth = new CodeMemberMethod { Name = "MyMethod" }; + meth.ReturnType = new CodeTypeReference (typeof(void)); + return meth; + } + + static CodeTypeMember CreateBoolField () + { + var type = new CodeTypeReference (typeof(bool)); + return new CodeMemberField { Name = "myField", Type = type }; + } + + static CodeTypeMember CreateBoolProperty () + { + var type = new CodeTypeReference (typeof(bool)); + var prop = new CodeMemberProperty { Name = "MyProperty", Type = type }; + prop.GetStatements.Add ( + new CodeMethodReturnStatement ( + new CodePrimitiveExpression (true) + ) + ); + return prop; + } + + /// <summary> + /// Remove empty lines which are not generated on Mono. + /// </summary> + static string FixOutput (string output, string newLine = "\n") + { + using (var writer = new StringWriter ()) { + using (var reader = new StringReader (output)) { + + string line; + while ((line = reader.ReadLine ()) != null) { + if (!String.IsNullOrWhiteSpace (line)) { + writer.Write (line); + writer.Write (newLine); + } + } + } + return writer.ToString (); + } + } + + public static string MethodAndFieldGeneratedOutput = +@" + private bool myField; + + private bool MyProperty { + get { + return true; + } + } +"; + } +} diff --git a/main/src/addins/TextTemplating/Mono.TextTemplating.Tests/Mono.TextTemplating.Tests.csproj b/main/src/addins/TextTemplating/Mono.TextTemplating.Tests/Mono.TextTemplating.Tests.csproj index 3b67d5045d..fec16c1aac 100644 --- a/main/src/addins/TextTemplating/Mono.TextTemplating.Tests/Mono.TextTemplating.Tests.csproj +++ b/main/src/addins/TextTemplating/Mono.TextTemplating.Tests/Mono.TextTemplating.Tests.csproj @@ -41,6 +41,7 @@ <Compile Include="GenerationTests.cs" /> <Compile Include="TemplatingEngineHelper.cs" /> <Compile Include="TemplateEnginePreprocessTemplateTests.cs" /> + <Compile Include="GenerateIndentedClassCodeTests.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Mono.TextTemplating\Mono.TextTemplating.csproj"> diff --git a/main/src/addins/TextTemplating/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs b/main/src/addins/TextTemplating/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs index 8a312101d2..9425f6dae8 100644 --- a/main/src/addins/TextTemplating/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs +++ b/main/src/addins/TextTemplating/Mono.TextTemplating/Mono.TextTemplating/TemplatingEngine.cs @@ -1091,7 +1091,7 @@ namespace Mono.TextTemplating } var cgType = typeof (CodeGenerator); - var cgInit = cgType.GetMethod ("InitOutput", BindingFlags.NonPublic | BindingFlags.Instance); + var initializeCodeGenerator = GetInitializeCodeGeneratorAction (cgType); var cgFieldGen = cgType.GetMethod ("GenerateField", BindingFlags.NonPublic | BindingFlags.Instance); var cgPropGen = cgType.GetMethod ("GenerateProperty", BindingFlags.NonPublic | BindingFlags.Instance); @@ -1103,19 +1103,42 @@ namespace Mono.TextTemplating foreach (CodeTypeMember member in members) { var f = member as CodeMemberField; if (f != null) { - cgInit.Invoke (generator, new object[] { sw, options }); + initializeCodeGenerator (generator, sw, options); cgFieldGen.Invoke (generator, new object[] { f }); continue; } var p = member as CodeMemberProperty; if (p != null) { - cgInit.Invoke (generator, new object[] { sw, options }); + initializeCodeGenerator (generator, sw, options); cgPropGen.Invoke (generator, new object[] { p, dummy }); continue; } } } + static Action<CodeGenerator, StringWriter, CodeGeneratorOptions> GetInitializeCodeGeneratorAction (Type cgType) + { + var cgInit = cgType.GetMethod ("InitOutput", BindingFlags.NonPublic | BindingFlags.Instance); + if (cgInit != null) { + return new Action<CodeGenerator, StringWriter, CodeGeneratorOptions> ((generator, sw, options) => { + cgInit.Invoke (generator, new object[] { sw, options }); + }); + } + + var cgOptions = cgType.GetField ("options", BindingFlags.NonPublic | BindingFlags.Instance); + var cgOutput = cgType.GetField ("output", BindingFlags.NonPublic | BindingFlags.Instance); + + if (cgOptions != null && cgOutput != null) { + return new Action<CodeGenerator, StringWriter, CodeGeneratorOptions> ((generator, sw, options) => { + var output = new IndentedTextWriter (sw); + cgOptions.SetValue (generator, options); + cgOutput.SetValue (generator, output); + }); + } + + throw new InvalidOperationException ("Unable to initialize CodeGenerator."); + } + public static string GenerateIndentedClassCode (CodeDomProvider provider, params CodeTypeMember[] members) { return GenerateIndentedClassCode (provider, (IEnumerable<CodeTypeMember>)members); |