/// <summary> /// Called when a <see cref="PHP.Core.AST.MethodDecl"/> AST node is entered during the emit phase. /// </summary> public void EnterMethodDeclaration(PhpMethod/*!*/ method) { bool is_optimized = (method.Properties & RoutineProperties.HasUnoptimizedLocals) == 0; bool rt_variables_table = (method.Properties & RoutineProperties.HasRTVariablesTable) != 0; CompilerLocationStack.TypeDeclContext class_context = locationStack.PeekTypeDecl(); CompilerLocationStack.MethodDeclContext md_context = new CompilerLocationStack.MethodDeclContext(); md_context.Type = class_context.Type; md_context.Method = method; // Set whether access to variables should be generated via locals or table md_context.OptimizedLocals = this.OptimizedLocals; OptimizedLocals = is_optimized; // set compile-time variables table: md_context.CurrentVariablesTable = this.currentVariablesTable; currentVariablesTable = method.Builder.LocalVariables; // set compile-time variables table: md_context.CurrentLabels = this.currentLabels; currentLabels = method.Builder.Labels; // Set the valid method to emit the "return" statement md_context.ReturnsPhpReference = this.ReturnsPhpReference; this.ReturnsPhpReference = method.Signature.AliasReturn; // CallSites (same as in TypeDecl, not changed): //md_context.CallSites = callSites; //this.callSites = new Compiler.CodeGenerator.CallSites(/*class_context = TypeContextPlace*/); // create new IL emitter for the method: md_context.IL = il; il = new ILEmitter(method.ArgFullInfo); // set RT variables table place: md_context.RTVariablesTablePlace = RTVariablesTablePlace; if (rt_variables_table) { LocalBuilder var_table_local = il.DeclareLocal(PhpVariable.RTVariablesTableType); if (sourceUnit.SymbolDocumentWriter != null) var_table_local.SetLocalSymInfo("<locals>"); RTVariablesTablePlace = new Place(var_table_local); } else RTVariablesTablePlace = LiteralPlace.Null; // sets ScriptContext and Self places appropriately: md_context.ClassContextPlace = TypeContextPlace; md_context.ScriptContextPlace = ScriptContextPlace; md_context.SelfPlace = SelfPlace; md_context.LateStaticBindTypePlace = LateStaticBindTypePlace; if (method.IsStatic) { ScriptContextPlace = new IndexedPlace(PlaceHolder.Argument, FunctionBuilder.ArgContextStatic); SelfPlace = LiteralPlace.Null; } else { ScriptContextPlace = new IndexedPlace(PlaceHolder.Argument, FunctionBuilder.ArgContextInstance); if (method.DeclaringPhpType.ProxyFieldInfo != null) { // the real this is not a DObject SelfPlace = new Place(IndexedPlace.ThisArg, method.DeclaringPhpType.ProxyFieldInfo); } else { // the real this is a DObject SelfPlace = IndexedPlace.ThisArg; } } // set Result place and return label: md_context.ResultPlace = ResultPlace; md_context.ReturnLabel = ReturnLabel; ResultPlace = null; LateStaticBindTypePlace = null; // set exception block nesting: md_context.ExceptionBlockNestingLevel = ExceptionBlockNestingLevel; ExceptionBlockNestingLevel = 0; // set current PhpRoutine md_context.PhpRoutine = method; // locationStack.PushMethodDecl(md_context); }
/// <summary> /// Emits stubs for one overridden or implemented PHP method. /// </summary> /// <param name="stubs">Already generated stubs.</param> /// <param name="target">The overriding/implementing method.</param> /// <param name="targetType">The type (perhaps constructed) that declared <paramref name="target"/>.</param> /// <param name="declaringType">The type where the stubs should be emitted.</param> /// <param name="template">The method being overridden/implemented (surely PHP).</param> /// <param name="newSlot"><B>True</B> if the stub should be assigned a new vtable slot, /// <B>false</B> otherwise.</param> /// <remarks> /// This method handles situations where method overriding/implementing does not work by itself because of /// the fact that method names in PHP are case insensitive. /// </remarks> private void EmitOverrideStubsForPhpTemplate(IDictionary<string, MethodBuilder>/*!*/ stubs, PhpMethod/*!*/ target, DType/*!*/ targetType, PhpType/*!*/ declaringType, DMemberRef/*!*/ template, bool newSlot) { PhpMethod php_template = (PhpMethod)template.Member; // Emit method stub if needed here ... (resolve .NET incompatibility of base method and overriding method) // // Until now, several possible cases or their combination are known: // - base and overriding methods match, but their name letter-casing don't (need to define override explicitly to properly Bake the type) // - base and overriding methods name match exactly, but overriding methods has additional arguments (with default values) (in PHP it is allowed) (stub needed) // - ghost stub, where B extends A implements I, where A contains definition of method in I and casing does not match // // if signatures don't match, virtual sealed stub must be created, it only calls the target method // if signatures match, only explicit override must be stated if (target.Name.ToString() != php_template.Name.ToString() || // the names differ (perhaps only in casing) target.Signature.ParamCount != php_template.Signature.ParamCount || // signature was extended (additional arguments added, with implicit value only) target.Signature.AliasReturn != php_template.Signature.AliasReturn // returns PhpReference instead of Object ) { MethodInfo target_argfull = DType.MakeConstructed(target.ArgFullInfo, targetType as ConstructedType); TypeBuilder type_builder = declaringType.RealTypeBuilder; // we have to generate a pass-thru override stub that overrides the template based on // name since it is impossible to install an explicit override of a method declared by // a generic type in v2.0 SRE (feedback ID=97425) bool sre_bug_workaround = (template.Type is ConstructedType); if (target.DeclaringType == declaringType && !sre_bug_workaround && target.Signature.ParamCount == php_template.Signature.ParamCount && target.Signature.AliasReturn == php_template.Signature.AliasReturn) { // signatures match, just install an explicit override if possible type_builder.DefineMethodOverride(target_argfull, DType.MakeConstructed(php_template.ArgFullInfo, template.Type as ConstructedType)); } else { string stubs_key = null; MethodAttributes attrs; if (sre_bug_workaround) { // check whether we have generated a stub having the template name before if (stubs.ContainsKey(stubs_key = "," + php_template.ArgFullInfo.Name)) return; attrs = php_template.ArgFullInfo.Attributes & ~MethodAttributes.Abstract; } else { attrs = MethodAttributes.PrivateScope | MethodAttributes.Virtual; } if (newSlot) attrs |= MethodAttributes.NewSlot; else attrs &= ~MethodAttributes.NewSlot; // determine stub return and parameters type Type return_type; Type[] param_types = php_template.Signature.ToArgfullSignature(1, out return_type); param_types[0] = Types.ScriptContext[0]; MethodBuilder override_stub = type_builder.DefineMethod( (sre_bug_workaround ? php_template.ArgFullInfo.Name : "<Override>"), attrs, return_type, param_types); ILEmitter il = new ILEmitter(override_stub); // // return target( [arg1, ...[, default, ...]] ); // // pass-thru all arguments, including this (arg0) int pass_args = Math.Min(param_types.Length, target.Signature.ParamCount + 1); for (int i = 0; i <= pass_args; ++i) il.Ldarg(i); // this, param1, .... for (int i = pass_args; i <= target.Signature.ParamCount; ++i) { // ... // PhpException.MissingArgument(i, target.FullName); // but in some override it can be optional argument il.Emit(OpCodes.Ldsfld, PHP.Core.Emit.Fields.Arg_Default); // paramN } il.Emit(OpCodes.Callvirt, target_argfull); // return if (target.Signature.AliasReturn != php_template.Signature.AliasReturn) il.Emit(OpCodes.Call, target.Signature.AliasReturn ? Methods.PhpVariable.Dereference // PhpVariable.Deference(obj) : Methods.PhpVariable.MakeReference); // PhpVariable.MakeReference(obj) il.Emit(OpCodes.Ret); if (sre_bug_workaround) { stubs.Add(stubs_key, override_stub); } else { if (!php_template.ArgFullInfo.IsVirtual) throw new InvalidOperationException(string.Format("Cannot override non-virtual method '{0}'!", php_template.ArgFullInfo.Name)); type_builder.DefineMethodOverride(override_stub, DType.MakeConstructed(php_template.ArgFullInfo, template.Type as ConstructedType)); } } } }
/// <summary> /// Emits stubs for all overloads of one exported method. /// </summary> /// <param name="stubs">Already generated stubs.</param> /// <param name="target">The exported method.</param> private void EmitExportStubs(IDictionary<string, MethodBuilder>/*!*/ stubs, PhpMethod/*!*/ target) { Debug.Assert(target.IsExported); string clr_sig = null; MethodAttributes attributes = Reflection.Enums.ToMethodAttributes(target.MemberDesc.MemberAttributes); attributes |= MethodAttributes.HideBySig; foreach (StubInfo stub in ClrStubBuilder.DefineMethodExportStubs( target, target.DeclaringPhpType, attributes, false, delegate(string[] genericParamNames, object[] parameterTypes, object returnType) { // check whether we have not generated this signature before clr_sig = ClrMethod.Overload.ClrSignatureToString(genericParamNames.Length, parameterTypes, returnType); return !stubs.ContainsKey(clr_sig); })) { // set parameter names and attributes ClrStubBuilder.DefineStubParameters(stub.MethodBuilder, null, stub.Parameters); if (!stub.MethodBuilder.IsAbstract) { EmissionContext emission_context = SetupStubPlaces(target.DeclaringPhpType, stub.MethodBuilder.IsStatic); try { // stub body ClrStubBuilder.EmitMethodStubBody( new ILEmitter(stub.MethodBuilder), ScriptContextPlace, stub.Parameters, stub.TypeParameters, stub.ReturnType, target, target.DeclaringType); } finally { RestorePlaces(emission_context); } } stubs.Add(clr_sig, stub.MethodBuilder); } }
/// <summary> /// Emits stubs for overridden/implemented methods and explicit export stubs. /// </summary> /// <param name="method">The overriding/implementing/exported method.</param> /// <remarks> /// If the <paramref name="method"/> implements or overrides a CLR method (or methods), /// appropriate stub(s) are generated and emitted to its declaring type. In addition, /// if the method is exported using the <c>Export</c> pseudo-custom attribute, stub(s) /// created according to its signature (type hints, default params, etc.) are also /// generated. /// </remarks> public void EmitOverrideAndExportStubs(PhpMethod/*!*/ method) { // keep track of the signatures that have already been generated Dictionary<string, MethodBuilder> stubs = null; // emit stub(s) for overridden method(s) if (method.Overrides != null) { stubs = new Dictionary<string, MethodBuilder>(); EmitOverrideStubs(stubs, method, method.DeclaringPhpType, method.DeclaringPhpType, method.Overrides, false); } if (method.Implements != null) { // emit stub(s) for implemented method(s) for (int i = 0; i < method.Implements.Count; i++) { if (stubs == null) stubs = new Dictionary<string, MethodBuilder>(); EmitOverrideStubs(stubs, method, method.DeclaringPhpType, method.DeclaringPhpType, method.Implements[i], true); } } // emit explicit export stubs if (method.IsExported) { if (stubs == null) stubs = new Dictionary<string, MethodBuilder>(); EmitExportStubs(stubs, method); } }
/// <summary> /// Emits stubs for all overloads of one overridden or implemented method. /// </summary> /// <param name="stubs">Already generated stubs.</param> /// <param name="target">The overriding/implementing method.</param> /// <param name="targetType">The type (perhaps constructed) that declared <paramref name="target"/>.</param> /// <param name="declaringType">The type where the stubs should be emitted.</param> /// <param name="template">The method being overridden/implemented.</param> /// <param name="newSlot"><B>True</B> if the stub should be assigned a new vtable slot, /// <B>false</B> otherwise.</param> private void EmitOverrideStubs(IDictionary<string, MethodBuilder>/*!*/ stubs, PhpMethod/*!*/ target, DType/*!*/ targetType, PhpType/*!*/ declaringType, DMemberRef/*!*/ template, bool newSlot) { ClrMethod clr_template = template.Member as ClrMethod; if (clr_template == null) { if (!target.IsStatic) EmitOverrideStubsForPhpTemplate(stubs, target, targetType, declaringType, template, newSlot); return; } // // following code emits stubs in case of CLR base method // ConstructedType constructed_type = template.Type as ConstructedType; TypeBuilder type_builder = declaringType.RealTypeBuilder; // override all virtual non-final overloads foreach (ClrMethod.Overload overload in clr_template.Overloads) { if (overload.Method.IsVirtual && !overload.Method.IsFinal) { // map generic type parameters according to the constructed type Type constructed_return_type; ParameterInfo[] constructed_params = overload.MakeConstructed(constructed_type, out constructed_return_type); // check whether we have not generated this signature before string clr_sig = ClrMethod.Overload.ClrSignatureToString( overload.GenericParamCount, constructed_params, constructed_return_type); if (stubs.ContainsKey(clr_sig)) continue; Type[] param_types = new Type[constructed_params.Length]; for (int j = 0; j < param_types.Length; j++) { param_types[j] = constructed_params[j].ParameterType; } // determine the stub attributes MethodAttributes attr; string name; name = overload.Method.Name; attr = Reflection.Enums.ToMethodAttributes(target.MemberDesc.MemberAttributes); attr |= (MethodAttributes.Virtual | MethodAttributes.HideBySig); if (newSlot) attr |= MethodAttributes.NewSlot; MethodBuilder overload_builder = type_builder.DefineMethod(name, attr); if (overload.MandatoryGenericParamCount > 0) { // define the same generic parameters that are defined for the overridden method // (the same constraints but possibly having different names) ClrStubBuilder.DefineStubGenericParameters( overload_builder, overload.GenericParameters, target.Signature, param_types); } overload_builder.SetReturnType(constructed_return_type); overload_builder.SetParameters(param_types); // set parameter names and attributes ClrStubBuilder.DefineStubParameters(overload_builder, target.Builder.Signature.FormalParams, constructed_params); if (!overload_builder.IsAbstract) { EmissionContext emission_context = SetupStubPlaces(target.DeclaringPhpType, false); try { // convert parameters and invoke the target ClrStubBuilder.EmitMethodStubBody( new ILEmitter(overload_builder), ScriptContextPlace, constructed_params, overload.GenericParameters, constructed_return_type, target, targetType); } finally { RestorePlaces(emission_context); } } stubs.Add(clr_sig, overload_builder); } } }
/// <summary> /// Emits stub for one overridden/implemented/exported CLR overload. /// </summary> /// <param name="il"></param> /// <param name="scriptContextPlace"></param> /// <param name="stubParameters">The overload parameters.</param> /// <param name="stubTypeParameters">The overload type parameters.</param> /// <param name="stubReturnType">The overload return type.</param> /// <param name="target">The overriding/implementing/exporting method.</param> /// <param name="targetType">The type (perhaps constructed) that declared <paramref name="target"/>.</param> public static void EmitMethodStubBody(ILEmitter/*!*/ il, IPlace/*!*/ scriptContextPlace, ParameterInfo[]/*!*/ stubParameters, Type[]/*!*/ stubTypeParameters, Type/*!*/ stubReturnType, PhpMethod/*!*/ target, DType/*!*/ targetType) { bool stub_is_static = il.MethodBase.IsStatic; ClrStubBuilder stub_builder = new ClrStubBuilder(il, scriptContextPlace, stubParameters.Length, (stub_is_static ? 0 : 1)); if (stubParameters.Length >= target.Signature.MandatoryParamCount && stubTypeParameters.Length >= target.Signature.MandatoryGenericParamCount && (target.Properties & RoutineProperties.IsArgsAware) == 0) { // we can directly call the target argful if (!stub_is_static) il.Ldarg(FunctionBuilder.ArgThis); scriptContextPlace.EmitLoad(il); stub_builder.EmitLoadArgfullParameters(stubParameters, stubTypeParameters, target); // invoke the target (virtually if it's not static) il.Emit(stub_is_static ? OpCodes.Call : OpCodes.Callvirt, DType.MakeConstructed(target.ArgFullInfo, targetType as ConstructedType)); } else { // we have to take the argless way stub_builder.EmitLoadArglessParameters(stubParameters, stubTypeParameters, target); // invoke the target's argless // TODO: this is not behaving 100% correct, because we're losing virtual dispatch here if (stub_is_static) il.Emit(OpCodes.Ldnull); else il.Ldarg(FunctionBuilder.ArgThis); scriptContextPlace.EmitLoad(il); il.Emit(OpCodes.Ldfld, Fields.ScriptContext_Stack); il.Emit(OpCodes.Call, DType.MakeConstructed(target.ArgLessInfo, targetType as ConstructedType)); } // do not keep it on stack needlessly if (stubReturnType == Types.Void) il.Emit(OpCodes.Pop); // convert ref/out parameters back to CLR type for (int i = 0; i < stubParameters.Length; i++) { stub_builder.EmitStoreClrParameter(stubParameters[i]); } if (stubReturnType != Types.Void) { // convert the return parameter back to CLR type stub_builder.EmitConvertReturnValue( stubReturnType, target.Signature.AliasReturn ? PhpTypeCode.PhpReference : PhpTypeCode.Object); } il.Emit(OpCodes.Ret); }
private void EmitLoadArglessParameters(ParameterInfo[]/*!*/ stubParameters, Type[]/*!*/ stubTypeParameters, PhpMethod/*!*/ target) { PhpStackBuilder.EmitAddFrame(il, scriptContextPlace, stubTypeParameters.Length, stubParameters.Length, delegate(ILEmitter eil, int i) { il.Emit(OpCodes.Ldtoken, stubTypeParameters[i]); il.Emit(OpCodes.Call, Methods.DTypeDesc_Create); }, delegate(ILEmitter eil, int i) { EmitLoadClrParameter(stubParameters[i], PhpTypeCode.Unknown); }); }
private void EmitLoadArgfullParameters(ParameterInfo[]/*!*/ stubParameters, Type[]/*!*/ stubTypeParameters, PhpMethod/*!*/ target) { for (int i = 0; i < target.Signature.GenericParamCount; i++) { if (i < stubTypeParameters.Length) { il.Emit(OpCodes.Ldtoken, stubTypeParameters[i]); il.Emit(OpCodes.Call, Methods.DTypeDesc_Create); } else { // optional type parameter, whose value is not supplied il.Emit(OpCodes.Ldsfld, Fields.Arg_DefaultType); } } for (int i = 0; i < target.Signature.ParamCount; i++) { if (i < stubParameters.Length) { EmitLoadClrParameter( stubParameters[i], target.Signature.IsAlias(i) ? PhpTypeCode.PhpReference : PhpTypeCode.Object); } else { // optional parameter, whose value is not supplied il.Emit(OpCodes.Ldsfld, Fields.Arg_Default); } } }
/// <summary> /// Notices the analyzer that method declaration is entered. /// </summary> internal void EnterMethodDeclaration(PhpMethod/*!*/ method) { //function declared within a method is global function //=> method is only declared direct within a class declaration Debug.Assert(locationStack.Peek().Kind == Locations.TypeDecl); RoutineDeclLoc m = new RoutineDeclLoc(method, locationStack.Count); routineDeclStack.Push(m); locationStack.Push(m); EnterConditionalCode(); }
internal override void AnalyzeMembers(Analyzer/*!*/ analyzer, PhpType/*!*/ declaringType) { method = declaringType.AddMethod(name, modifiers, body != null, signature, typeSignature, position, analyzer.SourceUnit, analyzer.ErrorSink); // method redeclared: if (method == null) return; method.WriteUp(typeSignature.ToPhpRoutineSignature(method)); typeSignature.PreAnalyze(analyzer, method); base.AnalyzeMembers(analyzer, declaringType); typeSignature.AnalyzeMembers(analyzer, declaringType.Declaration.Scope); signature.AnalyzeMembers(analyzer, method); method.IsDllImport = this.IsDllImport; if(method.IsDllImport && Body.Count != 0) analyzer.ErrorSink.Add(Warnings.BodyOfDllImportedFunctionIgnored, analyzer.SourceUnit, position); }