2013-12-13 2 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 S에서 안드로이드 4.1.1 빌드 3.16.401.8 빌드를 확인했지만, 전자 메일로 판단하면 안드로이드 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 (최신)이며 이클립스와 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 
+2

소리가 거의 들리지 않습니다. 그 루프에서's'의 각 값을 로깅하여 계산 방법이 어떻게 다른지 알아 냈습니까? – zapl

+0

"올바른가?" 만약에? – DoubleDouble

+0

수학에서 FWIW는 C (12, 4) = 495입니다. – rgettman

답변

3

안드로이드 팀 구성원은 내 issue에 주석에서 가능한 해결책을 올렸습니다. 내가 android:vmSafeMode="true"application 매니페스트 파일의 요소에 추가하면 모든 계산이 올바르게 수행됩니다. 이 옵션은 잘 설명되어 있지 않으며 정직하게도 속도에 어느 정도 영향을 미칠지 모르지만 최소한 수학은 정확할 것입니다. 더 나은 것을 찾을 때까지 나는 그것을 정답으로 표시 할 것입니다.

2

원래 코드 및 처리 된 코드는 Java VM 및 대부분의 Dalvik VM에서 정상적으로 작동하므로 유효해야합니다. 처리 된 코드가 몇 가지 Dalvik VM에서 가짜 결과를 생성하는 경우 문제는 해당 VM의 JIT 컴파일러 때문입니다. 그런 다음 Google의 Android 팀이 조사해야합니다.

여기에서 ProGuard가 적용하는 가장 확실한 최적화는 방법을 인라이닝하는 것입니다. 몇 가지 분기 명령어와 로컬 변수가 최종 바이트 코드에서 재정렬되지만,이 작은 코드의 실행 흐름은 근본적으로 동일합니다. ProGuard가 어떻게 문제를 피할 수 있는지 판단하기가 어렵습니다. 최적화 단계를 완전히 비활성화 할 수 있습니다.

ProGuard없이 코드를 수동으로 인라이닝하면 같은 문제가 발생하는지 확인할 수 있습니다 (문제는 내 장치에서 발생하지 않는 것 같습니다).

(나는 ProGuard에서의 개발자입니까?)

+0

에릭 감사합니다! 나는 안드로이드 팀에 문제를 게시하려고했는데, 나는 분명히 뭔가를 놓친 경우에 대비하여 하루를 기다리고 싶었고, 누군가는 그것을 지적했다. 저는 약 10 년 동안 모바일 개발을 위해 ProGuard를 사용하고 있으며 훌륭한 작업을하고 있습니다. 계속 올려! – Dmitry

+0

에릭, 몇 가지 옵션을 살펴 보았습니다 :'-dontoptimize'는 도움이되지 않습니다,'-dontobfuscate'는 도움이됩니다 만, 상업 프로젝트에서 난해한 코드를 원하십니까? 메소드가 인라인되었다고 생각하지 않는다. 왜냐하면'mapping.txt'에서 3 가지 메소드를 모두 볼 수 있기 때문이다. 불행히도 나는 MacOS에 몇 시간 전 스왑했고, 리버스 엔지니어링을 위해 사용했던 도구가 그리워. 안드로이드에 63790 호 문제를 게시했으며 답변을 얻을 수 있기를 희망하지만 곧 나올 것으로 생각하지 않습니다. – Dmitry

+1

난독 화 단계에서는 메서드를 'a'및 'b'로만 이름을 바꾸고 로컬 변수에 대한 디버그 정보를 제거하지만 실제로 'dx' 컴파일러에서 다른 바이트 코드를 내 보냅니다. '-keepattributes LocalVariableTable'이 차이를 만들면 시도 할 수 있습니다. 안드로이드 SDK에서'dexdump'를 사용하여 응용 프로그램의 바이트 코드를 볼 수 있습니다. –

1

이 모든 것이 ProGuard 최적화가 방아쇠를 당긴 JIT 컴파일러 버그로 밝혀졌습니다. AOSP issue

는 설명 : JIT를 실수로 낮은 32 비트에서 동일했다 지점을 두 번 정수를 부동의 사용을 최적화 할 것이있는 젤리 해제 시간에 창문이 있었다

(그리고 또한 몇 가지 다른 조건이 충족 됨). 결함은 내부 Google 트리에서 2012 년 11 월 말에, 그리고 외부에서 2013 년 2 월에 도입되었습니다.

자세한 내용은 AOSP issue입니다.

관련 문제