12

Context (Edit)

Some clarification was on demand, so I'll try to sum up what influences the question.

  • The goal of the project is to provide a certain functionality to programmers, most probably in the form of a library (a JAR with class files, I guess).

  • To use said functionality, programmers would have to conform to the constraints that must (should) be satisfied. Otherwise it won't function as expected (just like the locks from java.util.concurrent, that must be acquired / freed at the appropriate time and place).

  • This code won't be the entry point to the applications using it (ie, has no main).

  • There's a limited (and small) amount of operations exposed in the API.

Examples:

  1. Think of a small game, where almost everything is implemented and managed by the already implemented classes. The only thing left for the programmer to do, is to write a method, or a couple of them, that describe what the character will do (walk, change direction, stop, inspect object). I would want to make sure that their methods (possibly marked with an annotation?) just walk, or changeDirection, or calculate diff = desiredValue - x, and not, say, write to some file, or open a socket connection.

  2. Think of a transaction manager. The manager would be provided by this library, as well as some constant attributes of transactions (their isolation level, time-outs, ...). Now, the programmers would like to have transactions and use this manager. I would want to make sure that they only read, write, commit, or rollback on some resources, known to the manager. I wouldn't want them to launchRocket in the middle of the transaction, if the manager does not control any rockets to launch.

The Problem

I want to impose some invariants / restrictions / constraints on the body of a method (or group of methods), to be later implemented by some other programmer, in some other package/location. Say, I give them something along the lines of:

public abstract class ToBeExtended {
    // some private stuff they should not modify
    // ...
    public abstract SomeReturnType safeMethod();
}

It is important (probably imperative), for the purposes of this project, that the method body satisfies some invariants. Or rather, it is imperative that the set of commands this method's implementation uses is limited. Examples of these constraints:

  • This method must not perform any I/O.
  • This method must not instantiate any unknown (potentially dangerous) objects.
  • ...

Put another way:

  • This method can call the methods of a known (specific) class.
  • This method can execute some basic instructions (maths, assign local variables, ifs, loops...).

I've been looking through Annotations, and there seems to be nothing close to this.
My options so far:

  1. Define some annotation, @SafeAnnotation, and apply it to the method, defining a contract with the implementer, that he will follow the rules imposed, or else the system will malfunction.

  2. Define an Enum with the allowed operations. Instead of exposing the allowed methods, only a method is exposed, that accepts a list of these enum objects (or something similar to a Control Flow Graph?) and executes it, giving me the control of what can be done.

Example:

public enum AllowedOperations { OP1, OP2 }

public class TheOneKnown {
    public void executeMyStuff (List<AllowedOperations> ops) {
        // ...
    }
}

My Question

Is there any feature in the language, such as annotations, reflection, or otherwise, allowing me to inspect (either at compile time or runtime) if a method is valid (ie, satisfies my constraints)?
Or rather, is there any way to enforce it to call only a limited set of other methods?

If not (and I think not), would this second approach be a suitable alternative?
Suitable, as in intuitive, well designed and/or good practice.

Update (Progress)

Having had a look at some related questions, I'm also considering (as a third option, maybe) following the steps given in the accepted answer of this question. Although, this may require some rethinking on the architecture.

The whole idea of using annotations to impose restrictions seems to require implementing my own annotation processor. If this is true, I might as well consider a small domain-specific language, so that the programmer would use these limited operations, later translating the code to Java. This way, I would also have control over what is specified.

4

5 回答 5

6

查看 java 策略文件。我没有使用它们,我不确定它们是否完全适合您的问题,但是通过深入研究文档,它们可能是合适的。这里有几个可能会有所帮助的问题

在 Java 中限制文件访问

什么是用于限制文件写入单个目录的简单 Java 安全策略?

这里有一些关于策略文件的文档。

http://docs.oracle.com/javase/6/docs/technotes/guides/security/PolicyFiles.html

于 2013-04-11T21:36:18.660 回答
4

我认为这个问题的方向是好的。

  • 使用特定的ClassLoaderlo 加载类。请注意,它们是一种有趣的马,通常情况下,类本身是由父类加载器加载的。可能您想要某种UrlClassLoader,并且父类加载器将设置为 Root 类加载器,但这还不够。
  • 用于threads避免无限循环(而Runnable不是像那里那样实现扩展Thread) - 如果您不担心它,这可能是不必要的。
  • 使用 SecurityManager 避免java.io操作

除了上述之外,我推荐2个选项:

为该方法提供一个控制器,该控制器将包含它可以调用的函数

例如:

public void foo(Controller ctrl) {
}

public class Controller {
   public boolean commit();
   public boolean rollback();
}

这可以给用户一个句柄,允许哪些操作。

使用Intent-like 命令模式

在Android中,系统的组件是相当封闭的。他们不能直接相互交流,他们只能触发一个事件,“这发生了”,或者“我想做那个”。

这样,可用命令的集合不受限制。通常,如果这些方法只做小的业务逻辑,那就足够了。

于 2013-04-17T07:49:52.893 回答
4

您可以使用自定义类加载器限制不受信任的代码使用的类:

public class SafeClassLoader extends ClassLoader {

    Set<String> safe = new HashSet<>();

    {
        String[] s = {
            "java.lang.Object",
            "java.lang.String",
            "java.lang.Integer"
        };
        safe.addAll(Arrays.asList(s));
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        if (safe.contains(name)) {
            return super.loadClass(name, resolve);
        } else {
            throw new ClassNotFoundException(name);
        }
    }
}

public class Sandboxer {
    public static void main(String[] args) throws Exception {
        File f = new File("bin/");
        URL[] urls = {f.toURI().toURL()};
        ClassLoader loader = new URLClassLoader(urls, new SafeClassLoader());
        Class<?> good = loader.loadClass("tools.sandbox.Good");
        System.out.println(good.newInstance().toString());
        Class<?> evil = loader.loadClass("tools.sandbox.Evil");
        System.out.println(evil.newInstance().toString());
    }
}

public class Good {
    @Override
    public String toString() {
        return "I am good";
    }
}

public class Evil {
    @Override
    public String toString() {
        new Thread().start();
        return "I am evil.";
    }
}

运行这将导致

I am good
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Thread
    at tools.sandbox.Evil.toString(Evil.java:7)
    at tools.sandbox.Sandboxer.main(Sandboxer.java:18)
Caused by: java.lang.ClassNotFoundException: java.lang.Thread
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
    ... 2 more

当然,这假设您已将列入白名单的课程小心谨慎。它也不能防止拒绝服务的东西,例如

while (true) {}

或者

new long[1000000000];
于 2013-04-11T23:41:42.923 回答
3

另一种选择是使用嵌入式脚本解释器,例如 groovy one ( https://docs.groovy-lang.org/latest/html/documentation/guide-integrating.html ) 并评估第三方方法的内容带有执行前验证的运行时。

优点是您将能够限制仅访问您将为脚本执行绑定的变量。

您还可以编写自己的验证 dsl 并将其应用于将执行脚本的方法,例如使用自定义注释。

于 2013-04-17T06:41:06.430 回答
0

有几个按合同设计的库可用于 Java,但我不能特别推荐一个。 Java Argument Validation似乎是一个轻量级的解决方案,但同样,我没有第一手经验。

于 2013-04-11T21:25:33.307 回答