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) {
78 string partialns = null;
79 string partialclasstype = parser.PartialClassName;
81 int partialdot = partialclasstype.LastIndexOf ('.');
82 if (partialdot != -1) {
83 partialns = partialclasstype.Substring (0, partialdot);
84 partialclasstype = partialclasstype.Substring (partialdot + 1);
87 CodeNamespace partialNS = new CodeNamespace (partialns);
88 partialClass = new CodeTypeDeclaration (partialclasstype);
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);
98 string mainclasstype = parser.ClassName;
99 string mainns = "ASP";
102 int maindot = mainclasstype.LastIndexOf ('.');
104 mainns = mainclasstype.Substring (0, maindot);
105 mainclasstype = mainclasstype.Substring (maindot + 1);
109 mainNS = new CodeNamespace (mainns);
110 mainClass = new CodeTypeDeclaration (mainclasstype);
111 CodeTypeReference baseTypeRef;
113 if (partialClass != null) {
114 baseTypeRef = new CodeTypeReference (parser.PartialClassName);
115 baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
117 baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
118 if (parser.BaseTypeIsGlobal)
119 baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
122 baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
124 mainClass.BaseTypes.Add (baseTypeRef);
126 mainClassExpr = new CodeTypeReferenceExpression (mainns + "." + mainclasstype);
128 unit.Namespaces.Add (mainNS);
129 mainClass.TypeAttributes = TypeAttributes.Public;
130 mainNS.Types.Add (mainClass);
132 foreach (object o in parser.Imports) {
134 mainNS.Imports.Add (new CodeNamespaceImport ((string) o));
137 // StringCollection.Contains has O(n) complexity, but
138 // considering the number of comparisons we make on
139 // average and the fact that using an intermediate array
140 // would be even more costly, this is fine here.
141 StringCollection refAsm = unit.ReferencedAssemblies;
143 if (parser.Assemblies != null) {
144 foreach (object o in parser.Assemblies) {
145 asmName = o as string;
146 if (asmName != null && !refAsm.Contains (asmName))
147 refAsm.Add (asmName);
152 ArrayList al = WebConfigurationManager.ExtraAssemblies;
153 if (al != null && al.Count > 0) {
154 foreach (object o in al) {
155 asmName = o as string;
156 if (asmName != null && !refAsm.Contains (asmName))
157 refAsm.Add (asmName);
161 IList list = BuildManager.CodeAssemblies;
162 if (list != null && list.Count > 0) {
164 foreach (object o in list) {
168 asmName = asm.Location;
169 if (asmName != null && !refAsm.Contains (asmName))
170 refAsm.Add (asmName);
174 // Late-bound generators specifics (as for MonoBASIC/VB.NET)
175 unit.UserData["RequireVariableDeclaration"] = parser.ExplicitOn;
176 unit.UserData["AllowLateBound"] = !parser.StrictOn;
179 AddClassAttributes ();
180 CreateStaticFields ();
181 AddApplicationAndSessionObjects ();
184 CreateConstructor (null, null);
188 internal CodeDomProvider Provider {
189 get { return provider; }
192 internal CodeCompileUnit CompileUnit {
196 protected virtual void CreateStaticFields ()
198 CodeMemberField fld = new CodeMemberField (typeof (bool), "__initialized");
199 fld.Attributes = MemberAttributes.Private | MemberAttributes.Static;
200 fld.InitExpression = new CodePrimitiveExpression (false);
201 mainClass.Members.Add (fld);
205 void AssignAppRelativeVirtualPath (CodeConstructor ctor)
207 Type baseType = parser.CodeFileBaseClassType;
209 if (baseType == null)
210 baseType = parser.BaseType;
211 if (baseType == null)
213 if (!baseType.IsSubclassOf (typeof (System.Web.UI.TemplateControl)))
216 string arvp = Path.Combine (parser.BaseVirtualDir, Path.GetFileName (parser.InputFile));
217 if (VirtualPathUtility.IsAbsolute (arvp))
220 CodeExpression cast = new CodeCastExpression (baseType, new CodeThisReferenceExpression ());
221 CodePropertyReferenceExpression arvpProp = new CodePropertyReferenceExpression (cast, "AppRelativeVirtualPath");
222 CodeAssignStatement arvpAssign = new CodeAssignStatement ();
223 arvpAssign.Left = arvpProp;
224 arvpAssign.Right = new CodePrimitiveExpression (VirtualPathUtility.RemoveTrailingSlash (arvp));
225 ctor.Statements.Add (arvpAssign);
229 protected virtual void CreateConstructor (CodeStatementCollection localVars,
230 CodeStatementCollection trueStmt)
232 CodeConstructor ctor = new CodeConstructor ();
233 ctor.Attributes = MemberAttributes.Public;
234 mainClass.Members.Add (ctor);
237 AssignAppRelativeVirtualPath (ctor);
239 if (localVars != null)
240 ctor.Statements.AddRange (localVars);
242 CodeTypeReferenceExpression r;
244 if (parser.IsPartial)
245 r = new CodeTypeReferenceExpression (mainClass.Name);
248 r = new CodeTypeReferenceExpression (mainNS.Name + "." + mainClass.Name);
249 CodeFieldReferenceExpression initialized;
250 initialized = new CodeFieldReferenceExpression (r, "__initialized");
252 CodeBinaryOperatorExpression bin;
253 bin = new CodeBinaryOperatorExpression (initialized,
254 CodeBinaryOperatorType.ValueEquality,
255 new CodePrimitiveExpression (false));
257 CodeAssignStatement assign = new CodeAssignStatement (initialized,
258 new CodePrimitiveExpression (true));
260 CodeConditionStatement cond = new CodeConditionStatement (bin, assign);
261 if (trueStmt != null)
262 cond.TrueStatements.AddRange (trueStmt);
264 ctor.Statements.Add (cond);
269 if (parser.Scripts == null || parser.Scripts.Count == 0)
272 foreach (object o in parser.Scripts) {
274 mainClass.Members.Add (new CodeSnippetTypeMember ((string) o));
278 protected internal virtual void CreateMethods ()
283 void InternalCreatePageProperty (string retType, string name, string contextProperty)
285 CodeMemberProperty property = new CodeMemberProperty ();
286 property.Name = name;
287 property.Type = new CodeTypeReference (retType);
288 property.Attributes = MemberAttributes.Family | MemberAttributes.Final;
290 CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
291 CodeCastExpression cast = new CodeCastExpression ();
292 ret.Expression = cast;
294 CodePropertyReferenceExpression refexp = new CodePropertyReferenceExpression ();
295 refexp.TargetObject = new CodePropertyReferenceExpression (new CodeThisReferenceExpression (), "Context");
296 refexp.PropertyName = contextProperty;
298 cast.TargetType = new CodeTypeReference (retType);
299 cast.Expression = refexp;
301 property.GetStatements.Add (ret);
302 if (partialClass == null)
303 mainClass.Members.Add (property);
305 partialClass.Members.Add (property);
308 protected void CreateProfileProperty ()
311 if (AppCodeCompiler.HaveCustomProfile (WebConfigurationManager.GetSection ("system.web/profile") as ProfileSection))
312 retType = "ProfileCommon";
314 retType = "System.Web.Profile.DefaultProfile";
315 InternalCreatePageProperty (retType, "Profile", "Profile");
319 protected virtual void AddInterfaces ()
321 if (parser.Interfaces == null)
324 foreach (object o in parser.Interfaces) {
326 mainClass.BaseTypes.Add (new CodeTypeReference ((string) o));
330 protected virtual void AddClassAttributes ()
334 protected virtual void AddApplicationAndSessionObjects ()
338 /* Utility methods for <object> stuff */
339 protected void CreateApplicationOrSessionPropertyForObject (Type type,
344 /* if isApplication this generates (the 'cachedapp' field is created earlier):
345 private MyNS.MyClass app {
347 if ((this.cachedapp == null)) {
348 this.cachedapp = ((MyNS.MyClass)
349 (this.Application.StaticObjects.GetObject("app")));
351 return this.cachedapp;
355 else, this is for Session:
356 private MyNS.MyClass ses {
358 return ((MyNS.MyClass) (this.Session.StaticObjects.GetObject("ses")));
364 CodeExpression result = null;
366 CodeMemberProperty prop = new CodeMemberProperty ();
367 prop.Type = new CodeTypeReference (type);
368 prop.Name = propName;
370 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
372 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
374 CodePropertyReferenceExpression p1;
376 p1 = new CodePropertyReferenceExpression (thisRef, "Application");
378 p1 = new CodePropertyReferenceExpression (thisRef, "Session");
380 CodePropertyReferenceExpression p2;
381 p2 = new CodePropertyReferenceExpression (p1, "StaticObjects");
383 CodeMethodReferenceExpression getobject;
384 getobject = new CodeMethodReferenceExpression (p2, "GetObject");
386 CodeMethodInvokeExpression invoker;
387 invoker = new CodeMethodInvokeExpression (getobject,
388 new CodePrimitiveExpression (propName));
390 CodeCastExpression cast = new CodeCastExpression (prop.Type, invoker);
393 CodeFieldReferenceExpression field;
394 field = new CodeFieldReferenceExpression (thisRef, "cached" + propName);
396 CodeConditionStatement stmt = new CodeConditionStatement();
397 stmt.Condition = new CodeBinaryOperatorExpression (field,
398 CodeBinaryOperatorType.IdentityEquality,
399 new CodePrimitiveExpression (null));
401 CodeAssignStatement assign = new CodeAssignStatement ();
404 stmt.TrueStatements.Add (assign);
405 prop.GetStatements.Add (stmt);
411 prop.GetStatements.Add (new CodeMethodReturnStatement (result));
412 mainClass.Members.Add (prop);
415 protected string CreateFieldForObject (Type type, string name)
417 string fieldName = "cached" + name;
418 CodeMemberField f = new CodeMemberField (type, fieldName);
419 f.Attributes = MemberAttributes.Private;
420 mainClass.Members.Add (f);
424 protected void CreatePropertyForObject (Type type, string propName, string fieldName, bool isPublic)
426 CodeFieldReferenceExpression field = new CodeFieldReferenceExpression (thisRef, fieldName);
427 CodeMemberProperty prop = new CodeMemberProperty ();
428 prop.Type = new CodeTypeReference (type);
429 prop.Name = propName;
431 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
433 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
435 CodeConditionStatement stmt = new CodeConditionStatement();
436 stmt.Condition = new CodeBinaryOperatorExpression (field,
437 CodeBinaryOperatorType.IdentityEquality,
438 new CodePrimitiveExpression (null));
440 CodeObjectCreateExpression create = new CodeObjectCreateExpression (prop.Type);
441 stmt.TrueStatements.Add (new CodeAssignStatement (field, create));
442 prop.GetStatements.Add (stmt);
443 prop.GetStatements.Add (new CodeMethodReturnStatement (field));
445 mainClass.Members.Add (prop);
449 void CheckCompilerErrors (CompilerResults results)
451 if (results.NativeCompilerReturnValue == 0)
454 string fileText = null;
455 CompilerErrorCollection errors = results.Errors;
456 CompilerError ce = (errors != null && errors.Count > 0) ? errors [0] : null;
457 string inFile = (ce != null) ? ce.FileName : null;
459 if (inFile != null && File.Exists (inFile)) {
460 using (StreamReader sr = File.OpenText (inFile)) {
461 fileText = sr.ReadToEnd ();
464 StringWriter writer = new StringWriter();
465 provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
466 fileText = writer.ToString ();
468 throw new CompilationException (parser.InputFile, errors, fileText);
471 protected string DynamicDir ()
473 return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
476 [MonoTODO ("find out how to extract the warningLevel and compilerOptions in the <system.codedom> case")]
477 public virtual Type GetCompiledType ()
479 Type type = CachingCompiler.GetTypeFromCache (parser.InputFile);
484 string lang = parser.Language;
486 CompilationSection config = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
487 Compiler comp = config.Compilers[lang];
489 string compilerOptions = "";
490 int warningLevel = 0;
493 CompilerInfo info = CodeDomProvider.GetCompilerInfo (lang);
494 if (info != null && info.IsCodeDomProviderTypeValid)
495 provider = info.CreateProvider ();
497 // XXX there's no way to get
498 // warningLevel or compilerOptions out
499 // of the provider.. they're in the
500 // configuration section, though.
502 Type t = HttpApplication.LoadType (comp.Type, true);
503 provider = Activator.CreateInstance (t) as CodeDomProvider;
505 compilerOptions = comp.CompilerOptions;
506 warningLevel = comp.WarningLevel;
509 CompilationConfiguration config;
511 config = CompilationConfiguration.GetInstance (parser.Context);
512 provider = config.GetProvider (lang);
514 string compilerOptions = config.GetCompilerOptions (lang);
515 int warningLevel = config.GetWarningLevel (lang);
517 if (provider == null)
518 throw new HttpException ("Configuration error. Language not supported: " +
522 compiler = provider.CreateCompiler ();
525 compilerParameters.IncludeDebugInformation = parser.Debug;
526 compilerParameters.CompilerOptions = compilerOptions + " " + parser.CompilerOptions;
528 compilerParameters.WarningLevel = warningLevel;
529 bool keepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
531 string tempdir = config.TempDirectory;
532 if (tempdir == null || tempdir == "")
533 tempdir = DynamicDir ();
535 TempFileCollection tempcoll = new TempFileCollection (tempdir, keepFiles);
536 compilerParameters.TempFiles = tempcoll;
537 string dllfilename = Path.GetFileName (tempcoll.AddExtension ("dll", true));
538 compilerParameters.OutputAssembly = Path.Combine (DynamicDir (), dllfilename);
540 CompilerResults results = CachingCompiler.Compile (this);
541 CheckCompilerErrors (results);
542 Assembly assembly = results.CompiledAssembly;
543 if (assembly == null) {
544 if (!File.Exists (compilerParameters.OutputAssembly)) {
545 results.TempFiles.Delete ();
546 throw new CompilationException (parser.InputFile, results.Errors,
547 "No assembly returned after compilation!?");
550 assembly = Assembly.LoadFrom (compilerParameters.OutputAssembly);
553 results.TempFiles.Delete ();
554 Type mainClassType = assembly.GetType (mainClassExpr.Type.BaseType, true);
557 if (parser.IsPartial) {
558 // With the partial classes, we need to make sure we
559 // don't have any methods that should have not been
560 // created (because they are accessible from the base
561 // types). We cannot do this normally because the
562 // codebehind file is actually a partial class and we
563 // have no way of identifying the partial class' base
565 if (!isRebuilding && CheckPartialBaseType (mainClassType)) {
567 parser.RootBuilder.ResetState ();
568 return GetCompiledType ();
573 return mainClassType;
577 internal bool IsRebuildingPartial
579 get { return isRebuilding; }
582 internal bool CheckPartialBaseType (Type type)
584 // Get the base type. If we don't have any (bad thing), we
585 // don't need to replace ourselves. Also check for the
586 // core file, since that won't have any either.
587 Type baseType = type.BaseType;
588 if (baseType == null || baseType == typeof(System.Web.UI.Page))
591 bool rebuild = false;
593 if (CheckPartialBaseFields (type, baseType))
596 if (CheckPartialBaseProperties (type, baseType))
602 internal bool CheckPartialBaseFields (Type type, Type baseType)
604 bool rebuild = false;
606 foreach (FieldInfo baseInfo in baseType.GetFields (replaceableFlags)) {
607 if (baseInfo.IsPrivate)
610 FieldInfo typeInfo = type.GetField (baseInfo.Name, replaceableFlags);
612 if (typeInfo != null && typeInfo.DeclaringType == type) {
613 partialNameOverride [typeInfo.Name] = true;
621 internal bool CheckPartialBaseProperties (Type type, Type baseType)
623 bool rebuild = false;
625 foreach (PropertyInfo baseInfo in baseType.GetProperties ()) {
626 PropertyInfo typeInfo = type.GetProperty (baseInfo.Name);
628 if (typeInfo != null && typeInfo.DeclaringType == type) {
629 partialNameOverride [typeInfo.Name] = true;
638 internal CompilerParameters CompilerParameters {
639 get { return compilerParameters; }
642 internal CodeCompileUnit Unit {
646 internal virtual ICodeCompiler Compiler {
647 get { return compiler; }
650 internal TemplateParser Parser {
651 get { return parser; }