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

This commit is contained in:
Florian Nücke 2014-01-06 17:19:04 +01:00
parent 9478907a58
commit 3b59b53d5c
5 changed files with 145 additions and 119 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)
}
}

View File

@ -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.