Panic: debug_assert in SubroutineCollection::new on a CID-keyed font with 256 Font DICTs

オープン 初心者向け
#34 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
74/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
rust
領域
tooling

調査の方向性

まず subsetter-0.2.6/src/cff/subroutines.rs、特に SubroutineCollection::new と get_handler の経路を確認し、次に FDArray の数がどのように表現されているかを追ってください。提供された reproducer を実行するか、256 個の Font DICT を対象とする回帰テストを追加してください。有効な 256 個の Font DICT を持つフォントがデバッグビルドで panic を起こさなくなり、サポートされない数も panic なしで処理されれば完了です。

索引モデルが issue の本文から書いたものです。

説明

subset panics on a CID-keyed CFF font with 256 Font DICTs, in any build with debug assertions (debug builds, cargo test, fuzzing):

thread 'main' panicked at subsetter-0.2.6/src/cff/subroutines.rs:11:9:
assertion failed: subroutines.len() <= 255

SubroutineCollection::new asserts that there are at most 255 local subroutine lists, one per Font DICT. FDSelect gives each glyph's Font DICT as a Card8 (Technical Note #5176, FDSelect formats 0 and 3), so indices 0 to 255 address 256 Font DICTs, and a font with 256 is well formed. Without debug assertions subset returns Ok. This happens with 0.2.6 and with main (b0adcc1).

To reproduce with subsetter 0.2.6 (default-features = false). The font is built in code for this report:

/// A CFF INDEX of `items`.
fn index(items: &[Vec<u8>]) -> Vec<u8> {
    if items.is_empty() {
        return vec![0, 0];
    }
    let mut offsets = vec![1u32];
    for item in items {
        offsets.push(offsets.last().unwrap() + item.len() as u32);
    }
    let size = (1..=4u8).find(|&size| *offsets.last().unwrap() < 1 << (8 * size as u32)).unwrap();
    let mut out = (items.len() as u16).to_be_bytes().to_vec();
    out.push(size);
    for offset in offsets {
        out.extend(&offset.to_be_bytes()[4 - size as usize..]);
    }
    items.iter().for_each(|item| out.extend(item));
    out
}

/// A DICT integer operand in five bytes.
fn int5(value: i32) -> Vec<u8> {
    [&[29u8][..], &value.to_be_bytes()].concat()
}

/// A minimal OpenType font whose CFF table is CID-keyed: one glyph (.notdef, endchar)
/// and an FDArray of `count` Font DICTs, all pointing at one empty Private DICT. FDSelect
/// (format 0) puts the glyph in Font DICT 0.
fn font(count: usize) -> Vec<u8> {
    let header = vec![1, 0, 4, 1];
    let names = index(&[b"A".to_vec()]);
    let strings = index(&[b"Adobe".to_vec(), b"Identity".to_vec()]);
    let gsubrs = index(&[]);
    let charstrings = index(&[vec![14]]);
    let fdselect = vec![0, 0];
    let top = |charstrings_at: i32, fdarray_at: i32, fdselect_at: i32| {
        [
            int5(391), int5(392), int5(0), vec![12, 30], // ROS Adobe-Identity-0
            int5(charstrings_at), vec![17],
            int5(fdarray_at), vec![12, 36],
            int5(fdselect_at), vec![12, 37],
        ]
        .concat()
    };
    let font_dict = |private_at: i32| [int5(0), int5(private_at), vec![18]].concat();
    let top_len = index(&[top(0, 0, 0)]).len();
    let fdarray_len = index(&vec![font_dict(0); count]).len();
    let charstrings_at = header.len() + names.len() + top_len + strings.len() + gsubrs.len();
    let fdselect_at = charstrings_at + charstrings.len();
    let fdarray_at = fdselect_at + fdselect.len();
    let private_at = fdarray_at + fdarray_len;
    let top = index(&[top(charstrings_at as i32, fdarray_at as i32, fdselect_at as i32)]);
    let fdarray = index(&vec![font_dict(private_at as i32); count]);
    let cff = [header, names, top, strings, gsubrs, charstrings, fdselect, fdarray].concat();
    let mut font = b"OTTO".to_vec();
    for value in [1u16, 16, 0, 0] {
        font.extend(value.to_be_bytes());
    }
    font.extend(b"CFF ");
    for value in [0u32, 28, cff.len() as u32] {
        font.extend(value.to_be_bytes());
    }
    font.extend(cff);
    font
}

fn main() {
    for count in [255, 256] {
        let data = font(count);
        let remapper = subsetter::GlyphRemapper::new();
        println!("{count} Font DICTs: {:?}", subsetter::subset(&data, 0, &remapper).map(|font| font.len()));
    }
}

cargo run prints 255 Font DICTs: Ok(156) and then panics as above; cargo run --release prints Ok(156) for both.

It was found while reviewing how officewright guards against subsetter panics; until it is fixed, our call to subset turns the panic into an error.

A possible fix: assert subroutines.len() <= 256, or, since get_handler takes a u8, return a CFFError for an FDArray of more than 256 Font DICTs instead of asserting.

主要言語
Rust
スター
61
フォーク
16
PR マージ指標
30日以内にマージされた PR はありません

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

typst/subsetter のほかの issue

typst/subsetter の issue をすべて見る

似ている issue

Rust の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。