/** * @brief Perform runtime casting of XMRSDTypeClObj's. * @param ob = XMRSDTypeClObj of unknown script-defined class to cast * @param classindex = script-defined class to cast it to * @returns ob is a valid instance of classindex; else exception thrown */ public static XMRSDTypeClObj CastClass2Class(object ob, int classindex) { /* * Let mono check to see if we at least have an XMRSDTypeClObj. */ XMRSDTypeClObj ci = (XMRSDTypeClObj)ob; if (ci != null) { /* * This is the target class, ie, what we are hoping the object can cast to. */ TokenDeclSDTypeClass tc = (TokenDeclSDTypeClass)ci.xmrInst.m_ObjCode.sdObjTypesIndx[classindex]; /* * Step from the object's actual class rootward. * If we find the target class along the way, the cast is valid. * If we run off the end of the root, the cast is not valid. */ for (TokenDeclSDTypeClass ac = ci.sdtcClass; ac != tc; ac = ac.extends) { if (ac == null) { throw new InvalidCastException("invalid cast from " + ci.sdtcClass.longName.val + " to " + tc.longName.val); } } /* * The target class is at or rootward of the actual class, * so the cast is valid. */ } return(ci); }
/** * @brief Emit code that converts the top stack item from 'oldType' to 'newType' * @param scg = what script we are compiling * @param errorAt = token used for source location for error messages * @param oldType = type of item currently on the stack * @param newType = type to convert it to * @param explicitAllowed = false: only consider implicit casts * true: consider both implicit and explicit casts * @returns with code emitted for conversion (or error message output if not allowed, and stack left unchanged) */ public static void CastTopOfStack(IScriptCodeGen scg, Token errorAt, TokenType oldType, TokenType newType, bool explicitAllowed) { CastDelegate castDelegate; string oldString = oldType.ToString(); string newString = newType.ToString(); // 'key' -> 'bool' is the only time we care about key being different than string. if ((oldString == "key") && (newString == "bool")) { LSLUnwrap(scg, errorAt, oldType); scg.ilGen.Emit(errorAt, OpCodes.Call, keyToBoolMethodInfo); LSLWrap(scg, errorAt, newType); return; } // Treat key and string as same type for all other type casts. if (oldString == "key") { oldString = "string"; } if (newString == "key") { newString = "string"; } // If the types are the same, there is no conceptual casting needed. // However, there may be wraping/unwraping to/from the LSL wrappers. if (oldString == newString) { if (oldType.ToLSLWrapType() != newType.ToLSLWrapType()) { LSLUnwrap(scg, errorAt, oldType); LSLWrap(scg, errorAt, newType); } return; } // Script-defined classes can be cast up and down the tree. if ((oldType is TokenTypeSDTypeClass) && (newType is TokenTypeSDTypeClass)) { TokenDeclSDTypeClass oldSDTC = ((TokenTypeSDTypeClass)oldType).decl; TokenDeclSDTypeClass newSDTC = ((TokenTypeSDTypeClass)newType).decl; // implicit cast allowed from leaf toward root for (TokenDeclSDTypeClass sdtc = oldSDTC; sdtc != null; sdtc = sdtc.extends) { if (sdtc == newSDTC) { return; } } // explicit cast allowed from root toward leaf for (TokenDeclSDTypeClass sdtc = newSDTC; sdtc != null; sdtc = sdtc.extends) { if (sdtc == oldSDTC) { ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4, newSDTC.sdTypeIndex); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastClass2ClassMethodInfo); return; } } // not on same branch goto illcast; } // One script-defined interface type cannot be cast to another script-defined interface type, // unless the old interface declares that it implements the new interface. That proves that // the underlying object, no matter what type, implements the new interface. if ((oldType is TokenTypeSDTypeInterface) && (newType is TokenTypeSDTypeInterface)) { TokenDeclSDTypeInterface oldDecl = ((TokenTypeSDTypeInterface)oldType).decl; TokenDeclSDTypeInterface newDecl = ((TokenTypeSDTypeInterface)newType).decl; if (!oldDecl.Implements(newDecl)) { goto illcast; } scg.ilGen.Emit(errorAt, OpCodes.Ldstr, newType.ToString()); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastObj2IFaceMethodInfo); return; } // A script-defined class type can be implicitly cast to a script-defined interface type that it // implements. The result is an array of delegates that give the class's implementation of the // various methods defined by the interface. if ((oldType is TokenTypeSDTypeClass) && (newType is TokenTypeSDTypeInterface)) { TokenDeclSDTypeClass oldSDTC = ((TokenTypeSDTypeClass)oldType).decl; int intfIndex; if (!oldSDTC.intfIndices.TryGetValue(newType.ToString(), out intfIndex)) { goto illcast; } scg.ilGen.Emit(errorAt, OpCodes.Ldfld, sdtcITableFieldInfo); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4, intfIndex); scg.ilGen.Emit(errorAt, OpCodes.Ldelem, typeof(Delegate[])); return; } // A script-defined interface type can be explicitly cast to a script-defined class type by // extracting the Target property from element 0 of the delegate array that is the interface // object and making sure it casts to the correct script-defined class type. // // But then only if the class type implements the interface type. if ((oldType is TokenTypeSDTypeInterface) && (newType is TokenTypeSDTypeClass)) { TokenTypeSDTypeInterface oldSDTI = (TokenTypeSDTypeInterface)oldType; TokenTypeSDTypeClass newSDTC = (TokenTypeSDTypeClass)newType; if (!newSDTC.decl.CanCastToIntf(oldSDTI.decl)) { goto illcast; } ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4, newSDTC.decl.sdTypeIndex); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastIFace2ClassMethodInfo); return; } // A script-defined interface type can be implicitly cast to object. if ((oldType is TokenTypeSDTypeInterface) && (newType is TokenTypeObject)) { return; } // An object can be explicitly cast to a script-defined interface. if ((oldType is TokenTypeObject) && (newType is TokenTypeSDTypeInterface)) { ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); scg.ilGen.Emit(errorAt, OpCodes.Ldstr, newString); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastObj2IFaceMethodInfo); return; } // Cast to void is always allowed, such as discarding value from 'i++' or function return value. if (newType is TokenTypeVoid) { scg.ilGen.Emit(errorAt, OpCodes.Pop); return; } // Cast from undef to object or script-defined type is always allowed. if ((oldType is TokenTypeUndef) && ((newType is TokenTypeObject) || (newType is TokenTypeSDTypeClass) || (newType is TokenTypeSDTypeInterface))) { return; } // Script-defined classes can be implicitly cast to objects. if ((oldType is TokenTypeSDTypeClass) && (newType is TokenTypeObject)) { return; } // Script-defined classes can be explicitly cast from objects and other script-defined classes. // Note that we must manually check that it is the correct SDTypeClass however because as far as // mono is concerned, all SDTypeClass's are the same. if ((oldType is TokenTypeObject) && (newType is TokenTypeSDTypeClass)) { ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4, ((TokenTypeSDTypeClass)newType).decl.sdTypeIndex); scg.ilGen.Emit(errorAt, OpCodes.Call, sdTypeClassCastClass2ClassMethodInfo); return; } // Delegates can be implicitly cast to/from objects. if ((oldType is TokenTypeSDTypeDelegate) && (newType is TokenTypeObject)) { return; } if ((oldType is TokenTypeObject) && (newType is TokenTypeSDTypeDelegate)) { scg.ilGen.Emit(errorAt, OpCodes.Castclass, newType.ToSysType()); return; } // Some actual conversion is needed, see if it is in table of legal casts. string key = oldString + " " + newString; if (!legalTypeCasts.TryGetValue(key, out castDelegate)) { key = oldString + "*" + newString; if (!legalTypeCasts.TryGetValue(key, out castDelegate)) { goto illcast; } ExplCheck(scg, errorAt, explicitAllowed, oldString, newString); } // Ok, output cast. But make sure it is in native form without any LSL wrapping // before passing to our casting routine. Then if caller is expecting an LSL- // wrapped value on the stack upon return, wrap it up after our casting. LSLUnwrap(scg, errorAt, oldType); castDelegate(scg, errorAt); LSLWrap(scg, errorAt, newType); return; illcast: scg.ErrorMsg(errorAt, "illegal to cast from " + oldString + " to " + newString); if (!(oldType is TokenTypeVoid)) { scg.ilGen.Emit(errorAt, OpCodes.Pop); } scg.PushDefaultValue(newType); }
/** * @brief Fill in ScriptObjCode from an YEngine object file. * 'objFileReader' is a serialized form of the CIL code we generated * 'asmFileWriter' is where we write the disassembly to (or null if not wanted) * 'srcFileWriter' is where we write the decompilation to (or null if not wanted) * Throws an exception if there is any error (theoretically). */ public ScriptObjCode(BinaryReader objFileReader, TextWriter asmFileWriter, TextWriter srcFileWriter) { // Check version number to make sure we know how to process file contents. char[] ocm = objFileReader.ReadChars(ScriptCodeGen.OBJECT_CODE_MAGIC.Length); if (new String(ocm) != ScriptCodeGen.OBJECT_CODE_MAGIC) { throw new CVVMismatchException("Not an Yengine object file (bad magic)"); } int cvv = objFileReader.ReadInt32(); if (cvv != ScriptCodeGen.COMPILED_VERSION_VALUE) { throw new CVVMismatchException( "Object version is " + cvv.ToString() + " but accept only " + ScriptCodeGen.COMPILED_VERSION_VALUE.ToString()); } // Fill in simple parts of scriptObjCode object. sourceHash = objFileReader.ReadString(); glblSizes.ReadFromFile(objFileReader); int nStates = objFileReader.ReadInt32(); stateNames = new string[nStates]; for (int i = 0; i < nStates; i++) { stateNames[i] = objFileReader.ReadString(); if (asmFileWriter != null) { asmFileWriter.WriteLine(" state[{0}] = {1}", i, stateNames[i]); } } if (asmFileWriter != null) { glblSizes.WriteAsmFile(asmFileWriter, "numGbl"); } string gblName; while ((gblName = objFileReader.ReadString()) != "") { string gblType = objFileReader.ReadString(); int gblIndex = objFileReader.ReadInt32(); Dictionary <int, string> names; if (!globalVarNames.TryGetValue(gblType, out names)) { names = new Dictionary <int, string>(); globalVarNames.Add(gblType, names); } names.Add(gblIndex, gblName); if (asmFileWriter != null) { asmFileWriter.WriteLine(" {0} = {1}[{2}]", gblName, gblType, gblIndex); } } // Read in script-defined types. sdObjTypesName = new Dictionary <string, TokenDeclSDType>(); sdDelTypes = new Dictionary <Type, string>(); int maxIndex = -1; while ((gblName = objFileReader.ReadString()) != "") { TokenDeclSDType sdt = TokenDeclSDType.ReadFromFile(sdObjTypesName, gblName, objFileReader, asmFileWriter); sdObjTypesName.Add(gblName, sdt); if (maxIndex < sdt.sdTypeIndex) { maxIndex = sdt.sdTypeIndex; } if (sdt is TokenDeclSDTypeDelegate) { sdDelTypes.Add(sdt.GetSysType(), gblName); } } sdObjTypesIndx = new TokenDeclSDType[maxIndex + 1]; foreach (TokenDeclSDType sdt in sdObjTypesName.Values) { sdObjTypesIndx[sdt.sdTypeIndex] = sdt; } // Now fill in the methods (the hard part). scriptEventHandlerTable = new ScriptEventHandler[nStates, (int)ScriptEventCode.Size]; dynamicMethods = new Dictionary <string, DynamicMethod>(); scriptSrcLocss = new Dictionary <string, KeyValuePair <int, ScriptSrcLoc>[]>(); ObjectTokens objectTokens = null; if (asmFileWriter != null) { objectTokens = new OTDisassemble(this, asmFileWriter); } else if (srcFileWriter != null) { objectTokens = new OTDecompile(this, srcFileWriter); } try { ScriptObjWriter.CreateObjCode(sdObjTypesName, objFileReader, this, objectTokens); } finally { if (objectTokens != null) { objectTokens.Close(); } } // We enter all script event handler methods in the ScriptEventHandler table. // They are named: <statename> <eventname> foreach (KeyValuePair <string, DynamicMethod> kvp in dynamicMethods) { string methName = kvp.Key; int i = methName.IndexOf(' '); if (i < 0) { continue; } string stateName = methName.Substring(0, i); string eventName = methName.Substring(++i); int stateCode; for (stateCode = stateNames.Length; --stateCode >= 0;) { if (stateNames[stateCode] == stateName) { break; } } int eventCode = (int)Enum.Parse(typeof(ScriptEventCode), eventName); scriptEventHandlerTable[stateCode, eventCode] = (ScriptEventHandler)kvp.Value.CreateDelegate(typeof(ScriptEventHandler)); } // Fill in all script-defined class vtables. foreach (TokenDeclSDType sdt in sdObjTypesIndx) { if ((sdt != null) && (sdt is TokenDeclSDTypeClass)) { TokenDeclSDTypeClass sdtc = (TokenDeclSDTypeClass)sdt; sdtc.FillVTables(this); } } }
/** * @brief Set up everything except the instVars arrays. * @param inst = script instance this object is part of * @param classindex = which script-defined type class this object is an onstance of * @returns with the vtables filled in */ private void Construct(XMRInstAbstract inst, int classindex) { Delegate[] thisMid = null; TokenDeclSDTypeClass clas = (TokenDeclSDTypeClass)inst.m_ObjCode.sdObjTypesIndx[classindex]; xmrInst = inst; sdtcClass = clas; instVars = new XMRInstArrays(inst); /* * VTable consists of delegates built from DynamicMethods and the 'this' pointer. * Yes, yes, lots of shitty little mallocs. */ DynamicMethod[] vDynMeths = clas.vDynMeths; if (vDynMeths != null) { int n = vDynMeths.Length; Type[] vMethTypes = clas.vMethTypes; sdtcVTable = new Delegate[n]; for (int i = 0; i < n; i++) { sdtcVTable[i] = vDynMeths[i].CreateDelegate(vMethTypes[i], this); } } /* * Fill in interface vtables. * There is one array of delegates for each implemented interface. * The array of delegates IS the interface's object, ie, the 'this' value of the interface. * To cast from the class type to the interface type, just get the correct array from the table. * To cast from the interface type to the class type, just get Target of entry 0. * * So we end up with this: * sdtcITable[interfacenumber][methodofintfnumber] = delegate of this.ourimplementationofinterfacesmethod */ if (clas.iDynMeths != null) { int nIFaces = clas.iDynMeths.Length; sdtcITable = new Delegate[nIFaces][]; for (int i = 0; i < nIFaces; i++) { // get vector of entrypoints of our instance methods that implement that interface DynamicMethod[] iDynMeths = clas.iDynMeths[i]; Type[] iMethTypes = clas.iMethTypes[i]; // allocate an array with a slot for each method the interface defines int nMeths = iDynMeths.Length; Delegate[] ivec; if (nMeths > 0) { // fill in the array with delegates that reference back to this class instance ivec = new Delegate[nMeths]; for (int j = 0; j < nMeths; j++) { ivec[j] = iDynMeths[j].CreateDelegate(iMethTypes[j], this); } } else { // just a marker interface with no methods, // allocate a one-element array and fill // with a dummy entry. this will allow casting // back to the original class instance (this) // by reading Target of entry 0. if (thisMid == null) { thisMid = new Delegate[1]; thisMid[0] = markerInterfaceDummy.CreateDelegate(typeof(MarkerInterfaceDummy), this); } ivec = thisMid; } // save whatever we ended up allocating sdtcITable[i] = ivec; } } }