[Why Programs Fail] Deducing Errors, 오류 연역

Preview:

Citation preview

오류 연역Deducing Errors

http://ohyecloudy.com

http://cafe.naver.com/architect1 2009-08-08

연역(Deduction)

어떤 명제로부터 추론 규칙에 따라 결론을 이끌

어 냄. 또는 그런 과정. 일반적인 사실이나 원리를 전제로 하여 개별적인 사실이나 보다 특수한 다른 원리를 이끌어 내는 추

리를 이른다. 경험을 필요로 하지 않는 순수한 사유에 의하여 이루어지며 그 전형은 삼단 논법이다.

-네이버 국어 사전

프로그램 분석에서 연역은

추상적인 프로그램 코드에서 구체적인 프로그램 실행으로의 추론 기법

정적 분석 실제로 프로그램을 수행할 필요 없다.

코드만으로 연역해

낼 수 있는 일반적인

오류들은 무엇인가?

이 장의 주제

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

뭔가 불가능한 일이 일어났다면, 그리고 그에 대한 확실한 정보는 그것이 실제로 일어났다는 것뿐이라면, 결과로부터 거슬러 생각해서thinking backward 이유들을 찾아내야 한다.

- Kernighan & Pike(1999)

10 INPUT X

20 Y = 0

30 X = Y

40 PRINT “X = “, X

항상 X = 0 출력

어디에서 비롯되었을까?

거슬러 생각하기 실패 원인에 영향을 주는 문장을 찾는다.

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

$ gcc –o fibonacci fibonacci.c

$ ./fibonacci

fib(9) = 55

fib(8) = 34

fib(7) = 21

fib(6) = 13

fib(5) = 8

fib(4) = 5

fib(3) = 3

fib(2) = 2

fib(1) = 1291239

어디에서 비롯되었을까?

#include <stdio.h>

int fib( int n )

{

int f, f0 = 1, f1 = 1;

while( n > 1 ) {

n = n - 1;

f = f0 + f1;

f0 = f1;

f1 = f;

}

return f;

}

int main()

{

int n = 9;

while( n > 0 ) {

printf( "fib(%d) = %d\n", n, fib(n) );

n = n - 1;

}

return 0;

}

값에 영향을 미칠 수 있는 코드 영역을 식별 문장들이 수행되는 순서를 조사

짂입 : fib(n)

int f

int f0 = 1

int f1 = 1

while( n > 1 )

n = n - 1

f = f0 + f1

f0 = f1

f1 = f

return f

종료

문장 == 노드 갂선 == 제어흐름 A B A는 B에 앞선다. B는 A를 따른다.

제어 흐름의 난점들

점프와 goto goto 50

갂접 점프 goto x, 함수 포인터

동적 분배 shape.draw()

rectangle::draw()

circle::draw()

예외 throw new Exception(“제기랄”)

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

문장이 주는 영향

쓰기 : 프로그램 상태를 변경

v1 = 1

제어 : 프로그램 카운터 변경

while(n>1), if(n>1)

문장이 받는 영향

읽기 : 프로그램 상태를 읽는다.

v2 = v1 + 1

변수 v1이 가리키는 주소에서 값을 읽는다.

수행 : 프로그램 카운터가 현재 문장 을 가리키는가.

문장 읽기 쓰기 제어

0 fib( n ) n 1-9

1 int f f

2 f0 = 1 f0

3 f1 = 1 f1

4 while( n > 1 ) n 5-8

5 n = n – 1 n n

6 f = f0 + f1 f0, f1 f

7 f0 = f1 f1 f0

8 f1 = f f f1

9 return f f <반홖값>

종료

짂입 : fib(n)

int f

int f0 = 1

int f1 = 1

while( n > 1 )

n = n - 1

f = f0 + f1

f0 = f1

f1 = f

return f

제어 의존성 A는 B의 수행을 제어한다.

자료 의존성 A의 자료가 B에서 쓰인다.

짂입 : fib(n)

int f

int f0 = 1

int f1 = 1

while( n > 1 )

n = n - 1

f = f0 + f1

f0 = f1

f1 = f

return f

종료

이 값은 어디로 가는가?

n의 자료 의존성 n의 자료 의존성이 있는

while 문장의 제어 의존성

결국 n의 값은 f, f0, f1의 값을 결정

궁극적으로 f 값도 결정

이 값은 어디에서 왔는가? 짂입 : fib(n)

int f

int f0 = 1

int f1 = 1

while( n > 1 )

n = n - 1

f = f0 + f1

f0 = f1

f1 = f

return f

종료

f의 자료 의존성

결국 f(1)의 값은 초기화 되지 않은

임의의 값이 사용됐다.

의존성은 코드를 따라 항해하는데

중요한 지침

프로그래머들은 코드를 읽으면서

암묵적으로 의존성을 판단.

각 문장의 효과를 추정하는 것은

프로그램 이해의 일부.

의존성 활용

CodeSurfer의 의존성 추적

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

슬라이스slice

프로그램의 부분집합

슬라이싱slicing

슬라이스에 관한 연산

전방 슬라이스forward slice

A에 의해 변경된 변수를 읽거나

수행 여부가 A에 의해 결정되는

모든 문장

후방 슬라이스backward slice

B에 영향을 주는 모든 문장

BABASF

)(

BAABS B )(

A : 슬라이스 기죾

B : 슬라이스 기죾

0.짂입 : fib(n)

1.int f

2.int f0 = 1

3.int f1 = 1

4.while(n > 1)

5.n = n - 1

6.f = f0 + f1

7.f0 = f1

8.f1 = f

9.return f

종료

9,8,7,6,2)2( FS

9,8,7,6,5,4,3,2,1,0)9( BS0.짂입 : fib(n)

1.int f

2.int f0 = 1

3.int f1 = 1

4.while(n > 1)

5.n = n - 1

6.f = f0 + f1

7.f0 = f1

8.f1 = f

9.return f

종료

A전방 슬라이스 기죾가 B후방 슬라이스 기죾에

어떻게 영향을 주는가?

)()( BSAS BF 슬라이싱slicing

절단chop

중추backbone

입방체dice

0.짂입 : fib(n)

1.int f

2.int f0 = 1

3.int f1 = 1

4.while(n > 1)

5.n = n - 1

6.f = f0 + f1

7.f0 = f1

8.f1 = f

9.return f

종료

}8,6{)7()3( BF SS

f1의 초기값이 f0에 영향을 미칠 수 있는 모든 경로

슬라이싱slicing

절단chop

중추backbone

입방체dice 여러 값들의 계산에 기여하는

프로그램 부분들을 찾을 때

1. int main() { 2. int a, b, sum, mul; 3. sum = 0; 4. mul = 1; 5. a = read(); 6. b = read(); 7. while (a <= b) { 8. sum = sum + a; 9. mul = mul * a; 10. a = a + 1; 11. } 12. write(sum); 13. write(mul); 14.}

1. int main() { 2. 3. 4. 5. a = read(); 6. b = read(); 7. while (a <= b) { 8. 9. 10. a = a + 1; 11. } 12. 13. 14.}

)()( or or BSAS BFBF

)13()12( BB SS

슬라이싱slicing

절단chop

중추backbone

입방체dice 계산된 값들 대부분이 정확하지맊

일부만 그렇지 않을 때

)(\)( or or BSAS BFBF

1. int main() { 2. 3. 4. mul = 1; 5. 6. 7. 8. 9. mul = mul * a; 10. 11. 12. 13. write(mul); 14.}

)12(\)13( BB SS

정확한 변수의 후방 슬라이스 – 감염된 변수의 후방 슬라이스 = 감염된 값에 기여하는 문장들

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

int go; switch( color ) { case RED: case AMBER: go = 0; break; case GREEN: go = 1; break; } if( go ) { … }

color가 RED or AMBER or GREEN 이 아닐때는?

VS : Warning Level 4에서 default가 없다고 경고

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

int main(int argc, wchar_t* argv[]) { ... // argc, argv 사용되지 않음 return 0; }

VS : Warning Level 4에서 참조되지 않는다고 경고

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

if( w >= 0 ) printf(“w is non-negative\n”); else if( w > 0 ) printf(“w is positive\n”);

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

int* readbuf( int size ) { int* p = malloc(size*sizeof(int)); for( int i = 0; i < size; ++i ) { p[i] = readint(); if( p[i] == 0 ) return 0; } return p; }

스마트 포인터, RAII가 널 구원해죿지니…

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

void readfile() { int fp = open(file); int size = readint(file); if (size <= 0) return; ... close(fp); }

열었으면 닫아야지.

RAII가 널 구원해죿지니…

초기화되지 않은 변수 읽기

사용되지 않는 값

도달할 수 없는 코드

메모리 누수

인터페이스 오용

널 포인터

int *readbuf(int size) { int *p = malloc(size * sizeof(int)); for (int i = 0; i < size; i++) { p[i] = readint(); if (p[i] == 0) return 0; // end-of-file } return p; }

null일수도 있다.

정적 코드 분석 도구

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

x가 초기화되지 않고 사용되는가?

홀수 완전수가 존재해야 한다.

존재하는지 아는 사람은 아직 없음

따라서, 보수적 귺사에 의존해야 한다.

int x; for(i=j=k=1;--j||k;k=j?i%j?k:k-j:(j=i+=2)); write(x);

보수적 근사conservative approximation

배열 a[]의 임의의 변경이 그 배열 a[]의 임의의 읽기에 영향이 미칠 수 있다고 말하는것.

필요 이상으로 많은 자료 의존성을 맊들어내겠지맊 그래도 실제 자료 의존성들이 잘못 갂과되는 일은 막아죾다.

보수적 근사가 부정확한 원인들

갂접 접귺

변수가 실행시점에 결정된다면?

포인터

포인터가 가리키는 주소를 정확히 알 수 있는가?

함수

호출 지점에 직접 삽입하면 정확한 의존성을 알 수 있다.

함수가 아주 맋다면 비현실적

객체지향

병렧성

연역 자체의 위험

자체가 추상화에 의한 위험을 앆고 있다. 코드 불일치의 위험 정밀한 구성 관리, 버전 제어를 통한 극복

과도한 추상화의 위험 어쩔수 없지 뭐

부정확함의 위험 검증과 관찰을 혼합

값 귺원의 격리

제어 흐름 이해하기

의존성 추적

프로그램 슬라이싱

코드 악취의 연역

정적 분석의 한계

정리

값 귺원들을 격리하려면, 문제의 문장으로부터 의존성들을 거꾸로 추적한다.

의존성들을 통해서 코드 악취를 발견할 수 있다. 특히, 초기화되지 않은 변수 사용, 사용되지 않는 값들, 도달할 수 없는 코드 같은 일반적인 오류들을 찾을 수 있다.

디버깅을 시작하기 전에 자동적인 검출 도구(컴파일러 등)가 보고한 코드 악취를 제거할 것

프로그램을 슬라이싱할 때에는, 문장 S로부터 의존성을 따라가면서 다음과 같은 문장들을 모두 찾는다. – S에 영향을 받을 수 있는 문장들(전방 슬라이스)

– S에 영향을 죿 수 있는 문장들(후방 슬라이스)

연역맊을 사용한다면 코드 불일치, 관렦 세부사항의 과도한 추상화, 부정확 같은 위험이 생긴다.

어떤 종류의 연역도 멈춤 문제에 제한을 받으며, 그러면 보수적 근사에 의지해야 한다.

기억에 남는 디버깅

부록

디버그 모드에서는 제대로 작동.

릴리즈 모드에서맊 작업의 오차가 생긴다.

차라리 프로그램이 죽으면 좋으렦맊.

추적 결과 오류 문장을 찾음.

float f;

int a = static_cast<int>(f);

왜! float int 캐스팅인데

소수 부분fractional part 을

버리는 게 아니고

반올림을 하지???

컴파일러 옵션에 /QIfist 가 있었다.

_ftol()를 사용해서 변홖하는게 아니라

FPU(Floating-Point Unit)의

기본 rounding 모델인 반올림을 수행

Recommended