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 fileText = File.ReadAllText (inFile);
451 StringWriter writer = new StringWriter();
452 provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
453 fileText = writer.ToString ();
455 throw new CompilationException (parser.InputFile, errors, fileText);
458 protected string DynamicDir ()
460 return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
463 [MonoTODO ("find out how to extract the warningLevel and compilerOptions in the <system.codedom> case")]
464 public virtual Type GetCompiledType ()
466 Type type = CachingCompiler.GetTypeFromCache (parser.InputFile);
471 string lang = parser.Language;
473 CompilationSection config = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
474 Compiler comp = config.Compilers[lang];
476 string compilerOptions = "";
477 int warningLevel = 0;
480 CompilerInfo info = CodeDomProvider.GetCompilerInfo (lang);
481 if (info != null && info.IsCodeDomProviderTypeValid)
482 provider = info.CreateProvider ();
484 // XXX there's no way to get
485 // warningLevel or compilerOptions out
486 // of the provider.. they're in the
487 // configuration section, though.
490 Type t = Type.GetType (comp.Type, true);
491 provider = Activator.CreateInstance (t) as CodeDomProvider;
493 compilerOptions = comp.CompilerOptions;
494 warningLevel = comp.WarningLevel;
498 CompilationConfiguration config;
500 config = CompilationConfiguration.GetInstance (parser.Context);
501 provider = config.GetProvider (lang);
503 string compilerOptions = config.GetCompilerOptions (lang);
504 int warningLevel = config.GetWarningLevel (lang);
506 if (provider == null)
507 throw new HttpException ("Configuration error. Language not supported: " +
510 compiler = provider.CreateCompiler ();
512 compilerParameters.IncludeDebugInformation = parser.Debug;
513 compilerParameters.CompilerOptions = compilerOptions + " " + parser.CompilerOptions;
515 compilerParameters.WarningLevel = warningLevel;
516 bool keepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
518 string tempdir = config.TempDirectory;
519 if (tempdir == null || tempdir == "")
520 tempdir = DynamicDir ();
522 TempFileCollection tempcoll = new TempFileCollection (tempdir, keepFiles);
523 compilerParameters.TempFiles = tempcoll;
524 string dllfilename = Path.GetFileName (tempcoll.AddExtension ("dll", true));
525 compilerParameters.OutputAssembly = Path.Combine (DynamicDir (), dllfilename);
527 CompilerResults results = CachingCompiler.Compile (this);
528 CheckCompilerErrors (results);
529 Assembly assembly = results.CompiledAssembly;
530 if (assembly == null) {
531 if (!File.Exists (compilerParameters.OutputAssembly)) {
532 results.TempFiles.Delete ();
533 throw new CompilationException (parser.InputFile, results.Errors,
534 "No assembly returned after compilation!?");
537 assembly = Assembly.LoadFrom (compilerParameters.OutputAssembly);
540 results.TempFiles.Delete ();
541 Type mainClassType = assembly.GetType (mainClassExpr.Type.BaseType, true);
544 if (parser.IsPartial) {
545 // With the partial classes, we need to make sure we
546 // don't have any methods that should have not been
547 // created (because they are accessible from the base
548 // types). We cannot do this normally because the
549 // codebehind file is actually a partial class and we
550 // have no way of identifying the partial class' base
552 if (!isRebuilding && CheckPartialBaseType (mainClassType)) {
554 parser.RootBuilder.ResetState ();
555 return GetCompiledType ();
560 return mainClassType;
564 internal bool IsRebuildingPartial
566 get { return isRebuilding; }
569 internal bool CheckPartialBaseType (Type type)
571 // Get the base type. If we don't have any (bad thing), we
572 // don't need to replace ourselves. Also check for the
573 // core file, since that won't have any either.
574 Type baseType = type.BaseType;
575 if (baseType == null || baseType == typeof(System.Web.UI.Page))
578 bool rebuild = false;
580 if (CheckPartialBaseFields (type, baseType))
583 if (CheckPartialBaseProperties (type, baseType))
589 internal bool CheckPartialBaseFields (Type type, Type baseType)
591 bool rebuild = false;
593 foreach (FieldInfo baseInfo in baseType.GetFields (replaceableFlags)) {
594 if (baseInfo.IsPrivate)
597 FieldInfo typeInfo = type.GetField (baseInfo.Name, replaceableFlags);
599 if (typeInfo != null && typeInfo.DeclaringType == type) {
600 partialNameOverride [typeInfo.Name] = true;
608 internal bool CheckPartialBaseProperties (Type type, Type baseType)
610 bool rebuild = false;
612 foreach (PropertyInfo baseInfo in baseType.GetProperties ()) {
613 PropertyInfo typeInfo = type.GetProperty (baseInfo.Name);
615 if (typeInfo != null && typeInfo.DeclaringType == type) {
616 partialNameOverride [typeInfo.Name] = true;
625 internal CompilerParameters CompilerParameters {
626 get { return compilerParameters; }
629 internal CodeCompileUnit Unit {
633 internal virtual ICodeCompiler Compiler {
634 get { return compiler; }
637 internal TemplateParser Parser {
638 get { return parser; }