【问题标题】:Java Area constructor hangs for certain curves某些曲线的 Java 区域构造函数挂起
【发布时间】:2018-05-12 12:31:40
【问题描述】:

Area(Shape) 构造函数因某些奇怪的Shape 参数而挂起,如下所示。每条注释行都挂起,没有非注释行挂起。我在 Oracle Java 8 和 10 中看到了这种行为。

import java.awt.geom.*;

public static void main(String[] args) {
    // new Area(new CubicCurve2D.Double(0, Double.POSITIVE_INFINITY, 0, 0, 0, 0, 1, 0));
    new Area(new CubicCurve2D.Double(0, Double.POSITIVE_INFINITY, 0, 0, 0, 0, 0, 0));
    new Area(new CubicCurve2D.Double(0, Double.NEGATIVE_INFINITY, 0, 0, 0, 0, 1, 0));
    new Area(new CubicCurve2D.Double(0, Double.MAX_VALUE, 0, 0, 0, 0, 1, 0));
    new Area(new CubicCurve2D.Double(0, Double.MIN_VALUE, 0, 0, 0, 0, 1, 0));
    new Area(new CubicCurve2D.Double(0, Double.NaN, 0, 0, 0, 0, 1, 0));

    // new Area(new CubicCurve2D.Double(1, 0, 0, 0, 0, 0, 0, Double.POSITIVE_INFINITY));
    new Area(new CubicCurve2D.Double(0, 0, 0, 0, 0, 0, 0, Double.POSITIVE_INFINITY));

    // new Area(new QuadCurve2D.Double(0, Double.POSITIVE_INFINITY, 0, 0, 1, 0));
    new Area(new QuadCurve2D.Double(0, Double.POSITIVE_INFINITY, 0, 0, 0, 0));
    new Area(new QuadCurve2D.Double(0, Double.NEGATIVE_INFINITY, 0, 0, 1, 0));
    new Area(new QuadCurve2D.Double(Double.POSITIVE_INFINITY, 0, 0, 0, 0, 1));

    // new Area(new QuadCurve2D.Double(1, 2, 3, 4, 5, Double.POSITIVE_INFINITY));
    new Area(new QuadCurve2D.Double(0, 1, 2, 3, 4, Double.POSITIVE_INFINITY));

    // new Area(new QuadCurve2D.Double(1, 0, 0, 0, 0, Double.POSITIVE_INFINITY));
    new Area(new QuadCurve2D.Double(0, 0, 0, 0, 0, Double.POSITIVE_INFINITY));

    // new Area(new QuadCurve2D.Float(1, 0, 0, 0, 0, Float.POSITIVE_INFINITY));
    new Area(new QuadCurve2D.Float(0, 0, 0, 0, 0, Float.POSITIVE_INFINITY));

    Path2D path = new Path2D.Double();
    path.moveTo(1, 0);
    path.quadTo(0, 0, 0, Double.POSITIVE_INFINITY);
    // new Area(path);

    new Area(new Line2D.Double(0, Double.POSITIVE_INFINITY, 0, 0));
    new Area(new Ellipse2D.Double(0, Double.POSITIVE_INFINITY, 0, 0));
}

这里到底发生了什么?是否还有其他 Shapes 导致 Area 构造函数挂起?

主要问题:忽略 CubicCurve2DQuadCurve2DPath2D 对象与 Double.POSITIVE_INFINITYFloat.POSITIVE_INFINITY 任何坐标(我将处理这些作为边缘情况),我可以安全假设Area 构造函数永远不会挂起?

请注意,我无法将标准调试技术应用于此问题,因为我无法访问 Oracle 的 Area 源代码,所以我只能将其视为黑匣子。我想知道是否还有其他已知的输入会导致这个黑盒挂起。


请不要说这是一个毫无意义的问题,因为没有理智的人会编写此代码...我有一个运行随机生成的 Java 代码的应用程序,这实际上是一个问题。也请不要建议我在单独的进程中运行代码并在超时后将其终止,因为这种方法对我的需要来说太慢了。

【问题讨论】:

  • 对于这么复杂的你为什么不使用构建器模式?,只是建议
  • @emotionlessbananas 这有什么关系?你读过这个问题吗?
  • 您应该绘制曲线的近似值,以深入了解 Area 对象挂起的原因;我怀疑关闭曲线的算法可能在某些点配置方面遇到了困难。
  • @ReblochonMasque 你能更准确一点吗?我无法访问 Oracle 的 Area 构造函数的源代码,所以我能做的就是推测。请注意,在我的示例中,切换xy 坐标可能会导致不同的行为,就像将0 交换为1

标签: java geometry awt infinite-loop freeze


【解决方案1】:

由于您将Integer.POSITIVE_INFINITY 作为参数传递,Java API 最终会在计算中生成NaNs(不是数字),从而导致infinite loop

上面所有注释的方法在线程挂起后都有以下Stacktrace

Thread [main] (Suspended)   
    Order1(Curve).compareTo(Curve, double[]) line: 935  
    Order1.compareTo(Curve, double[]) line: 221 
    Edge.compareTo(Edge, double[]) line: 90 
    AreaOp$NZWindOp(AreaOp).pruneEdges(Vector) line: 278    
    AreaOp$NZWindOp(AreaOp).calculate(Vector, Vector) line: 159 
    Area.pathToCurves(PathIterator) line: 195   
    Area.<init>(Shape) line: 126    
    AreaTest.main(String[]) line: 8 

根据调试器,在935 to line 945 行的Order1(Curve).compareTo(Curve, double[]) line: 935 中有一个无限循环运行,因为我们没有源代码并且这个类没有使用调试信息编译,我们不能完全依赖行号。但是,我们可以在OpenJDK 源代码中仔细查看这些行。

注意:Order1(Curve).compareTo方法来自Order1的超类Curve

下面是生成NaN的代码位置

sun.awt.geom.Curve.compareTo

Oracle JDK decompile code (Enhanced class decompiler)

    //Renamed variables with INFINITY. prefix to signify that their value is INTEGER.POSITIVE_INFINITE
    label101:
    for (arg30 = arg2 + arg24; arg30 <= INFINITY.arg4; arg30 += INFINITY.arg26) {
        if (!this.fairlyClose(this.XforY(arg30), arg0.XforY(arg30))) {
            NaN.arg30 -= INFINITY.arg26;

OpenJDK source code

    while (y <= y1) {
        if (fairlyClose(this.XforY(y), that.XforY(y))) {
            if ((bump *= 2) > maxbump) {
                bump = maxbump;
            }
        } else {
            NaN.y -= INFINITY.bump;

Oracle 行 NaN.arg30 -= INFINITY.arg26; 或 OpenJDK 行 y -= INFINITY.bump; 将在第二次迭代时生成 NaN,因为 INFINITY - INFINITYNaN

让我们快速看一下以下内容

  1. 任何涉及NaN 的逻辑运算总是会产生false
  2. 任何算术运算总是会产生NaN

下面是无限循环

sun.awt.geom.Curve.compareTo

//Renamed variables with INFINITY. and NaN. prefix to signify their value as NaN or INFINITY

Oracle JDK decompile code (Enhanced class decompiler)

    while (true) {
        INFINITY.arg26 /= 2.0D;
        NaN.arg32 = NaN.arg30 + INFINITY.arg26;

        //Loop termination condition is always false, due to NaN in logical operation
        if (NaN.arg32 <= NaN.arg30) {
            break label101;
        }

        if (this.fairlyClose(this.XforY(NaN.arg32), arg0.XforY(NaN.arg32))) {
            NaN.arg30 = NaN.arg32;
        }
    }

OpenJDK source code

    while (true) {
        bump /= 2;
        double NaN.newy = NaN.y + NaN.bump;

        //Loop termination condition is always false, due to NaN in logical operation
        if (NaN.newy <= NaN.y) {
            break;
        }
        if (fairlyClose(this.XforY(newy), that.XforY(newy))) {
            y = newy;
        }
    }

循环终止条件最终会在总是导致错误的条件中引入NaN,因为循环永远不会中断。

OpenJDK 代码参考http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/sun/awt/geom/Curve.java#936

注意:我在openjdk version "1.8.0_151" 上测试过相同的代码,它也挂在那里。

以下是我的处理建议

  1. 超时是最好的策略。您还应该检测资源利用率,例如 RAM、磁盘和网络使用情况。如果您从未经验证的来源运行代码,它肯定应该在单独的进程下运行,不仅应该像在Docker 容器中那样完全在单独的环境中运行。 大多数在线编程竞赛都使用超时/资源利用率测量,例如CodeChef。您可以尝试在https://ideone.com 上运行无限循环,并看到它很快结束,很可能是因为超时。即使是容器也不是万无一失的,未经验证的代码可能会利用容器的漏洞并访问主机。看看这个视频https://youtu.be/rfjmeakbeH8?t=2035,我已经在视频中标出了容器隔离策略的当前时间。

  2. 没有其他策略 :) 它是来自计算机科学的 halting problem,即 NP Hard,这意味着可能还没有多项式时间解决这个问题。

【讨论】:

  • 感谢您的信息。您对我的主要问题有答案吗,即:“除了CubicCurve2DQuadCurve2DPath2D 具有无限坐标的对象之外,是否还有其他输入会导致Area 挂起”?这并不完全是停止问题,因为我们没有为任意程序解决这个问题。我们有一个固定的程序(即 Oracle 的 Area 构造函数),并且使用反编译的代码(我没有生成的工具),可以证明或反驳导致挂起的其他输入的存在.
  • 在你提出这个问题之前,我什至不知道这个案子。我很好奇并开始调试它。对于反编译,我使用了 Spring Tool Suite 3.8.2.RELEASE 和插件 Enhanced Class Decompiler 3.0.0 marketplace.eclipse.org/content/enhanced-class-decompiler。这两个都是免费的,因此您可以将它们用于测试。 OpenJDK 的源代码也是开放的。关于停机问题,是的,它需要任意程序和任意输入。我不确定给定的程序和任意输入,可能是undecidability problem,不知道其他任何原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-14
  • 1970-01-01
相关资源
最近更新 更多