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)
69 compilerParameters = new CompilerParameters ();
75 unit = new CodeCompileUnit ();
77 if (parser.IsPartial) {
79 string classtype = parser.PartialClassName;
81 if (classtype.Contains (".")) {
82 int dot = classtype.LastIndexOf (".");
83 ns = classtype.Substring (0, dot);
84 classtype = classtype.Substring (dot + 1);
87 CodeNamespace partialNS = new CodeNamespace (ns);
88 partialClass = new CodeTypeDeclaration (classtype);
89 partialClass.IsPartial = true;
90 partialClassExpr = new CodeTypeReferenceExpression (parser.PartialClassName);
92 unit.Namespaces.Add (partialNS);
93 partialClass.TypeAttributes = TypeAttributes.Public;
94 partialNS.Types.Add (partialClass);
97 mainNS = new CodeNamespace ("ASP");
98 mainClass = new CodeTypeDeclaration (parser.ClassName);
99 CodeTypeReference baseTypeRef;
101 if (partialClass != null) {
102 baseTypeRef = new CodeTypeReference (parser.PartialClassName);
103 baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
105 baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
106 if (parser.BaseTypeIsGlobal)
107 baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
110 baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
112 mainClass.BaseTypes.Add (baseTypeRef);
114 mainClassExpr = new CodeTypeReferenceExpression ("ASP." + parser.ClassName);
116 unit.Namespaces.Add (mainNS);
117 mainClass.TypeAttributes = TypeAttributes.Public;
118 mainNS.Types.Add (mainClass);
120 foreach (object o in parser.Imports) {
122 mainNS.Imports.Add (new CodeNamespaceImport ((string) o));
125 // StringCollection.Contains has O(n) complexity, but
126 // considering the number of comparisons we make on
127 // average and the fact that using an intermediate array
128 // would be even more costly, this is fine here.
129 StringCollection refAsm = unit.ReferencedAssemblies;
131 if (parser.Assemblies != null) {
132 foreach (object o in parser.Assemblies) {
133 asmName = o as string;
134 if (asmName != null && !refAsm.Contains (asmName))
135 refAsm.Add (asmName);
140 ArrayList al = WebConfigurationManager.ExtraAssemblies;
141 if (al != null && al.Count > 0) {
142 foreach (object o in al) {
143 asmName = o as string;
144 if (asmName != null && !refAsm.Contains (asmName))
145 refAsm.Add (asmName);
149 IList list = BuildManager.CodeAssemblies;
150 if (list != null && list.Count > 0) {
152 foreach (object o in list) {
156 asmName = asm.Location;
157 if (asmName != null && !refAsm.Contains (asmName))
158 refAsm.Add (asmName);
162 // Late-bound generators specifics (as for MonoBASIC/VB.NET)
163 unit.UserData["RequireVariableDeclaration"] = parser.ExplicitOn;
164 unit.UserData["AllowLateBound"] = !parser.StrictOn;
167 AddClassAttributes ();
168 CreateStaticFields ();
169 AddApplicationAndSessionObjects ();
172 CreateConstructor (null, null);
176 internal CodeDomProvider Provider {
177 get { return provider; }
180 internal CodeCompileUnit CompileUnit {
184 protected virtual void CreateStaticFields ()
186 CodeMemberField fld = new CodeMemberField (typeof (bool), "__initialized");
187 fld.Attributes = MemberAttributes.Private | MemberAttributes.Static;
188 fld.InitExpression = new CodePrimitiveExpression (false);
189 mainClass.Members.Add (fld);
193 void AssignAppRelativeVirtualPath (CodeConstructor ctor)
195 Type baseType = parser.CodeFileBaseClassType;
197 if (baseType == null)
198 baseType = parser.BaseType;
199 if (baseType == null)
201 if (!baseType.IsSubclassOf (typeof (System.Web.UI.TemplateControl)))
204 string arvp = Path.Combine (parser.BaseVirtualDir, Path.GetFileName (parser.InputFile));
205 if (VirtualPathUtility.IsAbsolute (arvp))
208 CodeExpression cast = new CodeCastExpression (baseType, new CodeThisReferenceExpression ());
209 CodePropertyReferenceExpression arvpProp = new CodePropertyReferenceExpression (cast, "AppRelativeVirtualPath");
210 CodeAssignStatement arvpAssign = new CodeAssignStatement ();
211 arvpAssign.Left = arvpProp;
212 arvpAssign.Right = new CodePrimitiveExpression (arvp);
213 ctor.Statements.Add (arvpAssign);
217 protected virtual void CreateConstructor (CodeStatementCollection localVars,
218 CodeStatementCollection trueStmt)
220 CodeConstructor ctor = new CodeConstructor ();
221 ctor.Attributes = MemberAttributes.Public;
222 mainClass.Members.Add (ctor);
225 AssignAppRelativeVirtualPath (ctor);
227 if (localVars != null)
228 ctor.Statements.AddRange (localVars);
230 CodeTypeReferenceExpression r;
232 if (parser.IsPartial)
233 r = new CodeTypeReferenceExpression (mainClass.Name);
236 r = new CodeTypeReferenceExpression (mainNS.Name + "." + mainClass.Name);
237 CodeFieldReferenceExpression initialized;
238 initialized = new CodeFieldReferenceExpression (r, "__initialized");
240 CodeBinaryOperatorExpression bin;
241 bin = new CodeBinaryOperatorExpression (initialized,
242 CodeBinaryOperatorType.ValueEquality,
243 new CodePrimitiveExpression (false));
245 CodeAssignStatement assign = new CodeAssignStatement (initialized,
246 new CodePrimitiveExpression (true));
248 CodeConditionStatement cond = new CodeConditionStatement (bin, assign);
249 if (trueStmt != null)
250 cond.TrueStatements.AddRange (trueStmt);
252 ctor.Statements.Add (cond);
257 if (parser.Scripts == null || parser.Scripts.Count == 0)
260 foreach (object o in parser.Scripts) {
262 mainClass.Members.Add (new CodeSnippetTypeMember ((string) o));
266 protected internal virtual void CreateMethods ()
271 void InternalCreatePageProperty (string retType, string name, string contextProperty)
273 CodeMemberProperty property = new CodeMemberProperty ();
274 property.Name = name;
275 property.Type = new CodeTypeReference (retType);
276 property.Attributes = MemberAttributes.Family | MemberAttributes.Final;
278 CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
279 CodeCastExpression cast = new CodeCastExpression ();
280 ret.Expression = cast;
282 CodePropertyReferenceExpression refexp = new CodePropertyReferenceExpression ();
283 refexp.TargetObject = new CodePropertyReferenceExpression (new CodeThisReferenceExpression (), "Context");
284 refexp.PropertyName = contextProperty;
286 cast.TargetType = new CodeTypeReference (retType);
287 cast.Expression = refexp;
289 property.GetStatements.Add (ret);
290 if (partialClass == null)
291 mainClass.Members.Add (property);
293 partialClass.Members.Add (property);
296 protected void CreateProfileProperty ()
299 ProfileSection ps = WebConfigurationManager.GetSection ("system.web/profile") as ProfileSection;
300 if (ps != null && ps.PropertySettings.Count > 0)
301 retType = "ProfileCommon";
303 retType = "System.Web.Profile.DefaultProfile";
304 InternalCreatePageProperty (retType, "Profile", "Profile");
308 protected virtual void AddInterfaces ()
310 if (parser.Interfaces == null)
313 foreach (object o in parser.Interfaces) {
315 mainClass.BaseTypes.Add (new CodeTypeReference ((string) o));
319 protected virtual void AddClassAttributes ()
323 protected virtual void AddApplicationAndSessionObjects ()
327 /* Utility methods for <object> stuff */
328 protected void CreateApplicationOrSessionPropertyForObject (Type type,
333 /* if isApplication this generates (the 'cachedapp' field is created earlier):
334 private MyNS.MyClass app {
336 if ((this.cachedapp == null)) {
337 this.cachedapp = ((MyNS.MyClass)
338 (this.Application.StaticObjects.GetObject("app")));
340 return this.cachedapp;
344 else, this is for Session:
345 private MyNS.MyClass ses {
347 return ((MyNS.MyClass) (this.Session.StaticObjects.GetObject("ses")));
353 CodeExpression result = null;
355 CodeMemberProperty prop = new CodeMemberProperty ();
356 prop.Type = new CodeTypeReference (type);
357 prop.Name = propName;
359 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
361 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
363 CodePropertyReferenceExpression p1;
365 p1 = new CodePropertyReferenceExpression (thisRef, "Application");
367 p1 = new CodePropertyReferenceExpression (thisRef, "Session");
369 CodePropertyReferenceExpression p2;
370 p2 = new CodePropertyReferenceExpression (p1, "StaticObjects");
372 CodeMethodReferenceExpression getobject;
373 getobject = new CodeMethodReferenceExpression (p2, "GetObject");
375 CodeMethodInvokeExpression invoker;
376 invoker = new CodeMethodInvokeExpression (getobject,
377 new CodePrimitiveExpression (propName));
379 CodeCastExpression cast = new CodeCastExpression (prop.Type, invoker);
382 CodeFieldReferenceExpression field;
383 field = new CodeFieldReferenceExpression (thisRef, "cached" + propName);
385 CodeConditionStatement stmt = new CodeConditionStatement();
386 stmt.Condition = new CodeBinaryOperatorExpression (field,
387 CodeBinaryOperatorType.IdentityEquality,
388 new CodePrimitiveExpression (null));
390 CodeAssignStatement assign = new CodeAssignStatement ();
393 stmt.TrueStatements.Add (assign);
394 prop.GetStatements.Add (stmt);
400 prop.GetStatements.Add (new CodeMethodReturnStatement (result));
401 mainClass.Members.Add (prop);
404 protected string CreateFieldForObject (Type type, string name)
406 string fieldName = "cached" + name;
407 CodeMemberField f = new CodeMemberField (type, fieldName);
408 f.Attributes = MemberAttributes.Private;
409 mainClass.Members.Add (f);
413 protected void CreatePropertyForObject (Type type, string propName, string fieldName, bool isPublic)
415 CodeFieldReferenceExpression field = new CodeFieldReferenceExpression (thisRef, fieldName);
416 CodeMemberProperty prop = new CodeMemberProperty ();
417 prop.Type = new CodeTypeReference (type);
418 prop.Name = propName;
420 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
422 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
424 CodeConditionStatement stmt = new CodeConditionStatement();
425 stmt.Condition = new CodeBinaryOperatorExpression (field,
426 CodeBinaryOperatorType.IdentityEquality,
427 new CodePrimitiveExpression (null));
429 CodeObjectCreateExpression create = new CodeObjectCreateExpression (prop.Type);
430 stmt.TrueStatements.Add (new CodeAssignStatement (field, create));
431 prop.GetStatements.Add (stmt);
432 prop.GetStatements.Add (new CodeMethodReturnStatement (field));
434 mainClass.Members.Add (prop);
438 void CheckCompilerErrors (CompilerResults results)
440 if (results.NativeCompilerReturnValue == 0)
443 string fileText = null;
444 CompilerErrorCollection errors = results.Errors;
445 CompilerError ce = (errors != null && errors.Count > 0) ? errors [0] : null;
446 string inFile = (ce != null) ? ce.FileName : null;
448 if (inFile != null && File.Exists (inFile)) {
449 using (StreamReader sr = File.OpenText (inFile)) {
450 fileText = sr.ReadToEnd ();
453 StringWriter writer = new StringWriter();
454 provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
455 fileText = writer.ToString ();
457 throw new CompilationException (parser.InputFile, errors, fileText);
460 protected string DynamicDir ()
462 return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
465 [MonoTODO ("find out how to extract the warningLevel and compilerOptions in the <system.codedom> case")]
466 public virtual Type GetCompiledType ()
468 Type type = CachingCompiler.GetTypeFromCache (parser.InputFile);
473 string lang = parser.Language;
475 CompilationSection config = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
476 Compiler comp = config.Compilers[lang];
478 string compilerOptions = "";
479 int warningLevel = 0;
482 CompilerInfo info = CodeDomProvider.GetCompilerInfo (lang);
483 if (info != null && info.IsCodeDomProviderTypeValid)
484 provider = info.CreateProvider ();
486 // XXX there's no way to get
487 // warningLevel or compilerOptions out
488 // of the provider.. they're in the
489 // configuration section, though.
492 Type t = Type.GetType (comp.Type, true);
493 provider = Activator.CreateInstance (t) as CodeDomProvider;
495 compilerOptions = comp.CompilerOptions;
496 warningLevel = comp.WarningLevel;
500 CompilationConfiguration config;
502 config = CompilationConfiguration.GetInstance (parser.Context);
503 provider = config.GetProvider (lang);
505 string compilerOptions = config.GetCompilerOptions (lang);
506 int warningLevel = config.GetWarningLevel (lang);
508 if (provider == null)
509 throw new HttpException ("Configuration error. Language not supported: " +
512 compiler = provider.CreateCompiler ();
514 compilerParameters.IncludeDebugInformation = parser.Debug;
515 compilerParameters.CompilerOptions = compilerOptions + " " + parser.CompilerOptions;
517 compilerParameters.WarningLevel = warningLevel;
518 bool keepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
520 string tempdir = config.TempDirectory;
521 if (tempdir == null || tempdir == "")
522 tempdir = DynamicDir ();
524 TempFileCollection tempcoll = new TempFileCollection (tempdir, keepFiles);
525 compilerParameters.TempFiles = tempcoll;
526 string dllfilename = Path.GetFileName (tempcoll.AddExtension ("dll", true));
527 compilerParameters.OutputAssembly = Path.Combine (DynamicDir (), dllfilename);
529 CompilerResults results = CachingCompiler.Compile (this);
530 CheckCompilerErrors (results);
531 Assembly assembly = results.CompiledAssembly;
532 if (assembly == null) {
533 if (!File.Exists (compilerParameters.OutputAssembly)) {
534 results.TempFiles.Delete ();
535 throw new CompilationException (parser.InputFile, results.Errors,
536 "No assembly returned after compilation!?");
539 assembly = Assembly.LoadFrom (compilerParameters.OutputAssembly);
542 results.TempFiles.Delete ();
543 Type mainClassType = assembly.GetType (mainClassExpr.Type.BaseType, true);
546 if (parser.IsPartial) {
547 // With the partial classes, we need to make sure we
548 // don't have any methods that should have not been
549 // created (because they are accessible from the base
550 // types). We cannot do this normally because the
551 // codebehind file is actually a partial class and we
552 // have no way of identifying the partial class' base
554 if (!isRebuilding && CheckPartialBaseType (mainClassType)) {
556 parser.RootBuilder.ResetState ();
557 return GetCompiledType ();
562 return mainClassType;
566 internal bool IsRebuildingPartial
568 get { return isRebuilding; }
571 internal bool CheckPartialBaseType (Type type)
573 // Get the base type. If we don't have any (bad thing), we
574 // don't need to replace ourselves. Also check for the
575 // core file, since that won't have any either.
576 Type baseType = type.BaseType;
577 if (baseType == null || baseType == typeof(System.Web.UI.Page))
580 bool rebuild = false;
582 if (CheckPartialBaseFields (type, baseType))
585 if (CheckPartialBaseProperties (type, baseType))
591 internal bool CheckPartialBaseFields (Type type, Type baseType)
593 bool rebuild = false;
595 foreach (FieldInfo baseInfo in baseType.GetFields (replaceableFlags)) {
596 if (baseInfo.IsPrivate)
599 FieldInfo typeInfo = type.GetField (baseInfo.Name, replaceableFlags);
601 if (typeInfo != null && typeInfo.DeclaringType == type) {
602 partialNameOverride [typeInfo.Name] = true;
610 internal bool CheckPartialBaseProperties (Type type, Type baseType)
612 bool rebuild = false;
614 foreach (PropertyInfo baseInfo in baseType.GetProperties ()) {
615 PropertyInfo typeInfo = type.GetProperty (baseInfo.Name);
617 if (typeInfo != null && typeInfo.DeclaringType == type) {
618 partialNameOverride [typeInfo.Name] = true;
627 internal CompilerParameters CompilerParameters {
628 get { return compilerParameters; }
631 internal CodeCompileUnit Unit {
635 internal virtual ICodeCompiler Compiler {
636 get { return compiler; }
639 internal TemplateParser Parser {
640 get { return parser; }