LuaJ массив / список типов безопасности

Так что с помощью LuaJ.

Если я перейду с Java на Lua, userdata List<T> с типом TLuaj по-прежнему допускает вставку в этот массив объектов любого типа через :add функция. Например:

Java-код:

import java.util.ArrayList;
import org.luaj.vm2.Globals;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.lib.jse.JsePlatform;
import org.luaj.vm2.LuaValue;

ArrayList<Integer>ExampleList=new ArrayList<>();
ExampleList.add(1);
LuaValue[] LuaParams=new LuaValue[] {
    CoerceJavaToLua.coerce(ExampleList)
};

Globals globals=JsePlatform.standardGlobals();
try { globals.get("TestFunc").invoke(LuaValue.varargsOf(LuaParams)); }
catch(Exception e) {}

Lua:

function TestFunc(arr)
    arr:add("str")
    arr:add(2);
end

Результат ExampleList:

{
    new Integer(1),
    new String("str"), //This should not be allowed!
    new Integer(2)
}

Эта строка не должна была быть разрешена с ExampleList это List<Integer>

Вопрос: есть ли способ поддерживать безопасность типов?

Если это помогает для тестирования, вот код для добавления скрипта lua в память lua (непосредственно перед try{}):

globals.load(
    "function TestFunc(arr)\n"+
    "        arr:add(\"str\")\n"+
    "        arr:add(2);\n"+
    "end",
"ExampleScript").call();

1 ответ

Решение

Проведя исследование, я обнаружил, что невозможно определить, какой универсальный тип объявлен массивом. Java не хранит эту информацию в объекте. Во время выполнения он просто использует тип, объявленный массивом для текущей ссылки на переменную.

Все, что вы можете сделать, это посмотреть на объекты внутри него, чтобы определить, каким он должен быть, но это не является надежным.

Если массив определен в другом объекте, вы можете просмотреть поля родительского объекта, чтобы получить компонент / шаблон / универсальный тип массива.

Отражение ArrayList

[edit of 2016-07-06] Еще один предложенный мне метод, о котором я знал, - это расширение всех классов списков с помощью интерфейса, который фактически хранит тип класса. Это не очень практично для проекта. После того, как он обдумал это, становится понятно, почему Java не хранит общий тип класса для списка.

Решение, которое я использовал, было редактированием. org.luaj.vm2.lib.jse.JavaMethod.invokeMethod(Object instance, Varargs args) со следующим (после Object[] a = convertArgs(args); линия:

//If this is adding/setting to a list, make sure the object type matches the list's 0th object type
java.util.List TheInstanceList;
if(
    instance instanceof java.util.List && //Object is a list
    java.util.Arrays.asList("add", "set").contains(method.getName()) && //Adding/setting to list
    (TheInstanceList=(java.util.List)instance).size()>0 && //List already has at least 1 item
    !a[a.length>1 ? 1 : 0].getClass().isInstance(TheInstanceList.get(0)) //New item does not match type of item #0
)
    return LuaValue.error(String.format(
            "list coercion error: %s is not instanceof %s",
            a[a.length>1 ? 1 : 0].getClass().getName(),
            TheInstanceList.get(0).getClass().getName()
    ));

Хотя это может быть расширено, чтобы учесть совпадение родительских классов, обойдя оба объекта extended-parent-type-list (все до java.lang.Object) это было бы менее безопасно с точки зрения безопасности типов, чем то, что нам нужно для проекта.

Решение, которое я использовал выше, специально предназначено для устранения ошибок в сценариях LUA до того, как они будут переданы в производство.

Мы также можем в конечном итоге сделать хак, при котором определенные классы считаются одним из классов-предков или классов наследования при сравнении.

[Редактировать 2016-07-08] В итоге я добавил возможность иметь списки с объявленным типом, так что угадывание типов не требуется.

Код замены для кодового блока сверху:

//If this is adding/setting to a list, make sure the object has the proper class type
if(
    instance instanceof java.util.List && //Object is a list
    java.util.Arrays.asList("add", "set").contains(method.getName()) //Adding/setting to list
) {
    //If this is a TypedList, use its stored class for the typecheck
    java.util.List TheInstanceList=(java.util.List)instance;
    Class ClassInstance=null;
    if(instance instanceof lua.TypedList)
        ClassInstance=((lua.TypedList)instance).GetListClass();
    //Otherwise, check for a 0th object to typecheck against
    else if(TheInstanceList.size()>0) //List already has at least 1 item
        ClassInstance=TheInstanceList.get(0).getClass(); //Class of the 0th item

    //Check if new item does not match found class type
    if(
        ClassInstance!=null && //Only check if there is a class to check against
        !ClassInstance.isInstance(a[a.length>1 ? 1 : 0]) //Check the last parameter's class
    )
        return LuaValue.error(String.format(
                "list coercion error: %s is not instanceof %s",
                a[a.length>1 ? 1 : 0].getClass().getName(),
                ClassInstance.getName()
        ));
}

И код для TypedList:

/**
 * This is a special List class used with LUA which tells LUA what the types of objects in its list must be instances of.
 * Otherwise, when updating a list in LUA, whatever is the first object in a list is what all other objects must be an instance of.
 */
public interface TypedList {
    Class GetListClass();
}

Голый ArrayList как TypeList:

import java.util.ArrayList;

public class TypedArrayList<E> extends ArrayList<E> implements TypedList {
    private Class ListType;
    public TypedArrayList(Class c) {
        DefaultConstructor(c);
    };
    public TypedArrayList(Class c, java.util.Collection<? extends E> collection) {
        super(collection);
        DefaultConstructor(c);
    }
    private void DefaultConstructor(Class c) { ListType=c; }
    @Override public Class GetListClass() {
        return ListType;
    }
}
Другие вопросы по тегам