/*
* Copyright 2019 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using CommonUtil;
using PluginCommon;
namespace SourceGen.Sandbox {
///
/// Maintains a collection of IPlugin instances, or communicates with the remote
/// PluginManager that holds the collection. Whether the plugins are instantiated
/// locally depends on how the class is constructed.
///
public class ScriptManager {
public const string FILENAME_EXT = ".cs";
public static readonly string FILENAME_FILTER = Res.Strings.FILE_FILTER_CS;
///
/// If true, the DomainManager will use the keep-alive timer hack.
///
public static bool UseKeepAliveHack { get; set; }
///
/// Reference to DomainManager, if we're using one.
///
public DomainManager DomainMgr { get; private set; }
///
/// Collection of loaded plugins, if we're not using a DomainManager.
///
private Dictionary mActivePlugins;
///
/// Reference to project, from which we can get the file data and project path name.
///
private DisasmProject mProject;
///
/// Constructor.
///
public ScriptManager(DisasmProject proj) {
mProject = proj;
if (!proj.UseMainAppDomainForPlugins) {
DomainMgr = new DomainManager(UseKeepAliveHack);
DomainMgr.CreateDomain("Plugin Domain", PluginDllCache.GetPluginDirPath());
DomainMgr.PluginMgr.SetFileData(proj.FileData);
} else {
mActivePlugins = new Dictionary();
}
}
///
/// Cleans up, discarding the AppDomain if one was created. Do not continue to use
/// the object after calling this.
///
public void Cleanup() {
if (DomainMgr != null) {
DomainMgr.Dispose();
DomainMgr = null;
}
mActivePlugins = null;
mProject = null;
}
///
/// Clears the list of plugins. This does not unload assemblies. Call this when
/// the list of extension scripts configured into the project has changed.
///
public void Clear() {
if (DomainMgr == null) {
mActivePlugins.Clear();
} else {
DomainMgr.PluginMgr.ClearPluginList();
}
}
///
/// Attempts to load the specified plugin. If the plugin is already loaded, this
/// does nothing. If not, the assembly is loaded and an instance is created.
///
/// Script identifier.
/// Report with errors and warnings.
/// True on success.
public bool LoadPlugin(string scriptIdent, out FileLoadReport report) {
// Make sure the most recent version is compiled.
string dllPath = PluginDllCache.GenerateScriptDll(scriptIdent,
mProject.ProjectPathName, out report);
if (dllPath == null) {
return false;
}
if (DomainMgr == null) {
if (mActivePlugins.TryGetValue(scriptIdent, out IPlugin plugin)) {
return true;
}
Assembly asm = Assembly.LoadFile(dllPath);
plugin = PluginDllCache.ConstructIPlugin(asm);
mActivePlugins.Add(scriptIdent, plugin);
report = new FileLoadReport(dllPath); // empty report
return true;
} else {
IPlugin plugin = DomainMgr.PluginMgr.LoadPlugin(dllPath, scriptIdent,
out string failMsg);
if (plugin == null) {
report.Add(FileLoadItem.Type.Error, "Failed loading plugin: " + failMsg);
}
return plugin != null;
}
}
public IPlugin GetInstance(string scriptIdent) {
if (DomainMgr == null) {
if (mActivePlugins.TryGetValue(scriptIdent, out IPlugin plugin)) {
return plugin;
}
Debug.Assert(false);
return null;
} else {
return DomainMgr.PluginMgr.GetPlugin(scriptIdent);
}
}
///
/// Generates a list of references to instances of loaded plugins.
///
/// Newly-created list of plugin references.
public List GetAllInstances() {
Dictionary dict;
if (DomainMgr == null) {
dict = mActivePlugins;
} else {
dict = DomainMgr.PluginMgr.GetActivePlugins();
}
List list = new List(dict.Count);
foreach (KeyValuePair kvp in dict) {
list.Add(kvp.Value);
}
return list;
}
///
/// Prepares all active scripts for action.
///
/// Reference to object providing app services.
public void PrepareScripts(IApplication appRef) {
List plSyms = GeneratePlSymbolList();
if (DomainMgr == null) {
AddressTranslate addrTrans = new AddressTranslate(mProject.AddrMap);
foreach (KeyValuePair kvp in mActivePlugins) {
IPlugin ipl = kvp.Value;
ipl.Prepare(appRef, mProject.FileData, addrTrans);
if (ipl is IPlugin_SymbolList) {
((IPlugin_SymbolList)ipl).UpdateSymbolList(plSyms);
}
}
} else {
List addrEnts = mProject.AddrMap.GetEntryList();
DomainMgr.PluginMgr.PreparePlugins(appRef, addrEnts, plSyms);
}
}
///
/// Puts scripts back to sleep.
///
public void UnprepareScripts() {
if (DomainMgr == null) {
foreach (KeyValuePair kvp in mActivePlugins) {
IPlugin ipl = kvp.Value;
ipl.Unprepare();
}
} else {
List addrEnts = mProject.AddrMap.GetEntryList();
DomainMgr.PluginMgr.UnpreparePlugins();
}
}
///
/// Returns true if any of the plugins report that the before or after label is
/// significant.
///
public bool IsLabelSignificant(Symbol before, Symbol after) {
string labelBefore = (before == null) ? string.Empty : before.Label;
string labelAfter = (after == null) ? string.Empty : after.Label;
if (DomainMgr == null) {
foreach (KeyValuePair kvp in mActivePlugins) {
IPlugin ipl = kvp.Value;
if (ipl is IPlugin_SymbolList &&
((IPlugin_SymbolList)ipl).IsLabelSignificant(labelBefore,
labelAfter)) {
return true;
}
}
return false;
} else {
return DomainMgr.PluginMgr.IsLabelSignificant(labelBefore, labelAfter);
}
}
///
/// Gathers a list of platform symbols from the project's symbol table.
///
private List GeneratePlSymbolList() {
List plSymbols = new List();
SymbolTable symTab = mProject.SymbolTable;
foreach (Symbol sym in symTab) {
PlSymbol.Source plsSource;
switch (sym.SymbolSource) {
case Symbol.Source.Platform:
plsSource = PlSymbol.Source.Platform;
break;
case Symbol.Source.Project:
plsSource = PlSymbol.Source.Project;
break;
case Symbol.Source.User:
plsSource = PlSymbol.Source.User;
break;
case Symbol.Source.Variable:
case Symbol.Source.Auto:
// don't forward these to plugins
continue;
default:
Debug.Assert(false);
continue;
}
PlSymbol.Type plsType;
switch (sym.SymbolType) {
case Symbol.Type.NonUniqueLocalAddr:
// don't forward these to plugins
continue;
case Symbol.Type.LocalOrGlobalAddr:
case Symbol.Type.GlobalAddr:
case Symbol.Type.GlobalAddrExport:
case Symbol.Type.ExternalAddr:
plsType = PlSymbol.Type.Address;
break;
case Symbol.Type.Constant:
plsType = PlSymbol.Type.Constant;
break;
default:
Debug.Assert(false);
continue;
}
int width = -1;
string tag = string.Empty;
if (sym is DefSymbol) {
DefSymbol defSym = sym as DefSymbol;
width = defSym.DataDescriptor.Length;
tag = defSym.Tag;
}
plSymbols.Add(new PlSymbol(sym.Label, sym.Value, width, plsSource, plsType, tag));
}
return plSymbols;
}
#if false
public delegate bool CheckMatch(IPlugin plugin);
public IPlugin GetMatchingScript(CheckMatch check) {
Dictionary plugins;
if (DomainMgr == null) {
plugins = mActivePlugins;
} else {
plugins = DomainMgr.PluginMgr.GetActivePlugins();
}
foreach (IPlugin plugin in plugins.Values) {
if (check(plugin)) {
return plugin;
}
}
return null;
}
#endif
///
/// Returns a list of loaded plugins. Callers should not retain this list, as the
/// set can change due to user activity.
///
public Dictionary GetActivePlugins() {
if (DomainMgr == null) {
// copy the contents
Dictionary pdict = new Dictionary();
foreach (KeyValuePair kvp in mActivePlugins) {
pdict.Add(kvp.Key, kvp.Value);
}
return pdict;
} else {
return DomainMgr.PluginMgr.GetActivePlugins();
}
}
///
/// For debugging purposes, get some information about the currently loaded
/// extension scripts.
///
public string DebugGetLoadedScriptInfo() {
StringBuilder sb = new StringBuilder();
if (DomainMgr == null) {
foreach (KeyValuePair kvp in mActivePlugins) {
string loc = kvp.Value.GetType().Assembly.Location;
sb.Append("[main] ");
sb.Append(loc);
sb.Append("\r\n ");
DebugGetScriptInfo(kvp.Value, sb);
}
} else {
Dictionary plugins = DomainMgr.PluginMgr.GetActivePlugins();
foreach (IPlugin plugin in plugins.Values) {
string loc = DomainMgr.PluginMgr.GetPluginAssemblyLocation(plugin);
sb.AppendFormat("[sub {0}] ", DomainMgr.Id);
sb.Append(loc);
sb.Append("\r\n ");
DebugGetScriptInfo(plugin, sb);
}
}
return sb.ToString();
}
private void DebugGetScriptInfo(IPlugin plugin, StringBuilder sb) {
sb.Append(plugin.Identifier);
sb.Append(":");
// The plugin is actually a MarshalByRefObject, so we can't use reflection
// to gather the list of interfaces.
// TODO(maybe): add a call that does a reflection query on the remote side
if (plugin is PluginCommon.IPlugin_SymbolList) {
sb.Append(" SymbolList");
}
if (plugin is PluginCommon.IPlugin_InlineJsr) {
sb.Append(" InlineJsr");
}
if (plugin is PluginCommon.IPlugin_InlineJsl) {
sb.Append(" InlineJsl");
}
if (plugin is PluginCommon.IPlugin_InlineBrk) {
sb.Append(" InlineBrk");
}
if (plugin is PluginCommon.IPlugin_Visualizer) {
sb.Append(" Visualizer");
}
sb.Append("\r\n");
}
}
}