overhaul lua sandboxing so that each lua script has its own sandbox. elaborate tracking of winform/event ownership to ensure that lua doesnt receive calls without going through the owner's sandbox. add win32-specific speed hacks for currdir set/get at higher speed. There may be bugs in this commit, but I think we're on the right track now.
This commit is contained in:
parent
c510e13d82
commit
5e89e563d0
|
@ -30,8 +30,6 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public override string Name { get { return "event"; } }
|
||||
|
||||
public Lua CurrentThread { get; set; }
|
||||
|
||||
#region Events Library Helpers
|
||||
|
||||
public void CallExitEvent(Lua thread)
|
||||
|
|
|
@ -3,22 +3,16 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//TODO - kill this file (or renew the concept as distinct from the LuaSandbox?)
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
public class EnvironmentSandbox
|
||||
{
|
||||
public static void Sandbox(Action callback)
|
||||
{
|
||||
string oldCurrentDirectory = Environment.CurrentDirectory;
|
||||
|
||||
try
|
||||
{
|
||||
callback();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.CurrentDirectory = oldCurrentDirectory;
|
||||
}
|
||||
//just a stub for right now
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,14 +31,14 @@ namespace BizHawk.Client.Common
|
|||
State = RunState.Disabled;
|
||||
}
|
||||
|
||||
public LuaFile(LuaFile file)
|
||||
{
|
||||
Name = file.Name;
|
||||
Path = file.Path;
|
||||
State = file.State;
|
||||
IsSeparator = file.IsSeparator;
|
||||
CurrentDirectory = file.CurrentDirectory;
|
||||
}
|
||||
//public LuaFile(LuaFile file)
|
||||
//{
|
||||
// Name = file.Name;
|
||||
// Path = file.Path;
|
||||
// State = file.State;
|
||||
// IsSeparator = file.IsSeparator;
|
||||
// CurrentDirectory = file.CurrentDirectory;
|
||||
//}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using LuaInterface;
|
||||
using BizHawk.Common.ReflectionExtensions;
|
||||
|
@ -24,6 +25,31 @@ namespace BizHawk.Client.Common
|
|||
public Action<string> LogOutputCallback { get; set; }
|
||||
public Lua Lua { get; set; }
|
||||
|
||||
public static Lua CurrentThread { get; private set; }
|
||||
static Thread CurrentHostThread;
|
||||
static object ThreadMutex = new object();
|
||||
|
||||
public static void ClearCurrentThread()
|
||||
{
|
||||
lock (ThreadMutex)
|
||||
{
|
||||
CurrentHostThread = null;
|
||||
CurrentThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetCurrentThread(Lua luaThread)
|
||||
{
|
||||
lock (ThreadMutex)
|
||||
{
|
||||
if (CurrentHostThread != null)
|
||||
throw new InvalidOperationException("Can't have lua running in two host threads at a time!");
|
||||
CurrentHostThread = Thread.CurrentThread;
|
||||
CurrentThread = luaThread;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void Log(object message)
|
||||
{
|
||||
if (LogOutputCallback != null)
|
||||
|
|
|
@ -1,43 +1,96 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using LuaInterface;
|
||||
|
||||
//TODO - evaluate for re-entrancy problems
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
public class LuaSandbox
|
||||
public unsafe class LuaSandbox
|
||||
{
|
||||
protected static Action<string> Logger;
|
||||
|
||||
public static void SetLogger(Action<string> logger)
|
||||
protected static Action<string> Logger;
|
||||
|
||||
static System.Runtime.CompilerServices.ConditionalWeakTable<Lua, LuaSandbox> SandboxForThread = new System.Runtime.CompilerServices.ConditionalWeakTable<Lua, LuaSandbox>();
|
||||
public static Action<string> DefaultLogger;
|
||||
|
||||
public void SetLogger(Action<string> logger)
|
||||
{
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public static void SetCurrentDirectory(string dir)
|
||||
public void SetSandboxCurrentDirectory(string dir)
|
||||
{
|
||||
CurrentDirectory = dir;
|
||||
}
|
||||
|
||||
static string CurrentDirectory;
|
||||
|
||||
public static void Sandbox(Action callback, Action exceptionCallback = null)
|
||||
{
|
||||
string CurrentDirectory;
|
||||
|
||||
#if WINDOWS
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
static extern bool SetCurrentDirectoryW(byte* lpPathName);
|
||||
[DllImport("kernel32.dll", SetLastError=true)]
|
||||
static extern uint GetCurrentDirectoryW(uint nBufferLength, byte* pBuffer);
|
||||
#endif
|
||||
|
||||
bool CoolSetCurrentDirectory(string path, string currDirSpeedHack = null)
|
||||
{
|
||||
string target = CurrentDirectory + "\\";
|
||||
|
||||
//first we'll bypass it with a general hack: dont do any setting if the value's already there (even at the OS level, setting the directory can be slow)
|
||||
//yeah I know, not the smoothest move to compare strings here, in case path normalization is happening at some point
|
||||
//but you got any better ideas?
|
||||
if (currDirSpeedHack == null)
|
||||
currDirSpeedHack = CoolGetCurrentDirectory();
|
||||
if (currDirSpeedHack == path)
|
||||
return true;
|
||||
|
||||
//WARNING: setting the current directory is SLOW!!! security checks for some reason.
|
||||
//so we're bypassing it with windows hacks
|
||||
#if WINDOWS
|
||||
fixed (byte* pstr = &System.Text.Encoding.Unicode.GetBytes(target + "\0")[0])
|
||||
return SetCurrentDirectoryW(pstr);
|
||||
#else
|
||||
if(System.IO.Directory.Exists(CurrentDirectory)) //race condition for great justice
|
||||
{
|
||||
Environment.CurrentDirectory = CurrentDirectory; //thats right, you can't set a directory as current that doesnt exist because .net's got to do SENSELESS SLOW-ASS SECURITY CHECKS on it and it can't do that on a NONEXISTENT DIRECTORY
|
||||
return true;
|
||||
}
|
||||
else return false
|
||||
#endif
|
||||
}
|
||||
|
||||
string CoolGetCurrentDirectory()
|
||||
{
|
||||
//GUESS WHAT!
|
||||
//.NET DOES A SECURITY CHECK ON THE DIRECTORY WE JUST RETRIEVED
|
||||
//AS IF ASKING FOR THE CURRENT DIRECTORY IS EQUIVALENT TO TRYING TO ACCESS IT
|
||||
//SCREW YOU
|
||||
#if WINDOWS
|
||||
var buf = new byte[32768];
|
||||
fixed(byte* pBuf = &buf[0])
|
||||
{
|
||||
uint ret = GetCurrentDirectoryW(32767, pBuf);
|
||||
return System.Text.Encoding.Unicode.GetString(buf, 0, (int)ret*2);
|
||||
}
|
||||
#else
|
||||
return Environment.CurrentDirectory;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Sandbox(Action callback, Action exceptionCallback)
|
||||
{
|
||||
string savedEnvironmentCurrDir = null;
|
||||
try
|
||||
{
|
||||
//so. lets talk about current directories.
|
||||
//ideally we'd have one current directory per script. but things get hairy.
|
||||
//events and callbacks can get setup and it isn't clear what script they belong to.
|
||||
//moreover we don't really have a sense of sandboxing individual scripts, they kind of all get run together in the same VM, i think
|
||||
//so let's just try keeping one 'current directory' for all lua. it's an improvement over lua's 'current directory' for the process, interfering with the core emulator's
|
||||
savedEnvironmentCurrDir = Environment.CurrentDirectory;
|
||||
if(System.IO.Directory.Exists(CurrentDirectory)) //race condition for great justice
|
||||
Environment.CurrentDirectory = CurrentDirectory;
|
||||
savedEnvironmentCurrDir = Environment.CurrentDirectory;
|
||||
|
||||
if (CurrentDirectory != null)
|
||||
CoolSetCurrentDirectory(CurrentDirectory, savedEnvironmentCurrDir);
|
||||
|
||||
EnvironmentSandbox.Sandbox(callback);
|
||||
CurrentDirectory = Environment.CurrentDirectory;
|
||||
}
|
||||
catch (LuaException ex)
|
||||
{
|
||||
|
@ -49,12 +102,46 @@ namespace BizHawk.Client.Common
|
|||
}
|
||||
finally
|
||||
{
|
||||
if (savedEnvironmentCurrDir != null)
|
||||
{
|
||||
if (System.IO.Directory.Exists(savedEnvironmentCurrDir)) //race condition for great justice
|
||||
Environment.CurrentDirectory = savedEnvironmentCurrDir;
|
||||
}
|
||||
if (CurrentDirectory != null)
|
||||
CoolSetCurrentDirectory(savedEnvironmentCurrDir);
|
||||
}
|
||||
}
|
||||
|
||||
public static LuaSandbox CreateSandbox(Lua thread, string initialDirectory)
|
||||
{
|
||||
var sandbox = new LuaSandbox();
|
||||
SandboxForThread.Add(thread, sandbox);
|
||||
sandbox.SetSandboxCurrentDirectory(initialDirectory);
|
||||
sandbox.SetLogger(DefaultLogger);
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
public static LuaSandbox GetSandbox(Lua thread)
|
||||
{
|
||||
//this is just placeholder.
|
||||
//we shouldnt be calling a sandbox with no thread--construct a sandbox properly
|
||||
if (thread == null)
|
||||
{
|
||||
return new LuaSandbox();
|
||||
}
|
||||
|
||||
lock (SandboxForThread)
|
||||
{
|
||||
LuaSandbox sandbox;
|
||||
if (SandboxForThread.TryGetValue(thread, out sandbox))
|
||||
return sandbox;
|
||||
else
|
||||
{
|
||||
//for now: throw exception (I want to manually creating them)
|
||||
//return CreateSandbox(thread);
|
||||
throw new InvalidOperationException("HOARY GORILLA HIJINX");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Sandbox(Lua thread, Action callback, Action exceptionCallback = null)
|
||||
{
|
||||
GetSandbox(thread).Sandbox(callback, exceptionCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public void Call(string name = null)
|
||||
{
|
||||
LuaSandbox.Sandbox(() => {
|
||||
LuaSandbox.Sandbox(Lua, () => {
|
||||
_function.Call(name);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -382,7 +382,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
)]
|
||||
public int NewForm(int? width = null, int? height = null, string title = null, LuaFunction onClose = null)
|
||||
{
|
||||
var form = new LuaWinform();
|
||||
var form = new LuaWinform(CurrentThread);
|
||||
_luaForms.Add(form);
|
||||
if (width.HasValue && height.HasValue)
|
||||
{
|
||||
|
|
|
@ -153,34 +153,44 @@ namespace BizHawk.Client.EmuHawk
|
|||
public void ExecuteString(string command)
|
||||
{
|
||||
_currThread = _lua.NewThread();
|
||||
|
||||
_currThread.DoString(command);
|
||||
}
|
||||
|
||||
public ResumeResult ResumeScript(Lua script)
|
||||
{
|
||||
EventsLibrary.CurrentThread = script;
|
||||
_currThread = script;
|
||||
var execResult = script.Resume(0);
|
||||
|
||||
_lua.RunScheduledDisposes();
|
||||
//not sure how this is going to work out, so do this too
|
||||
script.RunScheduledDisposes();
|
||||
|
||||
_currThread = null;
|
||||
var result = new ResumeResult();
|
||||
if (execResult == 0)
|
||||
try
|
||||
{
|
||||
// terminated
|
||||
result.Terminated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// yielded
|
||||
result.WaitForFrame = FrameAdvanceRequested;
|
||||
}
|
||||
LuaLibraryBase.SetCurrentThread(_currThread);
|
||||
|
||||
FrameAdvanceRequested = false;
|
||||
return result;
|
||||
var execResult = script.Resume(0);
|
||||
|
||||
_lua.RunScheduledDisposes();
|
||||
//not sure how this is going to work out, so do this too
|
||||
script.RunScheduledDisposes();
|
||||
|
||||
_currThread = null;
|
||||
var result = new ResumeResult();
|
||||
if (execResult == 0)
|
||||
{
|
||||
// terminated
|
||||
result.Terminated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// yielded
|
||||
result.WaitForFrame = FrameAdvanceRequested;
|
||||
}
|
||||
|
||||
FrameAdvanceRequested = false;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
LuaLibraryBase.ClearCurrentThread();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Print(params object[] outputs)
|
||||
|
|
|
@ -76,8 +76,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
LuaListView.QueryItemIndent += LuaListView_QueryItemIndent;
|
||||
LuaListView.VirtualMode = true;
|
||||
|
||||
LuaSandbox.SetLogger(this.ConsoleLog);
|
||||
LuaSandbox.SetCurrentDirectory(PathManager.GetLuaPath());
|
||||
//this is bad, in case we ever have more than one gui part running lua.. not sure how much other badness there is like that
|
||||
LuaSandbox.DefaultLogger = ConsoleLog;
|
||||
}
|
||||
|
||||
public EmuLuaLibrary LuaImp { get; set; }
|
||||
|
@ -162,9 +162,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
string pathToLoad = Path.IsPathRooted(file.Path) ? file.Path : PathManager.MakeProgramRelativePath(file.Path); //JUNIPIER SQUATCHBOX COMPLEX
|
||||
try
|
||||
{
|
||||
LuaSandbox.Sandbox(() =>
|
||||
LuaSandbox.Sandbox(file.Thread, () =>
|
||||
{
|
||||
file.Thread = LuaImp.SpawnCoroutine(pathToLoad);
|
||||
LuaSandbox.CreateSandbox(file.Thread, Path.GetDirectoryName(pathToLoad));
|
||||
file.State = LuaFile.RunState.Running;
|
||||
}, () =>
|
||||
{
|
||||
|
@ -198,9 +199,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
try
|
||||
{
|
||||
LuaSandbox.Sandbox(() =>
|
||||
LuaSandbox.Sandbox(null, () =>
|
||||
{
|
||||
luaFile.Thread = LuaImp.SpawnCoroutine(pathToLoad);
|
||||
LuaSandbox.CreateSandbox(luaFile.Thread, Path.GetDirectoryName(pathToLoad));
|
||||
luaFile.State = LuaFile.RunState.Running;
|
||||
}, () =>
|
||||
{
|
||||
|
@ -245,13 +247,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
foreach (var file in _luaList)
|
||||
{
|
||||
if (file.Enabled && file.Thread == null)
|
||||
if (!file.Enabled && file.Thread == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
LuaSandbox.Sandbox(() =>
|
||||
LuaSandbox.Sandbox(null, () =>
|
||||
{
|
||||
string pathToLoad = Path.IsPathRooted(file.Path) ? file.Path : PathManager.MakeProgramRelativePath(file.Path); //JUNIPIER SQUATCHBOX COMPLEX
|
||||
file.Thread = LuaImp.SpawnCoroutine(file.Path);
|
||||
LuaSandbox.CreateSandbox(file.Thread, Path.GetDirectoryName(pathToLoad));
|
||||
}, () =>
|
||||
{
|
||||
file.State = LuaFile.RunState.Disabled;
|
||||
|
@ -454,17 +458,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
try
|
||||
{
|
||||
LuaSandbox.Sandbox(() =>
|
||||
LuaSandbox.Sandbox(lf.Thread, () =>
|
||||
{
|
||||
var prohibit = lf.FrameWaiting && !includeFrameWaiters;
|
||||
if (!prohibit)
|
||||
{
|
||||
// Restore this lua thread's preferred current directory
|
||||
if (lf.CurrentDirectory != null)
|
||||
{
|
||||
Environment.CurrentDirectory = PathManager.MakeAbsolutePath(lf.CurrentDirectory, null);
|
||||
}
|
||||
|
||||
var result = LuaImp.ResumeScript(lf.Thread);
|
||||
if (result.Terminated)
|
||||
{
|
||||
|
@ -474,9 +472,6 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
lf.FrameWaiting = result.WaitForFrame;
|
||||
|
||||
// If the lua thread changed its current directory, capture that here
|
||||
lf.CurrentDirectory = Environment.CurrentDirectory;
|
||||
}
|
||||
}, () =>
|
||||
{
|
||||
|
@ -768,9 +763,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
try
|
||||
{
|
||||
LuaSandbox.Sandbox(() =>
|
||||
LuaSandbox.Sandbox(null, () =>
|
||||
{
|
||||
string pathToLoad = Path.IsPathRooted(item.Path) ? item.Path : PathManager.MakeProgramRelativePath(item.Path); //JUNIPIER SQUATCHBOX COMPLEX
|
||||
item.Thread = LuaImp.SpawnCoroutine(item.Path);
|
||||
LuaSandbox.CreateSandbox(item.Thread, Path.GetDirectoryName(pathToLoad));
|
||||
}, () =>
|
||||
{
|
||||
item.State = LuaFile.RunState.Disabled;
|
||||
|
@ -1174,12 +1171,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
// TODO: Maybe make these try-catches more general
|
||||
if (InputBox.Text != "")
|
||||
{
|
||||
LuaSandbox.Sandbox(() =>
|
||||
LuaSandbox.Sandbox(null, () =>
|
||||
{
|
||||
LuaImp.ExecuteString(string.Format("console.log({0})", InputBox.Text));
|
||||
}, () =>
|
||||
{
|
||||
LuaSandbox.Sandbox(() =>
|
||||
LuaSandbox.Sandbox(null, () =>
|
||||
{
|
||||
LuaImp.ExecuteString(InputBox.Text);
|
||||
|
||||
|
|
|
@ -12,9 +12,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
private string CurrentDirectory = Environment.CurrentDirectory;
|
||||
|
||||
public LuaWinform()
|
||||
Lua OwnerThread;
|
||||
|
||||
public LuaWinform(Lua ownerThread)
|
||||
{
|
||||
InitializeComponent();
|
||||
OwnerThread = ownerThread;
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
Closing += (o, e) => CloseThis();
|
||||
}
|
||||
|
@ -31,7 +34,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public void DoLuaEvent(IntPtr handle)
|
||||
{
|
||||
LuaSandbox.Sandbox(() =>
|
||||
LuaSandbox.Sandbox(OwnerThread, () =>
|
||||
{
|
||||
Environment.CurrentDirectory = CurrentDirectory;
|
||||
foreach (LuaEvent l_event in ControlEvents)
|
||||
|
|
Loading…
Reference in New Issue