2 // System.Web.Compilation.BaseCompiler
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (c) Copyright 2002,2003 Ximian, Inc (http://www.ximian.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.CodeDom.Compiler;
34 using System.Collections;
35 using System.Collections.Specialized;
36 using System.Reflection;
39 using System.Web.Configuration;
42 namespace System.Web.Compilation
44 abstract class BaseCompiler
47 static BindingFlags replaceableFlags = BindingFlags.Public | BindingFlags.NonPublic |
48 BindingFlags.Instance;
51 TemplateParser parser;
52 CodeDomProvider provider;
53 ICodeCompiler compiler;
56 CompilerParameters compilerParameters;
58 bool isRebuilding = false;
59 protected Hashtable partialNameOverride = new Hashtable();
60 protected CodeTypeDeclaration partialClass;
61 protected CodeTypeReferenceExpression partialClassExpr;
63 protected CodeTypeDeclaration mainClass;
64 protected CodeTypeReferenceExpression mainClassExpr;
65 protected static CodeThisReferenceExpression thisRef = new CodeThisReferenceExpression ();
67 protected BaseCompiler (TemplateParser parser)
74 unit = new CodeCompileUnit ();
76 if (parser.IsPartial) {
77 string partialns = null;
78 string partialclasstype = parser.PartialClassName;
80 int partialdot = partialclasstype.LastIndexOf ('.');
81 if (partialdot != -1) {
82 partialns = partialclasstype.Substring (0, partialdot);
83 partialclasstype = partialclasstype.Substring (partialdot + 1);
86 CodeNamespace partialNS = new CodeNamespace (partialns);
87 partialClass = new CodeTypeDeclaration (partialclasstype);
88 partialClass.IsPartial = true;
89 partialClassExpr = new CodeTypeReferenceExpression (parser.PartialClassName);
91 unit.Namespaces.Add (partialNS);
92 partialClass.TypeAttributes = TypeAttributes.Public;
93 partialNS.Types.Add (partialClass);
97 string mainclasstype = parser.ClassName;
98 string mainns = "ASP";
101 int maindot = mainclasstype.LastIndexOf ('.');
103 mainns = mainclasstype.Substring (0, maindot);
104 mainclasstype = mainclasstype.Substring (maindot + 1);
108 mainNS = new CodeNamespace (mainns);
109 mainClass = new CodeTypeDeclaration (mainclasstype);
110 CodeTypeReference baseTypeRef;
112 if (partialClass != null) {
113 baseTypeRef = new CodeTypeReference (parser.PartialClassName);
114 baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
116 baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
117 if (parser.BaseTypeIsGlobal)
118 baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
121 baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
123 mainClass.BaseTypes.Add (baseTypeRef);
125 mainClassExpr = new CodeTypeReferenceExpression (mainns + "." + mainclasstype);
127 unit.Namespaces.Add (mainNS);
128 mainClass.TypeAttributes = TypeAttributes.Public;
129 mainNS.Types.Add (mainClass);
131 foreach (object o in parser.Imports) {
133 mainNS.Imports.Add (new CodeNamespaceImport ((string) o));
136 // StringCollection.Contains has O(n) complexity, but
137 // considering the number of comparisons we make on
138 // average and the fact that using an intermediate array
139 // would be even more costly, this is fine here.
140 StringCollection refAsm = unit.ReferencedAssemblies;
142 if (parser.Assemblies != null) {
143 foreach (object o in parser.Assemblies) {
144 asmName = o as string;
145 if (asmName != null && !refAsm.Contains (asmName))
146 refAsm.Add (asmName);
151 ArrayList al = WebConfigurationManager.ExtraAssemblies;
152 if (al != null && al.Count > 0) {
153 foreach (object o in al) {
154 asmName = o as string;
155 if (asmName != null && !refAsm.Contains (asmName))
156 refAsm.Add (asmName);
160 IList list = BuildManager.CodeAssemblies;
161 if (list != null && list.Count > 0) {
163 foreach (object o in list) {
167 asmName = asm.Location;
168 if (asmName != null && !refAsm.Contains (asmName))
169 refAsm.Add (asmName);
173 // Late-bound generators specifics (as for MonoBASIC/VB.NET)
174 unit.UserData["RequireVariableDeclaration"] = parser.ExplicitOn;
175 unit.UserData["AllowLateBound"] = !parser.StrictOn;
178 AddClassAttributes ();
179 CreateStaticFields ();
180 AddApplicationAndSessionObjects ();
183 CreateConstructor (null, null);
186 protected virtual void CreateStaticFields ()
188 CodeMemberField fld = new CodeMemberField (typeof (bool), "__initialized");
189 fld.Attributes = MemberAttributes.Private | MemberAttributes.Static;
190 fld.InitExpression = new CodePrimitiveExpression (false);
191 mainClass.Members.Add (fld);
195 void AssignAppRelativeVirtualPath (CodeConstructor ctor)
197 Type baseType = parser.CodeFileBaseClassType;
199 if (baseType == null)
200 baseType = parser.BaseType;
201 if (baseType == null)
203 if (!baseType.IsSubclassOf (typeof (System.Web.UI.TemplateControl)))
206 string arvp = Path.Combine (parser.BaseVirtualDir, Path.GetFileName (parser.InputFile));
207 if (VirtualPathUtility.IsAbsolute (arvp))
210 CodeExpression cast = new CodeCastExpression (baseType, new CodeThisReferenceExpression ());
211 CodePropertyReferenceExpression arvpProp = new CodePropertyReferenceExpression (cast, "AppRelativeVirtualPath");
212 CodeAssignStatement arvpAssign = new CodeAssignStatement ();
213 arvpAssign.Left = arvpProp;
214 arvpAssign.Right = new CodePrimitiveExpression (VirtualPathUtility.RemoveTrailingSlash (arvp));
215 ctor.Statements.Add (arvpAssign);
219 protected virtual void CreateConstructor (CodeStatementCollection localVars,
220 CodeStatementCollection trueStmt)
222 CodeConstructor ctor = new CodeConstructor ();
223 ctor.Attributes = MemberAttributes.Public;
224 mainClass.Members.Add (ctor);
227 AssignAppRelativeVirtualPath (ctor);
229 if (localVars != null)
230 ctor.Statements.AddRange (localVars);
232 CodeTypeReferenceExpression r;
234 if (parser.IsPartial)
235 r = new CodeTypeReferenceExpression (mainClass.Name);
238 r = new CodeTypeReferenceExpression (mainNS.Name + "." + mainClass.Name);
239 CodeFieldReferenceExpression initialized;
240 initialized = new CodeFieldReferenceExpression (r, "__initialized");
242 CodeBinaryOperatorExpression bin;
243 bin = new CodeBinaryOperatorExpression (initialized,
244 CodeBinaryOperatorType.ValueEquality,
245 new CodePrimitiveExpression (false));
247 CodeAssignStatement assign = new CodeAssignStatement (initialized,
248 new CodePrimitiveExpression (true));
250 CodeConditionStatement cond = new CodeConditionStatement (bin, assign);
251 if (trueStmt != null)
252 cond.TrueStatements.AddRange (trueStmt);
254 ctor.Statements.Add (cond);
259 if (parser.Scripts == null || parser.Scripts.Count == 0)
262 foreach (object o in parser.Scripts) {
264 mainClass.Members.Add (new CodeSnippetTypeMember ((string) o));
268 protected internal virtual void CreateMethods ()
273 void InternalCreatePageProperty (string retType, string name, string contextProperty)
275 CodeMemberProperty property = new CodeMemberProperty ();
276 property.Name = name;
277 property.Type = new CodeTypeReference (retType);
278 property.Attributes = MemberAttributes.Family | MemberAttributes.Final;
280 CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
281 CodeCastExpression cast = new CodeCastExpression ();
282 ret.Expression = cast;
284 CodePropertyReferenceExpression refexp = new CodePropertyReferenceExpression ();
285 refexp.TargetObject = new CodePropertyReferenceExpression (new CodeThisReferenceExpression (), "Context");
286 refexp.PropertyName = contextProperty;
288 cast.TargetType = new CodeTypeReference (retType);
289 cast.Expression = refexp;
291 property.GetStatements.Add (ret);
292 if (partialClass == null)
293 mainClass.Members.Add (property);
295 partialClass.Members.Add (property);
298 protected void CreateProfileProperty ()
301 if (AppCodeCompiler.HaveCustomProfile (WebConfigurationManager.GetSection ("system.web/profile") as ProfileSection))
302 retType = "ProfileCommon";
304 retType = "System.Web.Profile.DefaultProfile";
305 InternalCreatePageProperty (retType, "Profile", "Profile");
309 protected virtual void AddInterfaces ()
311 if (parser.Interfaces == null)
314 foreach (object o in parser.Interfaces) {
316 mainClass.BaseTypes.Add (new CodeTypeReference ((string) o));
320 protected virtual void AddClassAttributes ()
324 protected virtual void AddApplicationAndSessionObjects ()
328 /* Utility methods for <object> stuff */
329 protected void CreateApplicationOrSessionPropertyForObject (Type type,
334 /* if isApplication this generates (the 'cachedapp' field is created earlier):
335 private MyNS.MyClass app {
337 if ((this.cachedapp == null)) {
338 this.cachedapp = ((MyNS.MyClass)
339 (this.Application.StaticObjects.GetObject("app")));
341 return this.cachedapp;
345 else, this is for Session:
346 private MyNS.MyClass ses {
348 return ((MyNS.MyClass) (this.Session.StaticObjects.GetObject("ses")));
354 CodeExpression result = null;
356 CodeMemberProperty prop = new CodeMemberProperty ();
357 prop.Type = new CodeTypeReference (type);
358 prop.Name = propName;
360 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
362 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
364 CodePropertyReferenceExpression p1;
366 p1 = new CodePropertyReferenceExpression (thisRef, "Application");
368 p1 = new CodePropertyReferenceExpression (thisRef, "Session");
370 CodePropertyReferenceExpression p2;
371 p2 = new CodePropertyReferenceExpression (p1, "StaticObjects");
373 CodeMethodReferenceExpression getobject;
374 getobject = new CodeMethodReferenceExpression (p2, "GetObject");
376 CodeMethodInvokeExpression invoker;
377 invoker = new CodeMethodInvokeExpression (getobject,
378 new CodePrimitiveExpression (propName));
380 CodeCastExpression cast = new CodeCastExpression (prop.Type, invoker);
383 CodeFieldReferenceExpression field;
384 field = new CodeFieldReferenceExpression (thisRef, "cached" + propName);
386 CodeConditionStatement stmt = new CodeConditionStatement();
387 stmt.Condition = new CodeBinaryOperatorExpression (field,
388 CodeBinaryOperatorType.IdentityEquality,
389 new CodePrimitiveExpression (null));
391 CodeAssignStatement assign = new CodeAssignStatement ();
394 stmt.TrueStatements.Add (assign);
395 prop.GetStatements.Add (stmt);
401 prop.GetStatements.Add (new CodeMethodReturnStatement (result));
402 mainClass.Members.Add (prop);
405 protected string CreateFieldForObject (Type type, string name)
407 string fieldName = "cached" + name;
408 CodeMemberField f = new CodeMemberField (type, fieldName);
409 f.Attributes = MemberAttributes.Private;
410 mainClass.Members.Add (f);
414 protected void CreatePropertyForObject (Type type, string propName, string fieldName, bool isPublic)
416 CodeFieldReferenceExpression field = new CodeFieldReferenceExpression (thisRef, fieldName);
417 CodeMemberProperty prop = new CodeMemberProperty ();
418 prop.Type = new CodeTypeReference (type);
419 prop.Name = propName;
421 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
423 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
425 CodeConditionStatement stmt = new CodeConditionStatement();
426 stmt.Condition = new CodeBinaryOperatorExpression (field,
427 CodeBinaryOperatorType.IdentityEquality,
428 new CodePrimitiveExpression (null));
430 CodeObjectCreateExpression create = new CodeObjectCreateExpression (prop.Type);
431 stmt.TrueStatements.Add (new CodeAssignStatement (field, create));
432 prop.GetStatements.Add (stmt);
433 prop.GetStatements.Add (new CodeMethodReturnStatement (field));
435 mainClass.Members.Add (prop);
439 void CheckCompilerErrors (CompilerResults results)
441 if (results.NativeCompilerReturnValue == 0)
444 string fileText = null;
445 CompilerErrorCollection errors = results.Errors;
446 CompilerError ce = (errors != null && errors.Count > 0) ? errors [0] : null;
447 string inFile = (ce != null) ? ce.FileName : null;
449 if (inFile != null && File.Exists (inFile)) {
450 using (StreamReader sr = File.OpenText (inFile)) {
451 fileText = sr.ReadToEnd ();
454 StringWriter writer = new StringWriter();
455 provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
456 fileText = writer.ToString ();
458 throw new CompilationException (parser.InputFile, errors, fileText);
461 protected string DynamicDir ()
463 return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
466 internal static CodeDomProvider CreateProvider (string lang, out string compilerOptions, out int warningLevel, out string tempdir)
468 return CreateProvider (HttpContext.Current, lang, out compilerOptions, out warningLevel, out tempdir);
471 internal static CodeDomProvider CreateProvider (HttpContext context, string lang, out string compilerOptions, out int warningLevel, out string tempdir)
473 CodeDomProvider ret = null;
476 CompilationSection config = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
477 Compiler comp = config.Compilers[lang];
480 CompilerInfo info = CodeDomProvider.GetCompilerInfo (lang);
481 if (info != null && info.IsCodeDomProviderTypeValid) {
482 ret = info.CreateProvider ();
484 CompilerParameters cp = info.CreateDefaultCompilerParameters ();
485 compilerOptions = cp.CompilerOptions;
486 warningLevel = cp.WarningLevel;
488 compilerOptions = String.Empty;
492 Type t = HttpApplication.LoadType (comp.Type, true);
493 ret = Activator.CreateInstance (t) as CodeDomProvider;
495 compilerOptions = comp.CompilerOptions;
496 warningLevel = comp.WarningLevel;
499 CompilationConfiguration config;
501 config = CompilationConfiguration.GetInstance (context);
502 ret = config.GetProvider (lang);
504 compilerOptions = config.GetCompilerOptions (lang);
505 warningLevel = config.GetWarningLevel (lang);
507 tempdir = config.TempDirectory;
512 [MonoTODO ("find out how to extract the warningLevel and compilerOptions in the <system.codedom> case")]
513 public virtual Type GetCompiledType ()
515 Type type = CachingCompiler.GetTypeFromCache (parser.InputFile);
520 string lang = parser.Language;
522 string compilerOptions;
525 Provider = CreateProvider (parser.Context, lang, out compilerOptions, out warningLevel, out tempdir);
526 if (Provider == null)
527 throw new HttpException ("Configuration error. Language not supported: " +
531 compiler = provider.CreateCompiler ();
534 CompilerParameters parameters = CompilerParameters;
535 parameters.IncludeDebugInformation = parser.Debug;
536 parameters.CompilerOptions = compilerOptions + " " + parser.CompilerOptions;
537 parameters.WarningLevel = warningLevel;
539 bool keepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
541 if (tempdir == null || tempdir == "")
542 tempdir = DynamicDir ();
544 TempFileCollection tempcoll = new TempFileCollection (tempdir, keepFiles);
545 parameters.TempFiles = tempcoll;
546 string dllfilename = Path.GetFileName (tempcoll.AddExtension ("dll", true));
547 parameters.OutputAssembly = Path.Combine (DynamicDir (), dllfilename);
549 CompilerResults results = CachingCompiler.Compile (this);
550 CheckCompilerErrors (results);
551 Assembly assembly = results.CompiledAssembly;
552 if (assembly == null) {
553 if (!File.Exists (parameters.OutputAssembly)) {
554 results.TempFiles.Delete ();
555 throw new CompilationException (parser.InputFile, results.Errors,
556 "No assembly returned after compilation!?");
559 assembly = Assembly.LoadFrom (parameters.OutputAssembly);
562 results.TempFiles.Delete ();
563 Type mainClassType = assembly.GetType (mainClassExpr.Type.BaseType, true);
566 if (parser.IsPartial) {
567 // With the partial classes, we need to make sure we
568 // don't have any methods that should have not been
569 // created (because they are accessible from the base
570 // types). We cannot do this normally because the
571 // codebehind file is actually a partial class and we
572 // have no way of identifying the partial class' base
574 if (!isRebuilding && CheckPartialBaseType (mainClassType)) {
576 parser.RootBuilder.ResetState ();
577 return GetCompiledType ();
582 return mainClassType;
586 internal bool IsRebuildingPartial
588 get { return isRebuilding; }
591 internal bool CheckPartialBaseType (Type type)
593 // Get the base type. If we don't have any (bad thing), we
594 // don't need to replace ourselves. Also check for the
595 // core file, since that won't have any either.
596 Type baseType = type.BaseType;
597 if (baseType == null || baseType == typeof(System.Web.UI.Page))
600 bool rebuild = false;
602 if (CheckPartialBaseFields (type, baseType))
605 if (CheckPartialBaseProperties (type, baseType))
611 internal bool CheckPartialBaseFields (Type type, Type baseType)
613 bool rebuild = false;
615 foreach (FieldInfo baseInfo in baseType.GetFields (replaceableFlags)) {
616 if (baseInfo.IsPrivate)
619 FieldInfo typeInfo = type.GetField (baseInfo.Name, replaceableFlags);
621 if (typeInfo != null && typeInfo.DeclaringType == type) {
622 partialNameOverride [typeInfo.Name] = true;
630 internal bool CheckPartialBaseProperties (Type type, Type baseType)
632 bool rebuild = false;
634 foreach (PropertyInfo baseInfo in baseType.GetProperties ()) {
635 PropertyInfo typeInfo = type.GetProperty (baseInfo.Name);
637 if (typeInfo != null && typeInfo.DeclaringType == type) {
638 partialNameOverride [typeInfo.Name] = true;
647 internal CodeDomProvider Provider {
648 get { return provider; }
649 set { provider = value; }
652 internal ICodeCompiler Compiler {
653 get { return compiler; }
654 set { compiler = value; }
657 internal CompilerParameters CompilerParameters {
659 if (compilerParameters == null)
660 compilerParameters = new CompilerParameters ();
662 return compilerParameters;
665 set { compilerParameters = value; }
668 internal CodeCompileUnit CompileUnit {
672 internal TemplateParser Parser {
673 get { return parser; }