From 3b59b53d5c243f320a8d9807a49aed3dc8de480d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 6 Jan 2014 17:19:04 +0100 Subject: [PATCH] added setting to allow logging of tracebacks for exceptions that occurred in Lua callbacks (disabled per default to avoid spamming the log with InvalidArgumentExceptions and the like); moved argument parsing and result pushing to extended lua state and added capability to push maps as tables, meaning callbacks can now return maps, too --- li/cil/oc/Settings.scala | 1 + li/cil/oc/server/component/Computer.scala | 172 ++++++++-------------- li/cil/oc/server/network/Component.scala | 5 +- li/cil/oc/util/ExtendedLuaState.scala | 77 +++++++++- reference.conf | 9 ++ 5 files changed, 145 insertions(+), 119 deletions(-) diff --git a/li/cil/oc/Settings.scala b/li/cil/oc/Settings.scala index 51de063c4..f8c2a447c 100644 --- a/li/cil/oc/Settings.scala +++ b/li/cil/oc/Settings.scala @@ -43,6 +43,7 @@ class Settings(config: Config) { val maxUsers = config.getInt("computer.maxUsers") max 0 val maxUsernameLength = config.getInt("computer.maxUsernameLength") max 0 val allowBytecode = config.getBoolean("computer.allowBytecode") + val logLuaCallbackErrors = config.getBoolean("computer.logCallbackErrors") // ----------------------------------------------------------------------- // // robot diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index e7271a642..0b9cbfd1f 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -20,8 +20,6 @@ import scala.Array.canBuildFrom import scala.Some import scala.collection.convert.WrapAsScala._ import scala.collection.mutable -import scala.math.ScalaNumber -import scala.runtime.BoxedUnit class Computer(val owner: tileentity.Computer) extends ManagedComponent with Context with Runnable { val node = api.Network.newNode(this, Visibility.Network). @@ -665,57 +663,6 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con // ----------------------------------------------------------------------- // private def init(): Boolean = { - // Utility functions for varargs callbacks. - def parseArgument(lua: LuaState, index: Int): AnyRef = lua.`type`(index) match { - case LuaType.BOOLEAN => Boolean.box(lua.toBoolean(index)) - case LuaType.NUMBER => Double.box(lua.toNumber(index)) - case LuaType.STRING => lua.toByteArray(index) - case LuaType.TABLE => lua.toJavaObject(index, classOf[java.util.Map[_, _]]) - case _ => Unit - } - - def parseArguments(lua: LuaState, start: Int) = - for (index <- start to lua.getTop) yield parseArgument(lua, index) - - def pushList(value: Iterator[(Any, Int)]) { - lua.newTable() - var count = 0 - value.foreach { - case (x, index) => x match { - case (entry: ScalaNumber) => - pushResult(lua, entry.underlying()) - case (entry) => - pushResult(lua, entry.asInstanceOf[AnyRef]) - } - lua.rawSet(-2, index + 1) - count = count + 1 - } - lua.pushString("n") - lua.pushInteger(count) - lua.rawSet(-3) - } - - def pushResult(lua: LuaState, value: AnyRef): Unit = value match { - case null | Unit | _: BoxedUnit => lua.pushNil() - case value: java.lang.Boolean => lua.pushBoolean(value.booleanValue) - case value: java.lang.Byte => lua.pushNumber(value.byteValue) - case value: java.lang.Character => lua.pushString(String.valueOf(value)) - case value: java.lang.Short => lua.pushNumber(value.shortValue) - case value: java.lang.Integer => lua.pushNumber(value.intValue) - case value: java.lang.Long => lua.pushNumber(value.longValue) - case value: java.lang.Float => lua.pushNumber(value.floatValue) - case value: java.lang.Double => lua.pushNumber(value.doubleValue) - case value: java.lang.String => lua.pushString(value) - case value: Array[Byte] => lua.pushByteArray(value) - case value: Array[_] => pushList(value.zipWithIndex.iterator) - case value: Product => pushList(value.productIterator.zipWithIndex) - case value: Seq[_] => pushList(value.zipWithIndex.iterator) - // TODO maps? - case _ => - OpenComputers.log.warning("A component callback tried to return an unsupported value of type " + value.getClass.getName + ".") - lua.pushNil() - } - // Reset error state. message = None @@ -862,7 +809,7 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con lua.setField(-2, "totalMemory") lua.pushScalaFunction(lua => { - lua.pushBoolean(signal(lua.checkString(1), parseArguments(lua, 2): _*)) + lua.pushBoolean(signal(lua.checkString(1), lua.toSimpleJavaObjects(2): _*)) 1 }) lua.setField(-2, "pushSignal") @@ -1029,7 +976,7 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con lua.pushScalaFunction(lua => { val address = lua.checkString(1) val method = lua.checkString(2) - val args = parseArguments(lua, 3) + val args = lua.toSimpleJavaObjects(3) try { (Option(node.network.node(address)) match { case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node => @@ -1048,57 +995,68 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con }) match { case results: Array[_] => lua.pushBoolean(true) - results.foreach(pushResult(lua, _)) + results.foreach(result => lua.pushValue(result)) 1 + results.length case _ => lua.pushBoolean(true) 1 } - } catch { - case _: LimitReachedException => - 0 - case e: IllegalArgumentException if e.getMessage != null => - lua.pushBoolean(false) - lua.pushString(e.getMessage) - 2 - case e: Throwable if e.getMessage != null => - lua.pushBoolean(true) - lua.pushNil() - lua.pushString(e.getMessage) - 3 - case _: ArrayIndexOutOfBoundsException => - lua.pushBoolean(false) - lua.pushString("index out of bounds") - 2 - case _: IllegalArgumentException => - lua.pushBoolean(false) - lua.pushString("bad argument") - 2 - case _: NoSuchMethodException => - lua.pushBoolean(false) - lua.pushString("no such method") - 2 - case _: FileNotFoundException => - lua.pushBoolean(true) - lua.pushNil() - lua.pushString("file not found") - 3 - case _: SecurityException => - lua.pushBoolean(true) - lua.pushNil() - lua.pushString("access denied") - 3 - case _: IOException => - lua.pushBoolean(true) - lua.pushNil() - lua.pushString("i/o error") - 3 + } + catch { case e: Throwable => - OpenComputers.log.log(Level.WARNING, "Unexpected error in Lua callback.", e) - lua.pushBoolean(true) - lua.pushNil() - lua.pushString("unknown error") - 3 + if (Settings.get.logLuaCallbackErrors) { + OpenComputers.log.log(Level.WARNING, "Exception in Lua callback.", e) + } + e match { + case _: LimitReachedException => + 0 + case e: IllegalArgumentException if e.getMessage != null => + lua.pushBoolean(false) + lua.pushString(e.getMessage) + 2 + case e: Throwable if e.getMessage != null => + lua.pushBoolean(true) + lua.pushNil() + lua.pushString(e.getMessage) + if (true) { + lua.pushString(e.getStackTraceString) + 4 + } + else 3 + case _: IndexOutOfBoundsException => + lua.pushBoolean(false) + lua.pushString("index out of bounds") + 2 + case _: IllegalArgumentException => + lua.pushBoolean(false) + lua.pushString("bad argument") + 2 + case _: NoSuchMethodException => + lua.pushBoolean(false) + lua.pushString("no such method") + 2 + case _: FileNotFoundException => + lua.pushBoolean(true) + lua.pushNil() + lua.pushString("file not found") + 3 + case _: SecurityException => + lua.pushBoolean(true) + lua.pushNil() + lua.pushString("access denied") + 3 + case _: IOException => + lua.pushBoolean(true) + lua.pushNil() + lua.pushString("i/o error") + 3 + case e: Throwable => + OpenComputers.log.log(Level.WARNING, "Unexpected error in Lua callback.", e) + lua.pushBoolean(true) + lua.pushNil() + lua.pushString("unknown error") + 3 + } } }) lua.setField(-2, "invoke") @@ -1293,19 +1251,7 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con else (signals.synchronized(if (signals.isEmpty) None else Some(signals.dequeue())) match { case Some(signal) => lua.pushString(signal.name) - signal.args.foreach { - case Unit => lua.pushNil() - case arg: Boolean => lua.pushBoolean(arg) - case arg: Double => lua.pushNumber(arg) - case arg: String => lua.pushString(arg) - case arg: Array[Byte] => lua.pushByteArray(arg) - case arg: Map[String, String] => - lua.newTable(0, arg.size) - for ((key, value) <- arg if key != null && value != null) { - lua.pushString(value) - lua.setField(-2, key) - } - } + signal.args.foreach(arg => lua.pushValue(arg)) lua.resume(1, 1 + signal.args.length) case _ => lua.resume(1, 0) diff --git a/li/cil/oc/server/network/Component.scala b/li/cil/oc/server/network/Component.scala index 5e3a3833b..a07330245 100644 --- a/li/cil/oc/server/network/Component.scala +++ b/li/cil/oc/server/network/Component.scala @@ -3,7 +3,6 @@ package li.cil.oc.server.network import cpw.mods.fml.common.FMLCommonHandler import cpw.mods.fml.relauncher.Side import java.lang.reflect.{Method, InvocationTargetException} -import java.util import li.cil.oc.api import li.cil.oc.api.network._ import li.cil.oc.server.component @@ -249,7 +248,7 @@ object Component { def isTable(index: Int) = index >= 0 && index < count && (args(index) match { - case value: util.Map[_, _] => true + case value: java.util.Map[_, _] => true case value: Map[_, _] => true case value: mutable.Map[_, _] => true case _ => false @@ -272,7 +271,7 @@ object Component { case _: java.lang.Double => "double" case _: java.lang.String => "string" case _: Array[Byte] => "string" - case value: util.Map[_, _] => "table" + case value: java.util.Map[_, _] => "table" case value: Map[_, _] => "table" case value: mutable.Map[_, _] => "table" case _ => value.getClass.getSimpleName diff --git a/li/cil/oc/util/ExtendedLuaState.scala b/li/cil/oc/util/ExtendedLuaState.scala index 653777a38..df466c68c 100644 --- a/li/cil/oc/util/ExtendedLuaState.scala +++ b/li/cil/oc/util/ExtendedLuaState.scala @@ -1,16 +1,87 @@ package li.cil.oc.util -import com.naef.jnlua.{JavaFunction, LuaState} +import com.naef.jnlua.{LuaType, JavaFunction, LuaState} +import li.cil.oc.OpenComputers +import scala.collection.convert.WrapAsScala._ +import scala.collection.mutable import scala.language.implicitConversions +import scala.math.ScalaNumber +import scala.runtime.BoxedUnit object ExtendedLuaState { implicit def extendLuaState(state: LuaState) = new ExtendedLuaState(state) - class ExtendedLuaState(val state: LuaState) { - def pushScalaFunction(f: (LuaState) => Int) = state.pushJavaFunction(new JavaFunction { + class ExtendedLuaState(val lua: LuaState) { + def pushScalaFunction(f: (LuaState) => Int) = lua.pushJavaFunction(new JavaFunction { override def invoke(state: LuaState) = f(state) }) + + def pushValue(value: Any) { + (value match { + case number: ScalaNumber => number.underlying + case reference: AnyRef => reference + case null => null + case primitive => primitive.asInstanceOf[AnyRef] + }) match { + case null | Unit | _: BoxedUnit => lua.pushNil() + case value: java.lang.Boolean => lua.pushBoolean(value.booleanValue) + case value: java.lang.Byte => lua.pushNumber(value.byteValue) + case value: java.lang.Character => lua.pushString(String.valueOf(value)) + case value: java.lang.Short => lua.pushNumber(value.shortValue) + case value: java.lang.Integer => lua.pushNumber(value.intValue) + case value: java.lang.Long => lua.pushNumber(value.longValue) + case value: java.lang.Float => lua.pushNumber(value.floatValue) + case value: java.lang.Double => lua.pushNumber(value.doubleValue) + case value: java.lang.String => lua.pushString(value) + case value: Array[Byte] => lua.pushByteArray(value) + case value: Array[_] => pushList(value.zipWithIndex.iterator) + case value: Product => pushList(value.productIterator.zipWithIndex) + case value: Seq[_] => pushList(value.zipWithIndex.iterator) + case value: java.util.Map[_, _] => pushTable(value.toMap) + case value: Map[_, _] => pushTable(value) + case value: mutable.Map[_, _] => pushTable(value.toMap) + case _ => + OpenComputers.log.warning("Tried to push an unsupported value of type to Lua: " + value.getClass.getName + ".") + lua.pushNil() + } + } + + def pushList(list: Iterator[(Any, Int)]) { + lua.newTable() + var count = 0 + list.foreach { + case (value, index) => + pushValue(value) + lua.rawSet(-2, index + 1) + count = count + 1 + } + lua.pushString("n") + lua.pushInteger(count) + lua.rawSet(-3) + } + + def pushTable(map: Map[_, _]) { + lua.newTable(0, map.size) + for ((key: AnyRef, value: AnyRef) <- map) { + if (key != null && key != Unit && !key.isInstanceOf[BoxedUnit]) { + pushValue(key) + pushValue(value) + lua.setTable(-3) + } + } + } + + def toSimpleJavaObject(index: Int): AnyRef = lua.`type`(index) match { + case LuaType.BOOLEAN => Boolean.box(lua.toBoolean(index)) + case LuaType.NUMBER => Double.box(lua.toNumber(index)) + case LuaType.STRING => lua.toByteArray(index) + case LuaType.TABLE => lua.toJavaObject(index, classOf[java.util.Map[_, _]]) + case _ => null + } + + def toSimpleJavaObjects(start: Int) = + for (index <- start to lua.getTop) yield toSimpleJavaObject(index) } } \ No newline at end of file diff --git a/reference.conf b/reference.conf index 84f59da85..d04acef18 100644 --- a/reference.conf +++ b/reference.conf @@ -150,6 +150,15 @@ opencomputers { # list of registered users on the Lua side. # See also: `canComputersBeOwned`. maxUsernameLength: 32 + + # This setting is meant for debugging errors that occur in Lua callbacks. + # Per default, if an error occurs and it has a message set, only the + # message is pushed back to Lua, and that's it. If you encounter weird + # errors or are developing an addon you'll want the stacktrace for those + # errors. Enabling this setting will log them to the game log. This is + # disabled per default to avoid spamming the log with inconsequentual + # exceptions such as IllegalArgumentExceptions and the like. + logCallbackErrors: false } # Robot related settings, what they may do and general balancing.