/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using System.Text; using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; /** * @brief Generate code for the backend API calls. */ namespace OpenSim.Region.ScriptEngine.Yengine { public abstract class TokenDeclInline: TokenDeclVar { public static VarDict inlineFunctions = CreateDictionary(); public abstract void CodeGen(ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args); private static HashSet noCheckRuns; private static string[] keyReturns; protected bool isTaggedCallsCheckRun; /** * @brief Create a dictionary of inline backend API functions. */ private static VarDict CreateDictionary() { /* * For those listed in noCheckRun, we just generate the call (simple computations). * For all others, we generate the call then a call to CheckRun(). * note to self: a change here implies change on magic numbers to invalidate older code and * script state */ noCheckRuns = new HashSet() { "llBase64ToString", "llCSV2List", "llDeleteSubList", "llDeleteSubString", "llDumpList2String", "llEscapeURL", "llEuler2Rot", "llGetListEntryType", "llGetListLength", "llGetSubString", "llGetUnixTime", "llInsertString", "llList2CSV", "llList2Float", "llList2Integer", "llList2Key", "llList2Rot", "llList2String", "llList2Vector", "llListFindList", "llListInsertList", "llListRandomize", "llListReplaceList", "llListSort", "llListStatistics", "llMD5String", "llParseString2List", "llParseStringKeepNulls", "llRot2Euler", "llStringLength", "llStringToBase64", "llStringTrim", "llSubStringIndex", "llUnescapeURL", "osGetSitActiveRange", "osIsNotValidNumber", "osSlerp", "osApproxEquals", "osVecDistSquare", "osVecMagSquare", "osRound", "osGetLinkNumber", "osMax", "osMin", "osIsUUID" }; /* * These functions really return a 'key' even though we see them as * returning 'string' because OpenSim has key and string as same type. */ keyReturns = new string[] { "llAvatarOnLinkSitTarget", "llAvatarOnSitTarget", "llDetectedKey", "llDetectedOwner", "llGenerateKey", "llGetCreator", "llGetInventoryCreator", "llGetInventoryKey", "llGetKey", "llGetLandOwnerAt", "llGetLinkKey", "llGetNotecardLine", "llGetNumberOfNotecardLines", "llGetOwner", "llGetOwnerKey", "llGetPermissionsKey", "llHTTPRequest", "llList2Key", "llRequestAgentData", "llRequestDisplayName", "llRequestInventoryData", "llRequestSecureURL", "llRequestSimulatorData", "llRequestURL", "llRequestUsername", "llSendRemoteData", "llTransferLindenDollars" }; VarDict ifd = new VarDict(false); Type[] oneDoub = new Type[] { typeof(double) }; Type[] twoDoubs = new Type[] { typeof(double), typeof(double) }; /* * Something weird about the code generation for these calls, so they all have their own handwritten code generators. */ new TokenDeclInline_GetFreeMemory(ifd); new TokenDeclInline_GetUsedMemory(ifd); /* * These are all the xmr...() calls directly in XMRInstAbstract. * Includes the calls from ScriptBaseClass that has all the stubs * which convert XMRInstAbstract to the various _Api contexts. */ MethodInfo[] absmeths = typeof(XMRInstAbstract).GetMethods(); AddInterfaceMethods(ifd, absmeths, null); return ifd; } /** * @brief Add API functions from the given interface to list of built-in functions. * Only functions beginning with a lower-case letter are entered, all others ignored. * @param ifd = internal function dictionary to add them to * @param ifaceMethods = list of API functions * @param acf = which field in XMRInstanceSuperType holds method's 'this' pointer */ // this one accepts only names beginning with a lower-case letter public static void AddInterfaceMethods(VarDict ifd, MethodInfo[] ifaceMethods, FieldInfo acf) { List lcms = new List(ifaceMethods.Length); foreach(MethodInfo meth in ifaceMethods) { string name = meth.Name; if((name[0] >= 'a') && (name[0] <= 'z')) { lcms.Add(meth); } } AddInterfaceMethods(ifd, lcms.GetEnumerator(), acf); } // this one accepts all methods given to it public static void AddInterfaceMethods(VarDict ifd, IEnumerator ifaceMethods, FieldInfo acf) { ifd ??= inlineFunctions; for(ifaceMethods.Reset(); ifaceMethods.MoveNext();) { MethodInfo ifaceMethod = ifaceMethods.Current; try { /* * See if we will generate a call to CheckRun() right * after we generate a call to the function. * If function begins with xmr, assume we will not call CheckRun() * Otherwise, assume we will call CheckRun() */ bool dcr = !(ifaceMethod.Name.StartsWith("xmr") || noCheckRuns.Contains(ifaceMethod.Name)); /* * Add function to dictionary. */ new TokenDeclInline_BEApi(ifd, dcr, ifaceMethod, acf); } catch { ///??? IGNORE ANY THAT FAIL - LIKE UNRECOGNIZED TYPE ???/// ///??? and OVERLOADED NAMES ???/// } } } /** * @brief Add an inline function definition to the dictionary. * @param ifd = dictionary to add inline definition to * @param doCheckRun = true iff the generated code or the function itself can possibly call CheckRun() * @param nameArgSig = inline function signature string, in form (,...) * @param retType = return type, use TokenTypeVoid if no return value */ protected TokenDeclInline(VarDict ifd, bool doCheckRun, string nameArgSig, TokenType retType) : base(null, null, null) { this.retType = retType; this.triviality = doCheckRun ? Triviality.complex : Triviality.trivial; int j = nameArgSig.IndexOf('('); this.name = new TokenName(null, nameArgSig.Substring(0, j++)); this.argDecl = new TokenArgDecl(null); if(nameArgSig[j] != ')') { int i; TokenName name; TokenType type; for(i = j; nameArgSig[i] != ')'; i++) { if(nameArgSig[i] == ',') { type = TokenType.FromLSLType(null, nameArgSig.Substring(j, i - j)); name = new TokenName(null, "arg" + this.argDecl.varDict.Count); this.argDecl.AddArg(type, name); j = i + 1; } } type = TokenType.FromLSLType(null, nameArgSig.Substring(j, i - j)); name = new TokenName(null, "arg" + this.argDecl.varDict.Count); this.argDecl.AddArg(type, name); } this.location = new CompValuInline(this); if(ifd == null) ifd = inlineFunctions; ifd.AddEntry(this); } protected TokenDeclInline(VarDict ifd, bool doCheckRun, MethodInfo methInfo) : base(null, null, null) { isTaggedCallsCheckRun = IsTaggedCallsCheckRun(methInfo); name = new TokenName(null, methInfo.Name); retType = GetRetType(methInfo, TokenType.FromSysType(null, methInfo.ReturnType)); argDecl = GetArgDecl(methInfo.GetParameters()); triviality = (doCheckRun || isTaggedCallsCheckRun) ? Triviality.complex : Triviality.trivial; location = new CompValuInline(this); ifd ??= inlineFunctions; ifd.AddEntry(this); } private static TokenArgDecl GetArgDecl(ParameterInfo[] parameters) { TokenArgDecl argDecl = new TokenArgDecl(null); foreach(ParameterInfo pi in parameters) { TokenType type = TokenType.FromSysType(null, pi.ParameterType); TokenName name = new TokenName(null, pi.Name); argDecl.AddArg(type, name); } return argDecl; } /** * @brief The above code assumes all methods beginning with 'xmr' are trivial, ie, * they do not call CheckRun() and also we do not generate a CheckRun() * call after they return. So if an 'xmr' method does call CheckRun(), it * must be tagged with attribute 'xmrMethodCallsCheckRunAttribute' so we know * the method is not trivial. But in neither case do we emit our own call * to CheckRun(), the 'xmr' method must do its own. We do however set up a * call label before the call to the non-trivial 'xmr' method so when we are * restoring the call stack, the restore will call directly in to the 'xmr' * method without re-executing any code before the call to the 'xmr' method. */ private static bool IsTaggedCallsCheckRun(MethodInfo methInfo) { return (methInfo != null) && Attribute.IsDefined(methInfo, typeof(xmrMethodCallsCheckRunAttribute)); } /** * @brief The dumbass OpenSim has key and string as the same type so non-ll * methods must be tagged with xmrMethodReturnsKeyAttribute if we * are to think they return a key type, otherwise we will think they * return string. */ private static TokenType GetRetType(MethodInfo methInfo, TokenType retType) { if((methInfo != null) && (retType != null) && (retType is TokenTypeStr)) { if(Attribute.IsDefined(methInfo, typeof(xmrMethodReturnsKeyAttribute))) { return ChangeToKeyType(retType); } string mn = methInfo.Name; foreach(string kr in keyReturns) { if(kr == mn) return ChangeToKeyType(retType); } } return retType; } private static TokenType ChangeToKeyType(TokenType retType) { if(retType is TokenTypeLSLString) { retType = new TokenTypeLSLKey(null); } else { retType = new TokenTypeKey(null); } return retType; } public virtual MethodInfo GetMethodInfo() { return null; } /** * @brief Print out a list of all the built-in functions and constants. */ public delegate void WriteLine(string str); public static void PrintBuiltins(bool inclNoisyTag, WriteLine writeLine) { writeLine("\nBuilt-in functions:\n"); SortedDictionary bifs = new SortedDictionary(); foreach(TokenDeclVar bif in TokenDeclInline.inlineFunctions) { bifs.Add(bif.fullName, (TokenDeclInline)bif); } foreach(TokenDeclInline bif in bifs.Values) { char noisy = (!inclNoisyTag || !IsTaggedNoisy(bif.GetMethodInfo())) ? ' ' : (bif.retType is TokenTypeVoid) ? 'N' : 'R'; writeLine(noisy + " " + bif.retType.ToString().PadLeft(8) + " " + bif.fullName); } if(inclNoisyTag) { writeLine("\nN - stub that writes name and arguments to stdout"); writeLine("R - stub that writes name and arguments to stdout then reads return value from stdin"); writeLine(" format is: function_name : return_value"); writeLine(" example: llKey2Name:\"Kunta Kinte\""); } writeLine("\nBuilt-in constants:\n"); SortedDictionary scs = new SortedDictionary(); int widest = 0; foreach(ScriptConst sc in ScriptConst.scriptConstants.Values) { if(widest < sc.name.Length) widest = sc.name.Length; scs.Add(sc.name, sc); } foreach(ScriptConst sc in scs.Values) { writeLine(" " + sc.rVal.type.ToString().PadLeft(8) + " " + sc.name.PadRight(widest) + " = " + BuiltInConstVal(sc.rVal)); } } public static bool IsTaggedNoisy(MethodInfo methInfo) { return (methInfo != null) && Attribute.IsDefined(methInfo, typeof(xmrMethodIsNoisyAttribute)); } public static string BuiltInConstVal(CompValu rVal) { if(rVal is CompValuInteger) { int x = ((CompValuInteger)rVal).x; return "0x" + x.ToString("X8") + " = " + x.ToString().PadLeft(11); } if(rVal is CompValuFloat) return ((CompValuFloat)rVal).x.ToString(); if(rVal is CompValuString) { StringBuilder sb = new StringBuilder(); PrintParam(sb, ((CompValuString)rVal).x); return sb.ToString(); } if(rVal is CompValuSField) { FieldInfo fi = ((CompValuSField)rVal).field; StringBuilder sb = new StringBuilder(); PrintParam(sb, fi.GetValue(null)); return sb.ToString(); } return rVal.ToString(); // just prints the type } public static void PrintParam(StringBuilder sb, object p) { if(p == null) { sb.Append("null"); } else if(p is LSL_List) { sb.Append('['); object[] d = ((LSL_List)p).Data; for(int i = 0; i < d.Length; i++) { if(i > 0) sb.Append(','); PrintParam(sb, d[i]); } sb.Append(']'); } else if(p is LSL_Rotation) { LSL_Rotation r = (LSL_Rotation)p; sb.Append('<'); sb.Append(r.x); sb.Append(','); sb.Append(r.y); sb.Append(','); sb.Append(r.z); sb.Append(','); sb.Append(r.s); sb.Append('>'); } else if(p is LSL_String) { PrintParamString(sb, (string)(LSL_String)p); } else if(p is LSL_Vector) { LSL_Vector v = (LSL_Vector)p; sb.Append('<'); sb.Append(v.x); sb.Append(','); sb.Append(v.y); sb.Append(','); sb.Append(v.z); sb.Append('>'); } else if(p is string) { PrintParamString(sb, (string)p); } else { sb.Append(p.ToString()); } } public static void PrintParamString(StringBuilder sb, string p) { sb.Append('"'); foreach(char c in p) { if(c == '\b') { sb.Append("\\b"); continue; } if(c == '\n') { sb.Append("\\n"); continue; } if(c == '\r') { sb.Append("\\r"); continue; } if(c == '\t') { sb.Append("\\t"); continue; } if(c == '"') { sb.Append("\\\""); continue; } if(c == '\\') { sb.Append("\\\\"); continue; } sb.Append(c); } sb.Append('"'); } } /** * @brief Code generators... * @param scg = script we are generating code for * @param result = type/location for result (type matches function definition) * @param args = type/location of arguments (types match function definition) */ /* public class TokenDeclInline_LLAbs: TokenDeclInline { public TokenDeclInline_LLAbs(VarDict ifd) : base(ifd, false, "llAbs(integer)", new TokenTypeInt(null)) { } public override void CodeGen(ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) { ScriptMyLabel itsPosLabel = scg.ilGen.DefineLabel("llAbstemp"); args[0].PushVal(scg, errorAt); scg.ilGen.Emit(errorAt, OpCodes.Dup); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4_0); scg.ilGen.Emit(errorAt, OpCodes.Bge_S, itsPosLabel); scg.ilGen.Emit(errorAt, OpCodes.Neg); scg.ilGen.MarkLabel(itsPosLabel); result.Pop(scg, errorAt, retType); } } public class TokenDeclInline_Math: TokenDeclInline { private MethodInfo methInfo; public TokenDeclInline_Math(VarDict ifd, string sig, string name, Type[] args) : base(ifd, false, sig, new TokenTypeFloat(null)) { methInfo = ScriptCodeGen.GetStaticMethod(typeof(System.Math), name, args); } public override void CodeGen(ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) { for(int i = 0; i < args.Length; i++) { args[i].PushVal(scg, errorAt, argDecl.types[i]); } scg.ilGen.Emit(errorAt, OpCodes.Call, methInfo); result.Pop(scg, errorAt, retType); } } public class TokenDeclInline_LLRound: TokenDeclInline { private static MethodInfo roundMethInfo = ScriptCodeGen.GetStaticMethod(typeof(System.Math), "Round", new Type[] { typeof(double), typeof(MidpointRounding) }); public TokenDeclInline_LLRound(VarDict ifd) : base(ifd, false, "llRound(float)", new TokenTypeInt(null)) { } public override void CodeGen(ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) { args[0].PushVal(scg, errorAt, new TokenTypeFloat(null)); scg.ilGen.Emit(errorAt, OpCodes.Ldc_I4, (int)System.MidpointRounding.AwayFromZero); scg.ilGen.Emit(errorAt, OpCodes.Call, roundMethInfo); result.Pop(scg, errorAt, new TokenTypeFloat(null)); } } */ public class TokenDeclInline_GetFreeMemory: TokenDeclInline { private static readonly MethodInfo getFreeMemMethInfo = typeof(XMRInstAbstract).GetMethod("xmrHeapLeft", new Type[] { }); public TokenDeclInline_GetFreeMemory(VarDict ifd) : base(ifd, false, "llGetFreeMemory()", new TokenTypeInt(null)) { } // appears as llGetFreeMemory() in script source code // but actually calls xmrHeapLeft() public override void CodeGen(ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) { scg.PushXMRInst(); scg.ilGen.Emit(errorAt, OpCodes.Call, getFreeMemMethInfo); result.Pop(scg, errorAt, new TokenTypeInt(null)); } } public class TokenDeclInline_GetUsedMemory: TokenDeclInline { private static readonly MethodInfo getUsedMemMethInfo = typeof(XMRInstAbstract).GetMethod("xmrHeapUsed", new Type[] { }); public TokenDeclInline_GetUsedMemory(VarDict ifd) : base(ifd, false, "llGetUsedMemory()", new TokenTypeInt(null)) { } // appears as llGetUsedMemory() in script source code // but actually calls xmrHeapUsed() public override void CodeGen(ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) { scg.PushXMRInst(); scg.ilGen.Emit(errorAt, OpCodes.Call, getUsedMemMethInfo); result.Pop(scg, errorAt, new TokenTypeInt(null)); } } /** * @brief Generate code for the usual ll...() functions. */ public class TokenDeclInline_BEApi: TokenDeclInline { // private static readonly MethodInfo fixLLParcelMediaQuery = ScriptCodeGen.GetStaticMethod // (typeof (XMRInstAbstract), "FixLLParcelMediaQuery", new Type[] { typeof (LSL_List) }); // private static readonly MethodInfo fixLLParcelMediaCommandList = ScriptCodeGen.GetStaticMethod // (typeof (XMRInstAbstract), "FixLLParcelMediaCommandList", new Type[] { typeof (LSL_List) }); public bool doCheckRun; private FieldInfo apiContextField; private MethodInfo methInfo; /** * @brief Constructor * @param ifd = dictionary to add the function to * @param dcr = append a call to CheckRun() * @param methInfo = ll...() method to be called */ public TokenDeclInline_BEApi(VarDict ifd, bool dcr, MethodInfo methInfo, FieldInfo acf) : base(ifd, dcr, methInfo) { this.methInfo = methInfo; doCheckRun = dcr; apiContextField = acf; } public override MethodInfo GetMethodInfo() { return methInfo; } /** * @brief Generate call to backend API function (eg llSay()) maybe followed by a call to CheckRun(). * @param scg = script being compiled * @param result = where to place result (might be void) * @param args = script-visible arguments to pass to API function */ public override void CodeGen(ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args) { if(isTaggedCallsCheckRun) { // see if 'xmr' method that calls CheckRun() internally new ScriptCodeGen.CallLabel(scg, errorAt); // if so, put a call label immediately before it // .. so restoring the frame will jump immediately to the // .. call without re-executing any code before this } if(!methInfo.IsStatic) { scg.PushXMRInst(); // XMRInstanceSuperType pointer if(apiContextField != null) // 'this' pointer for API function scg.ilGen.Emit(errorAt, OpCodes.Ldfld, apiContextField); } for(int i = 0; i < args.Length; i++) // push arguments, boxing/unboxing as needed args[i].PushVal(scg, errorAt, argDecl.types[i]); // this should not be needed // if (methInfo.Name == "llParcelMediaQuery") { // scg.ilGen.Emit (errorAt, OpCodes.Call, fixLLParcelMediaQuery); // } // this should not be needed // if (methInfo.Name == "llParcelMediaCommandList") { // scg.ilGen.Emit (errorAt, OpCodes.Call, fixLLParcelMediaCommandList); // } if(methInfo.IsVirtual) // call API function scg.ilGen.Emit(errorAt, OpCodes.Callvirt, methInfo); else scg.ilGen.Emit(errorAt, OpCodes.Call, methInfo); result.Pop(scg, errorAt, retType); // pop result, boxing/unboxing as needed if(isTaggedCallsCheckRun) scg.openCallLabel = null; if(doCheckRun) scg.EmitCallCheckRun(errorAt, false); // maybe call CheckRun() } } }