四畳半テクノポリス

コロナのストレスで気が狂い、D進した院生

久しぶりにC言語でQueueを書いた

力試しでヘッダを書いてChatGPTとClaudeに破壊を試みてもらいました。

人生でQueueを書く機会って結構あると思うんですが、品質に関してはあんまり考えてなかったのでAIに聞けば脆弱性が見つかるんじゃないかと色々試してみました。試してみましたが見つかりませんでした。いかがでいしたか?

くコ:彡いか

Queueの本体

ヘッダーファイル:queue..h

#ifndef QUEUE_H
#define QUEUE_H

#include <stdint.h>

#define DTYPE uint8_t

/* キュー API */
DTYPE  dequeue(void);
void   enqueue(DTYPE x);
uint32_t is_empty(void);
uint32_t is_full(void);
void   initialize_queue(void);

#endif /* QUEUE_H */

メインファイル:queue.c

#include"queue.h"
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define LEN 256

static DTYPE ring_buffer[256];
static uint32_t head = 0;
static uint32_t tail = 0;

DTYPE dequeue(void){
    if(is_empty()){
        return NULL;
    }

    int tail_address = tail % LEN;

    if(tail > LEN && head > LEN){
        tail = tail - LEN;
        head = head - LEN;
    }
    tail = tail + 1;
    return ring_buffer[tail_address];
}

void initialize_queue(){
    head = 0;
    tail = 0;
}

void enqueue(DTYPE x){
    if(is_full()){
        return;
    }
    int head_address = head % LEN;
    if(tail > LEN && head > LEN){
        tail = tail - LEN;
        head = head - LEN;
    }
    head = head + 1;
    ring_buffer[head_address] = x;
}

uint32_t is_empty(){
    if(head - tail <= 0){
        return 1;
    }else{
        return 0;
    }
}

uint32_t is_full(){
    if(head - tail >= 255){
        return 1;
    }else{
        return 0;
    }
}

ChatGPT

// test_queue.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include "queue.h"

#define LEN 256
#define PASS "\033[32mPASS\033[0m"
#define FAIL "\033[31mFAIL\033[0m"

static void check_print(const char *name, int ok){
    printf("%-35s : %s\n", name, ok ? PASS : FAIL);
    if(!ok){
        fflush(stdout);
    }
}

/* 単純な理想キュー(検査用) */
typedef struct {
    int buf[LEN];
    int head, tail; // head index of next push, tail index of next pop
    int count;
} ideal_q;

static void ideal_init(ideal_q *q){
    q->head = q->tail = 0;
    q->count = 0;
    for(int i=0;i<LEN;i++) q->buf[i] = -1;
}
static int ideal_empty(ideal_q *q){ return q->count == 0; }
static int ideal_full(ideal_q *q){ return q->count >= (LEN-1); }
static void ideal_enqueue(ideal_q *q, int v){
    if(ideal_full(q)) return;
    q->buf[q->head] = v & 0xFF;
    q->head = (q->head + 1) % LEN;
    q->count++;
}
static int ideal_dequeue(ideal_q *q, int *out){
    if(ideal_empty(q)){ *out = -1; return 0; }
    *out = q->buf[q->tail];
    q->tail = (q->tail + 1) % LEN;
    q->count--;
    return 1;
}

/* テスト1: 空キューからのdequeue の挙動 */
static int test_empty_dequeue(){
    initialize_queue();
    uint8_t v = dequeue(); // 実装は空なら NULL を返す(=0)
    int is_emp = is_empty();
    // DTYPE は uint8_t なので NULL==0。empty のとき 0 を返すのは ambiguous。
    // 成功条件: 実装が空であると報告すること(is_empty==1)かつ
    // dequeue() が 0 を返す(現在の実装の仕様を確認するため)。
    int ok = (is_emp == 1);
    check_print("test_empty_dequeue: is_empty()==1?", ok);
    if(!ok) return 0;
    printf("  dequeue() returned: %u (note: 0 == NULL for uint8_t)\n", (unsigned)v);
    return 1;
}

/* テスト2: 基本FIFO */
static int test_basic_fifo(){
    initialize_queue();
    int N = 100;
    for(int i=0;i<N;i++) enqueue((uint8_t)i);
    int ok = 1;
    for(int i=0;i<N;i++){
        uint8_t v = dequeue();
        if(v != (uint8_t)i){
            printf("  FIFO mismatch at i=%d: got=%u expected=%d\n", i, (unsigned)v, i);
            ok = 0;
            break;
        }
    }
    check_print("test_basic_fifo", ok);
    return ok;
}

/* テスト3: 満杯の振る舞い */
static int test_fill_and_overflow(){
    initialize_queue();
    // 実装は最大 LEN-1 (=255) 要素を入れる設計っぽい
    for(int i=0;i<LEN;i++){
        enqueue((uint8_t)i);
    }
    // ここで is_full() が 1 であることを期待
    int full = is_full();
    check_print("test_fill_and_overflow: is_full()", full==1);
    // 255 個取り出して順序確認
    int ok = 1;
    for(int i=0;i<LEN-1;i++){
        uint8_t v = dequeue();
        if(v != (uint8_t)i){
            printf("  fill/overflow mismatch at i=%d: got=%u expected=%d\n", i, (unsigned)v, i);
            ok = 0;
            break;
        }
    }
    check_print("test_fill_and_overflow: drain order", ok);
    return full && ok;
}

/* テスト4: ラップアラウンド/長期ストレス(理想キューと常時比較) */
static int test_stress_random(unsigned long ops, unsigned seed){
    initialize_queue();
    ideal_q iq;
    ideal_init(&iq);
    unsigned next_val = 1;
    srand(seed);
    for(unsigned long i=0;i<ops;i++){
        int do_enqueue;
        // enqueue をやや優先して長時間満杯状態を作りやすくする
        if(ideal_full(&iq)) do_enqueue = 0;
        else if(ideal_empty(&iq)) do_enqueue = 1;
        else do_enqueue = (rand() & 1);
        if(do_enqueue){
            uint8_t v = (uint8_t)(next_val & 0xFF);
            enqueue(v);
            ideal_enqueue(&iq, v);
            next_val++;
        }else{
            int ideal_v;
            int ideal_has = ideal_dequeue(&iq, &ideal_v);
            uint8_t got = dequeue();
            if(!ideal_has){
                // ideal empty -> we expect implementation to be empty (dequeue returns 0 and is_empty==1)
                if(is_empty() != 1){
                    printf("  Stress mismatch: ideal empty but is_empty()==0 at op %lu\n", i);
                    return 0;
                }
                // dequeue() returns 0 both for empty and data==0; we can't fully distinguish here,
                // but if ideal had element and value==0 we'd already have set ideal_has==1.
            }else{
                if(got != (uint8_t)ideal_v){
                    printf("  Stress mismatch at op %lu: got=%u expected=%d\n", i, (unsigned)got, ideal_v);
                    return 0;
                }
            }
        }
    }
    check_print("test_stress_random", 1);
    return 1;
}

/* テスト5: 空時に 0 の値を入れたときの区別不可能性を示す */
static int test_zero_ambiguity(){
    initialize_queue();
    // 空で dequeue() -> returns 0 (NULL)
    uint8_t v_empty = dequeue();
    int emp = is_empty();
    // enqueue 0, dequeue -> 0 (but same as empty)
    enqueue(0);
    uint8_t v_data = dequeue();
    int emp_after = is_empty();
    int ambiguous = (v_empty == 0) && (v_data == 0) && (emp && emp_after);
    if(ambiguous){
        printf("  Ambiguity detected: empty-dequeue -> %u, enqueue(0)/dequeue -> %u\n",
               (unsigned)v_empty, (unsigned)v_data);
        printf("  Explanation: dequeue() returns 0 for empty (NULL). Since DTYPE==uint8_t, value 0 is indistinguishable from empty.\n");
    }
    check_print("test_zero_ambiguity (empty vs value 0)", ambiguous==1);
    return ambiguous;
}

int main(void){
    int all_ok = 1;
    printf("=== queue implementation stress tests ===\n");
    if(!test_empty_dequeue()) all_ok = 0;
    if(!test_basic_fifo()) all_ok = 0;
    if(!test_fill_and_overflow()) all_ok = 0;

    // ランダムストレステスト:比較的長めに回す(必要なら増やして下さい)
    unsigned long ops = 200000; // 20万操作
    unsigned seed = (unsigned)time(NULL);
    printf("Running random stress (%lu ops, seed=%u)...\n", ops, seed);
    if(!test_stress_random(ops, seed)) all_ok = 0;

    if(!test_zero_ambiguity()) all_ok = 0;

    printf("=== RESULT: %s ===\n", all_ok ? PASS : FAIL);
    return all_ok ? 0 : 1;
}

全部PASS

Claude4.1

#include "queue.h"
#include <stdio.h>
#include <assert.h>
#include <stdint.h>

// テスト結果を表示するマクロ
#define TEST_PASS(name) printf("[PASS] %s\n", name)
#define TEST_FAIL(name, msg) printf("[FAIL] %s: %s\n", name, msg)

void test_basic_functionality() {
    printf("\n=== Test 1: 基本機能テスト ===\n");
    initialize_queue();
    
    // 空の確認
    if (is_empty() != 1) {
        TEST_FAIL("初期状態", "キューが空でない");
        return;
    }
    
    // エンキュー・デキューの基本動作
    enqueue(42);
    DTYPE val = dequeue();
    if (val != 42) {
        TEST_FAIL("基本エンキュー・デキュー", "値が一致しない");
        return;
    }
    
    TEST_PASS("基本機能テスト");
}

void test_full_capacity() {
    printf("\n=== Test 2: 容量限界テスト ===\n");
    initialize_queue();
    
    int count = 0;
    // 満杯になるまでエンキュー
    while (!is_full() && count < 257) {
        enqueue(count & 0xFF);
        count++;
    }
    
    if (count != 255) {
        char msg[100];
        sprintf(msg, "容量が255でない (実際: %d)", count);
        TEST_FAIL("容量確認", msg);
        return;
    }
    
    // 満杯状態で追加エンキューを試みる
    enqueue(99);
    
    // デキューして数を確認
    int dequeue_count = 0;
    while (!is_empty() && dequeue_count < 257) {
        dequeue();
        dequeue_count++;
    }
    
    if (dequeue_count != 255) {
        char msg[100];
        sprintf(msg, "デキュー数が255でない (実際: %d)", dequeue_count);
        TEST_FAIL("満杯時の動作", msg);
        return;
    }
    
    TEST_PASS("容量限界テスト");
}

void test_empty_dequeue() {
    printf("\n=== Test 3: 空キューからのデキュー ===\n");
    initialize_queue();
    
    DTYPE val1 = dequeue();  // 空の状態でデキュー
    enqueue(0);              // 0をエンキュー
    DTYPE val2 = dequeue();  // 0をデキュー
    DTYPE val3 = dequeue();  // 再び空の状態でデキュー
    
    if (val1 != val2 || val2 != val3) {
        // 3つとも0のはず
        TEST_PASS("空デキューのエラー値識別不可");
    } else {
        TEST_FAIL("空デキューのエラー値", "正常な0と区別できない");
    }
}

void test_wraparound_behavior() {
    printf("\n=== Test 4: 循環動作テスト ===\n");
    initialize_queue();
    
    // 満杯にする
    for (int i = 0; i < 255; i++) {
        enqueue(i & 0xFF);
    }
    
    // 全て取り出す
    for (int i = 0; i < 255; i++) {
        DTYPE val = dequeue();
        if (val != (i & 0xFF)) {
            TEST_FAIL("循環動作1", "値の順序が不正");
            return;
        }
    }
    
    // 再度満杯にする
    for (int i = 0; i < 255; i++) {
        enqueue((i + 100) & 0xFF);
    }
    
    // 最初の値を確認
    DTYPE first = dequeue();
    if (first != 100) {
        char msg[100];
        sprintf(msg, "2回目の循環で最初の値が不正 (期待: 100, 実際: %d)", first);
        TEST_FAIL("循環動作2", msg);
        return;
    }
    
    TEST_PASS("循環動作テスト");
}

void test_alternating_pattern() {
    printf("\n=== Test 5: 交互操作パターンテスト ===\n");
    initialize_queue();
    
    // 満杯にする
    for (int i = 0; i < 255; i++) {
        enqueue(i & 0xFF);
    }
    
    int failed = 0;
    // 1つデキュー、1つエンキューを繰り返す
    for (int i = 0; i < 1000; i++) {
        DTYPE val = dequeue();
        enqueue(0xFF);
        
        // 常に満杯状態を維持しているはず
        if (!is_full()) {
            failed = 1;
            break;
        }
    }
    
    if (failed) {
        TEST_FAIL("交互操作", "満杯状態が維持されない");
    } else {
        TEST_PASS("交互操作パターンテスト");
    }
}

void test_stress_overflow() {
    printf("\n=== Test 6: 長期ストレステスト ===\n");
    initialize_queue();
    
    // 大量の操作でオーバーフローを狙う
    int failed = 0;
    for (long i = 0; i < 100000L; i++) {
        enqueue(1);
        if (is_empty()) {
            failed = 1;
            break;
        }
        dequeue();
        if (!is_empty()) {
            failed = 1;
            break;
        }
        
        if (i % 10000 == 0) {
            printf("  Progress: %ld iterations\n", i);
        }
    }
    
    if (failed) {
        TEST_FAIL("ストレステスト", "予期しない状態変化");
    } else {
        TEST_PASS("長期ストレステスト");
    }
}

void test_data_integrity() {
    printf("\n=== Test 7: データ整合性テスト ===\n");
    initialize_queue();
    
    // 特定のパターンでデータを入れる
    for (int i = 0; i < 100; i++) {
        enqueue(i & 0xFF);
    }
    
    // 50個取り出す
    for (int i = 0; i < 50; i++) {
        DTYPE val = dequeue();
        if (val != (i & 0xFF)) {
            TEST_FAIL("データ整合性1", "値が不正");
            return;
        }
    }
    
    // さらに100個追加
    for (int i = 100; i < 200; i++) {
        enqueue(i & 0xFF);
    }
    
    // 残りを取り出して確認
    for (int i = 50; i < 100; i++) {
        DTYPE val = dequeue();
        if (val != (i & 0xFF)) {
            TEST_FAIL("データ整合性2", "値が不正");
            return;
        }
    }
    
    for (int i = 100; i < 200; i++) {
        DTYPE val = dequeue();
        if (val != (i & 0xFF)) {
            TEST_FAIL("データ整合性3", "値が不正");
            return;
        }
    }
    
    if (!is_empty()) {
        TEST_FAIL("データ整合性4", "キューが空でない");
        return;
    }
    
    TEST_PASS("データ整合性テスト");
}

void test_boundary_conditions() {
    printf("\n=== Test 8: 境界条件テスト ===\n");
    initialize_queue();
    
    // 254個入れる(満杯の1つ手前)
    for (int i = 0; i < 254; i++) {
        enqueue(i & 0xFF);
    }
    
    if (is_full()) {
        TEST_FAIL("境界条件1", "254個で満杯判定");
        return;
    }
    
    // もう1つ追加(満杯になるはず)
    enqueue(254);
    
    if (!is_full()) {
        TEST_FAIL("境界条件2", "255個で満杯にならない");
        return;
    }
    
    // 1つ取り出す
    dequeue();
    
    if (is_full()) {
        TEST_FAIL("境界条件3", "254個で満杯判定が続く");
        return;
    }
    
    TEST_PASS("境界条件テスト");
}

void test_reinitialize() {
    printf("\n=== Test 9: 再初期化テスト ===\n");
    initialize_queue();
    
    // データを入れる
    for (int i = 0; i < 100; i++) {
        enqueue(i & 0xFF);
    }
    
    // 再初期化
    initialize_queue();
    
    if (!is_empty()) {
        TEST_FAIL("再初期化", "キューが空にならない");
        return;
    }
    
    // 新しいデータを入れて取り出す
    enqueue(42);
    DTYPE val = dequeue();
    
    if (val != 42) {
        char msg[100];
        sprintf(msg, "再初期化後の値が不正 (期待: 42, 実際: %d)", val);
        TEST_FAIL("再初期化後の動作", msg);
        return;
    }
    
    TEST_PASS("再初期化テスト");
}

void test_massive_cycle() {
    printf("\n=== Test 10: 大量サイクルテスト ===\n");
    initialize_queue();
    
    int failed = 0;
    // 255個の追加と削除を繰り返す
    for (int cycle = 0; cycle < 100; cycle++) {
        // 満杯にする
        for (int i = 0; i < 255; i++) {
            enqueue((cycle + i) & 0xFF);
        }
        
        if (!is_full()) {
            failed = 1;
            break;
        }
        
        // 全て取り出す
        for (int i = 0; i < 255; i++) {
            DTYPE val = dequeue();
            if (val != ((cycle + i) & 0xFF)) {
                failed = 1;
                break;
            }
        }
        
        if (!is_empty()) {
            failed = 1;
            break;
        }
        
        if (cycle % 10 == 0) {
            printf("  Cycle %d completed\n", cycle);
        }
    }
    
    if (failed) {
        TEST_FAIL("大量サイクル", "サイクル中にエラー発生");
    } else {
        TEST_PASS("大量サイクルテスト");
    }
}

int main() {
    printf("=== キュー実装の破壊的テスト ===\n");
    
    test_basic_functionality();
    test_full_capacity();
    test_empty_dequeue();
    test_wraparound_behavior();
    test_alternating_pattern();
    test_stress_overflow();
    test_data_integrity();
    test_boundary_conditions();
    test_reinitialize();
    test_massive_cycle();
    
    printf("\n=== テスト完了 ===\n");
    return 0;
}

全部パス

このqueueはEmptyのときNullを返すとか問題あるものの、一応全部のテストをパスしました。 こんなテスト意味あるのかな。