ProGuard可能导致错误的计算

时间:2013-12-13 18:34:52

标签: java android proguard android-sdk-tools

我遇到了一个非常奇怪的错误。以下一小段代码使用了相当简单的数学。

protected double C_n_k(int n, int k)
{
  if(k<0 || k>n)
    return 0;
  double s=1;
  for(int i=1;i<=k;i++)
    s=s*(n+1-i)/i;
  return s;
}

修改使用ProGuard可能会在某些设备上出错。我已经在HTC One S Android 4.1.1 build 3.16.401.8上确认了,但是通过我收到的电子邮件判断,很多Android 4+的手机都受到了影响。对于其中一些(Galaxy S3),美国运营商品牌的手机受到影响,而国际版本则没有。很多手机都没有受到影响。

下面是计算1 <= n <25且0 <= k <= n的C(n,k)的活动代码。在上面提到的设备上,第一个会话给出了正确的结果,但随后的启动显示不正确的结果,每次都在不同的位置。

我有3个问题:

  1. 怎么样?即使ProGuard出错了,计算也应该在设备和会话之间保持一致。

  2. 我们如何避免它?我知道double替换long在这种情况下是好的,但它不是一种通用的方法。使用double删除或发布未混淆的版本是不可能的。

  3. 哪些Android版本会受到影响?我很快就把它固定在游戏中,所以我只知道很多玩家已经看过它了,至少最多有Android 4.0

  4. 溢出是不可能的,因为有时我会在计算C(3,3)=3/1*2/2*1/3时看到错误。通常不正确的数字从C(10,...)的某个地方开始,看起来就像电话已“忘记”进行某些划分。

    我的SDK工具是22.3(最新的),我在Eclipse和IntelliJ IDEA创建的构建中看到过它。

    活动代码:

    package com.karmangames.mathtest;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.text.method.ScrollingMovementMethod;
    import android.widget.TextView;
    
    public class MathTestActivity extends Activity
    {
      /**
       * Called when the activity is first created.
       */
      @Override
      public void onCreate(Bundle savedInstanceState)
      {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        String s="";
        for(int n=0;n<=25;n++)
          for(int k=0;k<=n;k++)
          {
            double v=C_n_k_double(n,k);
            s+="C("+n+","+k+")="+v+(v==C_n_k_long(n,k) ? "" : "   Correct is "+C_n_k_long(n,k))+"\n";
            if(k==n)
              s+="\n";
          }
        System.out.println(s);
        ((TextView)findViewById(R.id.text)).setText(s);
        ((TextView)findViewById(R.id.text)).setMovementMethod(new ScrollingMovementMethod());
      }
    
      protected double C_n_k_double(int n, int k)
      {
        if(k<0 || k>n)
          return 0;
        //C_n^k
        double s=1;
        for(int i=1;i<=k;i++)
          s=s*(n+1-i)/i;
        return s;
      }
    
      protected double C_n_k_long(int n, int k)
      {
        if(k<0 || k>n)
          return 0;
        //C_n^k
        long s=1;
        for(int i=1;i<=k;i++)
          s=s*(n+1-i)/i;
        return (double)s;
      }
    
    }
    

    main.xml中:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:orientation="vertical"
                  android:layout_width="fill_parent"
                  android:layout_height="fill_parent"
      >
    
      <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text"
        android:text="Hello World!"
        />
    </LinearLayout>
    

    错误的计算结果示例(请记住,每次尝试都会有所不同)

    C(0,0)=1.0
    
    C(1,0)=1.0
    C(1,1)=1.0
    
    C(2,0)=1.0
    C(2,1)=2.0
    C(2,2)=1.0
    
    C(3,0)=1.0
    C(3,1)=3.0
    C(3,2)=3.0
    C(3,3)=1.0
    
    C(4,0)=1.0
    C(4,1)=4.0
    C(4,2)=6.0
    C(4,3)=4.0
    C(4,4)=1.0
    
    C(5,0)=1.0
    C(5,1)=5.0
    C(5,2)=10.0
    C(5,3)=10.0
    C(5,4)=30.0   Correct is 5.0
    C(5,5)=1.0
    
    C(6,0)=1.0
    C(6,1)=6.0
    C(6,2)=15.0
    C(6,3)=40.0   Correct is 20.0
    C(6,4)=90.0   Correct is 15.0
    C(6,5)=144.0   Correct is 6.0
    C(6,6)=120.0   Correct is 1.0
    
    C(7,0)=1.0
    C(7,1)=7.0
    C(7,2)=21.0
    C(7,3)=35.0
    C(7,4)=105.0   Correct is 35.0
    C(7,5)=504.0   Correct is 21.0
    C(7,6)=840.0   Correct is 7.0
    C(7,7)=720.0   Correct is 1.0
    
    C(8,0)=1.0
    C(8,1)=8.0
    C(8,2)=28.0
    C(8,3)=112.0   Correct is 56.0
    C(8,4)=70.0
    C(8,5)=1344.0   Correct is 56.0
    C(8,6)=3360.0   Correct is 28.0
    C(8,7)=5760.0   Correct is 8.0
    C(8,8)=5040.0   Correct is 1.0
    
    C(9,0)=1.0
    C(9,1)=9.0
    C(9,2)=36.0
    C(9,3)=168.0   Correct is 84.0
    C(9,4)=756.0   Correct is 126.0
    C(9,5)=3024.0   Correct is 126.0
    C(9,6)=10080.0   Correct is 84.0
    C(9,7)=25920.0   Correct is 36.0
    C(9,8)=45360.0   Correct is 9.0
    C(9,9)=40320.0   Correct is 1.0
    
    C(10,0)=1.0
    C(10,1)=10.0
    C(10,2)=45.0
    C(10,3)=120.0
    C(10,4)=210.0
    C(10,5)=252.0
    C(10,6)=25200.0   Correct is 210.0
    C(10,7)=120.0
    C(10,8)=315.0   Correct is 45.0
    C(10,9)=16800.0   Correct is 10.0
    C(10,10)=1.0
    

3 个答案:

答案 0 :(得分:3)

Android小组成员已在评论issue中发布了可能的解决方案。如果我将android:vmSafeMode="true"添加到清单文件的application元素,则所有计算都会正确执行。这个选项没有很好的记录,老实说,我不知道它会对速度有多大影响,但至少数学是正确的。我会将其标记为正确的答案,直到找到更好的答案。

答案 1 :(得分:2)

原始代码和处理过的代码在Java VM和大多数Dalvik VM上运行良好,因此它必须有效。如果处理过的代码在几个Dalvik VM上产生虚假结果,那么问题可能是由这些VM中的JIT编译器引起的。然后谷歌的Android团队应该调查它。

ProGuard在此处应用的最明显的优化是内联方法。一些分支指令和局部变量在最终字节码中重新排序,但这一小段代码的执行流程基本相同。很难确定ProGuard如何避免这个问题。您可以完全禁用优化步骤。

如果没有ProGuard,您可以检查手动内联代码是否会导致相同的问题(我的设备上似乎没有出现此问题)。

(我是ProGuard的开发者)

答案 2 :(得分:1)

这一切都证明是ProGuard优化恰好触发的JIT编译器错误。

正如AOSP issue所解释的那样:

  

在Jelly Bean发布时间中有一个窗口,其中JIT会错误地优化浮点双常数的使用,这些常量在低32位中是相同的(并且还满足一些其他条件)。该缺陷于2012年11月下旬在内部Google树中引入,并于2013年2月在aosp中引入。固定于2013年4月。

此另一个AOSP issue中有更详细的解释。