2013-12-13 8 views
9

私はかなり奇妙なバグに出会っています。次の小さなコードは、かなり単純な数式を使用しています。ProGuardが誤った計算をする可能性があります

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のAndroid 4.1.1ビルド3.16.401.8で確認しましたが、電子メールで判断すると、Android 4+を搭載した携帯電話が多く影響を受けています。それらの一部(Galaxy S3)では、アメリカのオペレーターブランドの携帯電話が影響を受けますが、国際版は影響を受けません。多くの電話は影響を受けません。

以下は、1 < = n < 25および0 < = k < = nのC(n、k)を計算するアクティビティコードです。上記のデバイスでは、最初のセッションで正しい結果が得られますが、その後の起動では、毎回違う位置に不正な結果が表示されます。

  1. はどのようにそれができる:

    私は3つの質問がありますか? ProGuardが何かを間違えたとしても、デバイスとセッションの間で一貫した計算が行われるはずです。

  2. どうすれば避けることができますか?この場合、doublelongに置き換えることはわかりますが、普遍的な方法ではありません。 doubleを使用して削除するか、難読化されていないバージョンをリリースすることは問題になりません。

  3. 影響を受けるAndroidのバージョンは何ですか?私はゲームでそれを固定して、非常に速かったので、私はちょうど多くの選手がそれを見ていることを知っている、と時々私は計算に間違いを見るので、少なくともほとんど、問題外であるアンドロイド4.0

オーバーフローしていました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:間違った計算結果(それは私がそれをしようとするたびに違う、覚えておいてください)

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 
+2

ほとんど聞こえません。そのループで 's'の各値を記録しようとしましたが、計算の仕方が分かりませんでしたか? – zapl

+0

「正しいですか?」どちらか? – DoubleDouble

+0

FWIW、数学では、C(12,4)= 495です。 – rgettman

答えて

3

Androidのチームメンバーは、私のissueへのコメントに可能な解決策を掲載しています。マニフェストファイルのapplication要素にandroid:vmSafeMode="true"を追加すると、すべての計算が正しく実行されます。このオプションは十分に文書化されておらず、正直なところ、どれだけ速度に影響するかはわかりませんが、少なくとも数学は正しいでしょう。私はより良いものが見つかるまで正解とマークします。

2

元の

<?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> 

例コードと処理されたコードは、Java VMとほとんどのDalvik VMで正常に動作するため、有効である必要があります。処理されたコードがいくつかのDalvik VMで偽の結果を生成する場合、そのVM内のJITコンパイラによって問題が発生する可能性があります。 GoogleのAndroidチームがそれに目を通す必要があります。

ProGuardがここで適用する最も明白な最適化は、メソッドのインライン展開です。いくつかの分岐命令とローカル変数は最終的なバイトコードで再配列されますが、この小さなコードの実行フローは基本的に同じです。 ProGuardがどのようにこの問題を回避できるかを判断するのは難しいです。最適化ステップを完全に無効にすることができます。

ProGuardを使わずにコードを手動でインライン展開すると、同じ問題が発生するかどうかを確認することができます(問題はデバイスで発生していないようです)。

(私はProGuardののデベロッパー)

+0

ありがとう、エリック!私はアンドロイドチームに問題を投稿しようとしていました。私が何か明白なものを見逃していて、誰かがそれを指摘してしまった場合に備えて、1日待つことを望んでいました。私は約10年間、ProGuard for mobile developmentを使用しています。あなたは素晴らしい仕事をしています。がんばり続ける! – Dmitry

+0

Eric、私はいくつかのオプションをチェックしました: '-dontoptimize'は助けにはなりません、' -dontobfuscate'は助けますが、商用プロジェクトには邪魔されないコードを望んでいますか?メソッドがインライン展開されているとは思わない。なぜなら、私は 'mapping.txt'で3つのメソッドすべてを見るからです。残念なことに、私はMacOSにしばらく前に交換していましたが、リバースエンジニアリングに使用したツールがありません。アンドロイドの問題63790を投稿しましたが、何か答えを得ることを望みますが、すぐにはそうなるとは思わない。 – Dmitry

+1

難読化ステップでは、メソッドの名前を 'a'と 'b'に変更するだけで、ローカル変数に関するデバッグ情報は削除されますが、実際には 'dx'コンパイラが別のバイトコードを発行します。 '-keepattributes LocalVariableTable'が違いを生むなら、あなたは試みることができます。 Android SDKの 'dexdump'を使って、アプリケーションのバイトコードを見ることができます。 –

1

これはすべてProGuardの最適化が起こったばかりのJITコンパイラのバグです。

AOSP issueは説明したように:

をJITが誤って低い32ビットで同一であった浮動小数点二重定数(の使用を離れて最適化することになるたジェリービーン放出時間の窓があったとまたいくつかの条件が満たされていた)。この欠陥は2012年の11月下旬にGoogleの内部ツリーで、2013年2月にaospで導入されました。

さらに詳しい説明が他のAOSP issueにあります。

関連する問題