mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-05-05 17:04:14 +00:00
Fix overflow when converting recursive objects to Lua
In cdcd82679c8e286738afd6f19d1d9ed845dcd2aa, we rewrote the Lua conversion function to update the "Java -> Lua" mapping after conversion, rather than part way through. This made the code a little cleaner (as we only updated the mapping in one place), but is entirely incorrect — we need to store the object first, in order to correctly handle recursive objects — otherwise we'll just recurse infinitely (or until we overflow). This partially reverts the above commit, while preserving the new behaviour for singleton collections. Fixes #1955.
This commit is contained in:
parent
c36c8605bf
commit
0069591af9
@ -95,11 +95,8 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
|
|
||||||
private void addAPI(LuaState state, LuaTable globals, ILuaAPI api) throws LuaError {
|
private void addAPI(LuaState state, LuaTable globals, ILuaAPI api) throws LuaError {
|
||||||
// Add the methods of an API to the global table
|
// Add the methods of an API to the global table
|
||||||
var table = wrapLuaObject(api);
|
var table = new LuaTable();
|
||||||
if (table == null) {
|
if (!makeLuaObject(api, table)) LOG.warn("API {} does not provide any methods", api);
|
||||||
LOG.warn("API {} does not provide any methods", api);
|
|
||||||
table = new LuaTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
var names = api.getNames();
|
var names = api.getNames();
|
||||||
for (var name : names) globals.rawset(name, table);
|
for (var name : names) globals.rawset(name, table);
|
||||||
@ -163,13 +160,16 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
timeout.removeListener(timeoutListener);
|
timeout.removeListener(timeoutListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
/**
|
||||||
private LuaTable wrapLuaObject(Object object) {
|
* Populate a table with methods from an object.
|
||||||
var table = new LuaTable();
|
*
|
||||||
var found = luaMethods.forEachMethod(object, (target, name, method, info) ->
|
* @param object The object to draw methods from.
|
||||||
|
* @param table The table to fill.
|
||||||
|
* @return Whether any methods were found.
|
||||||
|
*/
|
||||||
|
private boolean makeLuaObject(Object object, LuaTable table) {
|
||||||
|
return luaMethods.forEachMethod(object, (target, name, method, info) ->
|
||||||
table.rawset(name, new ResultInterpreterFunction(this, method, target, context, name)));
|
table.rawset(name, new ResultInterpreterFunction(this, method, target, context, name)));
|
||||||
|
|
||||||
return found ? table : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Object, LuaValue> values) throws LuaError {
|
private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Object, LuaValue> values) throws LuaError {
|
||||||
@ -184,47 +184,35 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
return ValueFactory.valueOf(bytes);
|
return ValueFactory.valueOf(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't share singleton values, and instead convert them to a new table.
|
// We have a more complex object, which is possibly recursive. First look up our object in the lookup map,
|
||||||
if (LuaUtil.isSingletonCollection(object)) return new LuaTable();
|
// and reuse it if present.
|
||||||
|
|
||||||
if (values == null) values = new IdentityHashMap<>(1);
|
if (values == null) values = new IdentityHashMap<>(1);
|
||||||
var result = values.get(object);
|
var result = values.get(object);
|
||||||
if (result != null) return result;
|
if (result != null) return result;
|
||||||
|
|
||||||
var wrapped = toValueWorker(object, values);
|
|
||||||
if (wrapped == null) {
|
|
||||||
LOG.warn(Logging.JAVA_ERROR, "Received unknown type '{}', returning nil.", object.getClass().getName());
|
|
||||||
return Constants.NIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
values.put(object, wrapped);
|
|
||||||
return wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a complex Java object (such as a collection or Lua object) to a Lua value.
|
|
||||||
* <p>
|
|
||||||
* This is a worker function for {@link #toValue(Object, IdentityHashMap)}, which handles the actual construction
|
|
||||||
* of values, without reading/writing from the value map.
|
|
||||||
*
|
|
||||||
* @param object The object to convert.
|
|
||||||
* @param values The map of Java to Lua values.
|
|
||||||
* @return The converted value, or {@code null} if it could not be converted.
|
|
||||||
* @throws LuaError If the value could not be converted.
|
|
||||||
*/
|
|
||||||
private @Nullable LuaValue toValueWorker(Object object, IdentityHashMap<Object, LuaValue> values) throws LuaError {
|
|
||||||
if (object instanceof ILuaFunction) {
|
if (object instanceof ILuaFunction) {
|
||||||
return new ResultInterpreterFunction(this, FUNCTION_METHOD, object, context, object.toString());
|
var function = new ResultInterpreterFunction(this, FUNCTION_METHOD, object, context, object.toString());
|
||||||
|
values.put(object, function);
|
||||||
|
return function;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object instanceof IDynamicLuaObject) {
|
if (object instanceof IDynamicLuaObject) {
|
||||||
LuaValue wrapped = wrapLuaObject(object);
|
var table = new LuaTable();
|
||||||
if (wrapped == null) wrapped = new LuaTable();
|
makeLuaObject(object, table);
|
||||||
return wrapped;
|
values.put(object, table);
|
||||||
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following objects may be recursive. In these instances, we need to be careful to store the value *before*
|
||||||
|
// recursing, to avoid stack overflows.
|
||||||
|
|
||||||
if (object instanceof Map<?, ?> map) {
|
if (object instanceof Map<?, ?> map) {
|
||||||
|
// Don't share singleton values, and instead convert them to a new table.
|
||||||
|
if (LuaUtil.isSingletonMap(map)) return new LuaTable();
|
||||||
|
|
||||||
var table = new LuaTable();
|
var table = new LuaTable();
|
||||||
|
values.put(object, table);
|
||||||
|
|
||||||
for (var pair : map.entrySet()) {
|
for (var pair : map.entrySet()) {
|
||||||
var key = toValue(pair.getKey(), values);
|
var key = toValue(pair.getKey(), values);
|
||||||
var value = toValue(pair.getValue(), values);
|
var value = toValue(pair.getValue(), values);
|
||||||
@ -234,7 +222,12 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (object instanceof Collection<?> objects) {
|
if (object instanceof Collection<?> objects) {
|
||||||
|
// Don't share singleton values, and instead convert them to a new table.
|
||||||
|
if (LuaUtil.isSingletonCollection(objects)) return new LuaTable();
|
||||||
|
|
||||||
var table = new LuaTable(objects.size(), 0);
|
var table = new LuaTable(objects.size(), 0);
|
||||||
|
values.put(object, table);
|
||||||
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
for (var child : objects) table.rawset(++i, toValue(child, values));
|
for (var child : objects) table.rawset(++i, toValue(child, values));
|
||||||
return table;
|
return table;
|
||||||
@ -242,11 +235,20 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
|
|
||||||
if (object instanceof Object[] objects) {
|
if (object instanceof Object[] objects) {
|
||||||
var table = new LuaTable(objects.length, 0);
|
var table = new LuaTable(objects.length, 0);
|
||||||
|
values.put(object, table);
|
||||||
|
|
||||||
for (var i = 0; i < objects.length; i++) table.rawset(i + 1, toValue(objects[i], values));
|
for (var i = 0; i < objects.length; i++) table.rawset(i + 1, toValue(objects[i], values));
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapLuaObject(object);
|
var table = new LuaTable();
|
||||||
|
if (makeLuaObject(object, table)) {
|
||||||
|
values.put(object, table);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.warn(Logging.JAVA_ERROR, "Received unknown type '{}', returning nil.", object.getClass().getName());
|
||||||
|
return Constants.NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Varargs toValues(@Nullable Object[] objects) throws LuaError {
|
Varargs toValues(@Nullable Object[] objects) throws LuaError {
|
||||||
|
@ -36,7 +36,20 @@ public class LuaUtil {
|
|||||||
* @param value The value to test.
|
* @param value The value to test.
|
||||||
* @return Whether this is a singleton collection.
|
* @return Whether this is a singleton collection.
|
||||||
*/
|
*/
|
||||||
public static boolean isSingletonCollection(Object value) {
|
public static boolean isSingletonCollection(Collection<?> value) {
|
||||||
return value == EMPTY_LIST || value == EMPTY_SET || value == EMPTY_MAP;
|
return value == EMPTY_LIST || value == EMPTY_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a value is a singleton map, such as one created with {@link Map#of()}.
|
||||||
|
* <p>
|
||||||
|
* These collections are treated specially by {@link ILuaMachine} implementations: we skip sharing for them, and
|
||||||
|
* create a new table each time.
|
||||||
|
*
|
||||||
|
* @param value The value to test.
|
||||||
|
* @return Whether this is a singleton map.
|
||||||
|
*/
|
||||||
|
public static boolean isSingletonMap(Map<?, ?> value) {
|
||||||
|
return value == EMPTY_MAP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,14 @@ describe("The os library", function()
|
|||||||
expect(xs[1]):eq(xs[2])
|
expect(xs[1]):eq(xs[2])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("handles recursive tables", function()
|
||||||
|
local tbl = {}
|
||||||
|
tbl[1] = tbl
|
||||||
|
|
||||||
|
local xs = roundtrip(tbl)
|
||||||
|
expect(xs):eq(xs[1])
|
||||||
|
end)
|
||||||
|
|
||||||
it("does not preserve references in separate args", function()
|
it("does not preserve references in separate args", function()
|
||||||
-- I'm not sure I like this behaviour, but it is what CC has always done.
|
-- I'm not sure I like this behaviour, but it is what CC has always done.
|
||||||
local tbl = {}
|
local tbl = {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user