Skip to content

Commit f2c8317

Browse files
author
jun0
committed
[rustjava-adopt-bound-bootstrap-method-attr-index-p0] feat(classfile): BootstrapMethods 를 두 번 선언한 클래스를 거부한다
JVMS 4.7.23 은 최대 한 개만 허용하는데, bootstrap_method_indices_resolve 의 find_map 은 첫 표를 쓰고 나머지를 조용히 무시했다 — 인덱스가 어느 표에 대해 경계 검사되는지가 임의였다. 참조 JVM(OpenJDK 26.0.1)은 같은 파일에 ClassFormatError: Multiple BootstrapMethods attributes 를 내고, 표가 하나뿐인 대조군은 rc=0 으로 로드한다. 술어를 옆에 두고 validate_class 에 이었다 — indices_resolve 는 「인덱스가 실재 항목을 가리키는가」 한 문장이고 「표가 몇 개인가」는 다른 문장이라, 접어 넣으면 이름까지 바꿔야 한다. 픽스처는 같은 유효한 표를 바이트 동일하게 두 번 써서 거부 원인을 「둘이라는 사실」로 고정했다. 개악 양방향(호출 제거 · 상수 통과) 전건 red · cargo test --all 571 passed.
1 parent 5fbae91 commit f2c8317

8 files changed

Lines changed: 241 additions & 0 deletions

‎REPORT.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
11
# REPORT
2+
## [2026-09-16] `BootstrapMethods` 를 두 번 선언한 클래스를 거부한다 (rustjava-adopt-bound-bootstrap-method-attr-index-p0)
3+
- 무엇을: JVMS 4.7.23 은 `BootstrapMethods` 를 **최대 한 개**만 허용한다. 두 개를 실은 파일을 ★**거부**한다
4+
(종전에는 `find_map` 이 **첫 표**를 쓰고 나머지를 조용히 무시했다).
5+
- 왜: 채택 제안 `2026-09-16-bound-bootstrap-method-attr-index#p0`(운영자 tower 패널 채택).
6+
- 사용자 영향: ★**진단이 바뀐다** — `UnsupportedOperationException`(「이 런타임이 아직 못 한다」) →
7+
★`ClassFormatError`(「이 파일이 깨졌다」). ★**참조 JVM 과 같은 판정**이 된다.
8+
- ★★**참조 JVM 이 근거다**(observable behavior · OpenJDK 소스 미참조): OpenJDK 26.0.1 은 같은 파일에
9+
**`ClassFormatError: Multiple BootstrapMethods attributes in class file`** 를 내고, ★**표가 하나뿐인 대조군은
10+
rc=0 으로 로드**한다 ⇒ 픽스처의 결함이 «둘이라는 사실» 하나임이 참조 구현으로 확증된다.
11+
- ★**고친 자리는 «한 곳»**: `validate_class` 에 술어 `at_most_one_bootstrap_methods_attribute` 를 이었다.
12+
★`bootstrap_method_indices_resolve` 는 **무접촉** — 그 함수의 doc 이 스스로 「인덱스가 실재 항목을 가리키는가」라는
13+
**한 문장**임을 선언하고, 「표가 몇 개인가」는 **다른 문장**이다(접어 넣으면 이름까지 바꿔야 한다).
14+
★**새 관용 0** — 필드 `ConstantValue`·메서드 `Code` 가 이미 쓰는 **개수 세기** 모양 그대로다.
15+
- ★**픽스처는 «결함이 하나»가 되게 지었다**: `LdcDynamicDuplicateBSM.class` = 같은 유효한 표를 ★**바이트 동일**하게 두 번.
16+
어느 한 표만 있어도 정상 파일이라 ★**거부 원인이 «둘»로 고정**된다(둘째 표를 다르게 하면 다른 규칙이 먼저 물어
17+
테스트가 «이름과 다른 이유»로 통과한다). 구조 실측: 속성 `['BootstrapMethods','BootstrapMethods']` · 본문 동일 `True`.
18+
- ★★**제안의 한 문장은 «과했다»**: 「생성기가 만들 수 없는 픽스처가 필요하다」 — ★**8줄 래퍼로 됐다**
19+
(속성 목록이 빌더에 그대로 전달된다). **관측은 맞았고 비용 추정이 틀렸다.**
20+
- 검증: 개악 **양방향** — ⑴호출부에서 술어 제거(=제안 이전 상태) **red** ⑵술어 본문을 **`true`(상수 통과)** 로 **red**
21+
(★⑵가 없으면 「검사가 상수로 뭉개진」 축을 못 잡는다) · 정상 **green** ·
22+
`cargo test --all` **571 passed / 0 failed / 1 ignored**(27 스위트 **전건 합산**) · 픽스처 재생성 **멱등**(기존 11 바이트 동일) · DoD 7명령 rc=0.
23+
- 후속 추천: 클래스 수준의 **다른 「최대 1개」 속성**(SourceFile·EnclosingMethod·Signature…)도 같은 규칙을 받아야 하는지 **판정**
24+
— ★이번 건이 검사를 얻은 이유는 「중복이 하류에 임의 선택을 만든다」이고, 파서가 무시하는 속성엔 그 논거가 **전이되지 않는다**.
25+
상세 = `docs/worklog/2026-09-16-reject-duplicate-bootstrap-methods.md`.
26+
227
## [2026-09-16] `ldc` 태그 15/16/17 — ★**ASM 은 «낸다»**(Kotlin·Scala·Lombok 산출물은 0) (rustjava-ldc-tags-15-16-17-real-world-generator-survey)
328
- 무엇을: 선행 회차가 남긴 **「못 쟀다」**(ASM·Kotlin·Scala·Lombok)를 **쟀다**. 조사 회차 — ★**크레이트 무접촉**(파서·테스트 0).
429
- 왜: 채택 제안 `2026-09-16-ldc-tags-15-16-17#p2`. javac 은 안 낸다가 이미 증명됐고, **직접 바이트코드를 짜는 도구**가 남아 있었다.

‎STATE.md‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,29 @@
44
(없음 — 2026-09-16 실측: 착수 시 진행 티켓 0 · 열린 PR 0. ※「열린 PR 0」은 ★**이 회차 PR 착지 시점 기준**이다 — 회신 시점에는 그 PR 자신이 열려 있다)
55

66
## 완료
7+
- [rustjava-adopt-bound-bootstrap-method-attr-index-p0] ★★**`BootstrapMethods` 를 «두 번» 선언한 클래스를 거부한다 — 임의 선택을 없앴다.**
8+
채택 제안 `2026-09-16-bound-bootstrap-method-attr-index#p0`(운영자 tower 패널 채택 · worklog json `adoptedProposals` 기록).
9+
★**JVMS 4.7.23 = 최대 한 개.** 종전에는 `find_map` 이 **첫 표**를 쓰고 나머지를 **조용히 무시**했다 ⇒
10+
★**`bootstrap_method_attr_index` 가 «어느 표»에 대해 경계 검사되는지가 임의**였고 그 사실이 아무 데도 드러나지 않았다.
11+
★**전/후**: `UnsupportedOperationException`(「아직 못 한다」) → ★`ClassFormatError`(「이 파일이 깨졌다」).
12+
★★**참조 JVM 이 근거다**(observable behavior · OpenJDK 소스 미참조): OpenJDK 26.0.1 →
13+
**`ClassFormatError: Multiple BootstrapMethods attributes in class file`** · ★**표 하나짜리 대조군은 rc=0 로드**.
14+
★**고친 자리 «한 곳»** — `validate_class` 에 술어 `at_most_one_bootstrap_methods_attribute` 를 이었다.
15+
★`bootstrap_method_indices_resolve` **무접촉**(그 doc 이 스스로 「인덱스가 실재 항목을 가리키는가」라는 한 문장임을
16+
선언한다 — 「표가 몇 개인가」는 다른 문장이고, 접어 넣으면 **이름까지 바꿔야** 한다) · ★**새 관용 0**
17+
(필드 `ConstantValue`·메서드 `Code` 가 이미 쓰는 **개수 세기** 모양 그대로).
18+
★★**픽스처를 «결함이 하나»가 되게 지었다** — `LdcDynamicDuplicateBSM.class` = **같은 유효한 표를 바이트 동일하게 두 번**.
19+
어느 한 표만 있어도 정상 파일이라 ★거부 원인이 «둘이라는 사실»로 **고정**된다(둘째를 다르게 하면 다른 규칙이 먼저 물어
20+
테스트가 «이름과 다른 이유»로 통과한다 — 이 저장소가 #49 에서 세운 그 규율).
21+
구조를 **측정**했다: 속성 `['BootstrapMethods','BootstrapMethods']` · 두 본문 **바이트 동일 True**.
22+
★★**제안의 한 문장은 «과했다»** — 「생성기가 만들 수 없는 픽스처가 필요하다」는 **거짓**이고 ★**8줄 래퍼**로 됐다
23+
(속성 목록이 빌더에 그대로 전달된다). ⇒ **관측은 맞았고 «비용 추정»이 틀렸다** — 다음 사람이 같은 이유로 미루지 않게 적는다.
24+
★**개악 대조 양방향**: ⑴호출부에서 술어 제거(= 제안 이전 상태) **red** ⑵술어 본문을 **`true`(상수 통과)** 로 **red** ·
25+
정상 **green**. ★**⑵가 없으면 「검사가 상수로 뭉개진」 축을 못 잡는다**(⑴만으로는 호출 삭제만 잡힌다).
26+
★`cargo test --all` **571 / 0 failed / 1 ignored**(27 스위트 **전건 합산** — 꼬리만 세지 않았다) ·
27+
픽스처 재생성 **멱등**(기존 11 전건 바이트 동일 · 신규 1) · DoD **7줄 전건 rc=0**.
28+
★**잃는 것**: 지금까지 «로드되던» 파일 하나가 거부된다 — 다만 그 형상은 어제 이 저장소가 잰 대로
29+
**javac·kotlinc·scalac·Lombok 산출물 5,479 클래스에 0**이고 ASM 으로도 «일부러» 만들어야 나온다.
730
- [rustjava-ldc-tags-15-16-17-real-world-generator-survey] ★★**「못 쟀다」를 «쟀다»로 바꿨다 — ASM 은 태그 15/16/17 을 «낸다».**
831
채택 제안 `2026-09-16-ldc-tags-15-16-17#p2`(worklog json `adoptedProposals` 기록). ★**조사 회차 · 크레이트 무접촉**(파서·테스트 0).
932
★★**ASM 9.7.1 = 낸다(실증)** — `visitLdcInsn(Handle)`·`(Type.getMethodType)`·`(ConstantDynamic)` 15줄로 만든 클래스에서

‎classfile/src/validation.rs‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub(crate) fn validate_class(class: &ClassInfo) -> Result<(), ClassFileError> {
1616
|| !validate_constant_pool(&class.constant_pool)
1717
|| !constant_pool_tags_fit_the_class_file_version(class)
1818
|| !bootstrap_method_indices_resolve(class)
19+
|| !at_most_one_bootstrap_methods_attribute(class)
1920
{
2021
return Err(ClassFileError::InvalidFormat);
2122
}
@@ -134,6 +135,27 @@ fn bootstrap_method_indices_resolve(class: &ClassInfo) -> bool {
134135
})
135136
}
136137

138+
/// JVMS 4.7.23: at most one `BootstrapMethods` attribute may appear in a ClassFile's attributes
139+
/// table. A file carrying two is broken, not a file using a feature this runtime lacks.
140+
///
141+
/// Kept separate from `bootstrap_method_indices_resolve` because it is a different sentence: that
142+
/// one asks whether an index names a real entry, this one asks how many tables exist. Folding it in
143+
/// would also mean renaming that function for a rule it did not previously make.
144+
///
145+
/// It matters because `bootstrap_method_indices_resolve` resolves the table with `find_map`, which
146+
/// stops at the first one. With two tables that choice is arbitrary — the index would be bounded
147+
/// against whichever came first and the other silently ignored — so the honest answer is to reject
148+
/// the file rather than pick. Counting is the same shape `validate_class` already uses for the
149+
/// per-member "at most one" rules (`ConstantValue` on a field, `Code` on a method).
150+
fn at_most_one_bootstrap_methods_attribute(class: &ClassInfo) -> bool {
151+
class
152+
.attributes
153+
.iter()
154+
.filter(|attribute| matches!(attribute, AttributeInfo::BootstrapMethods(_)))
155+
.count()
156+
<= 1
157+
}
158+
137159
fn validate_constant_pool(constant_pool: &BTreeMap<u16, ConstantPoolItem>) -> bool {
138160
constant_pool.values().all(|item| match item {
139161
ConstantPoolItem::Class { name_index } => constant_pool
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"schema": "worklog/v1",
3+
"date": "2026-09-16",
4+
"taskId": "rustjava-adopt-bound-bootstrap-method-attr-index-p0",
5+
"summary": "A class declaring BootstrapMethods twice is now rejected as malformed instead of being read with whichever table came first. JVMS 4.7.23 allows at most one; OpenJDK 26 answers ClassFormatError: Multiple BootstrapMethods attributes for the same file.",
6+
"changes": [
7+
"classfile/src/validation.rs: new at_most_one_bootstrap_methods_attribute predicate, wired into validate_class's rejection chain. bootstrap_method_indices_resolve is untouched — counting tables is a different sentence from bounding an index, and folding it in would have meant renaming that function",
8+
"test-data/src/ldc/make_ldc_fixtures.py: new duplicate_bootstrap_methods(inner) wrapper that re-appends the attribute its inner builder wrote",
9+
"test-data/ldc/LdcDynamicDuplicateBSM.class: new fixture, the same valid table byte-for-byte twice",
10+
"tests/test_class_format.rs: test_a_class_declaring_bootstrap_methods_twice_is_malformed"
11+
],
12+
"verification": [
13+
"proposal re-measured at start: the find_map it names is still at classfile/src/validation.rs:117, so the proposal is still live",
14+
"reference JVM, OpenJDK 26.0.1: the fixture gives 'java.lang.ClassFormatError: Multiple BootstrapMethods attributes in class file LdcDynamicDuplicateBSM', while the single-table control LdcDynamic loads with rc=0 and no output",
15+
"our runtime before the fix: UnsupportedOperationException 'ldc of a dynamically-computed constant'; after: ClassFormatError 'Invalid class file'; the single-table control's answer is unchanged, which shows the change is narrow",
16+
"fixture structure measured, not assumed: class attributes are ['BootstrapMethods', 'BootstrapMethods'] and the two bodies are byte-identical",
17+
"fixture regeneration is idempotent: 11 existing .class files byte-identical, 1 added",
18+
"mutation M1, removing the call from validate_class (i.e. the pre-proposal state): the new test goes red",
19+
"mutation M2, replacing the predicate body with `true` (constant pass): red at the same assertion — M1 alone would not catch a check flattened to a constant",
20+
"restore: test_class_format 12 passed / 0 failed",
21+
"full suite: 571 passed / 0 failed / 1 ignored, counted by summing every 'test result' line rather than reading the tail",
22+
"DoD 7 commands all rc=0"
23+
],
24+
"issues": [
25+
"The proposal's tradeoff said the fixture was one 'the generator cannot currently build'. That was too strong: the attribute list is handed to each builder, so an 8-line wrapper that re-appends what the inner builder wrote is enough. The observation was right, the cost estimate was not."
26+
],
27+
"adoptedProposals": [
28+
"2026-09-16-bound-bootstrap-method-attr-index#p0"
29+
],
30+
"proposals": [
31+
{
32+
"title": "Decide whether the other class-level attributes also need cardinality rules",
33+
"plainSummary": "We now reject a class that declares two bootstrap tables. Several other class attributes are also allowed only once each, and nothing checks those.",
34+
"userBenefit": "The same honesty applied consistently: a file carrying two of something the spec allows one of is reported as broken, whichever attribute it is.",
35+
"why": "JVMS 4.7 marks several ClassFile attributes as at-most-one (SourceFile, EnclosingMethod, Signature, SourceDebugExtension, and the module-related ones). This round added the rule for BootstrapMethods because a proposal named it and because the resolver silently picks the first of two. Whether the others deserve the same treatment is a separate question, and the answer may be no for the ones nothing reads.",
36+
"tradeoff": "Rejecting more files that load today, for attributes whose duplication changes nothing we act on — the BootstrapMethods case earned its check because a duplicate makes an arbitrary choice observable downstream, and that argument does not transfer to an attribute the parser ignores. Doing it uniformly would also need a fixture per attribute.",
37+
"effort": "S",
38+
"target": "classfile/src/validation.rs"
39+
}
40+
]
41+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# 2026-09-16 — `BootstrapMethods` 를 두 번 선언한 클래스를 거부한다
2+
3+
티켓 `rustjava-adopt-bound-bootstrap-method-attr-index-p0` — 채택 제안
4+
`2026-09-16-bound-bootstrap-method-attr-index#p0`(운영자가 tower 패널에서 채택).
5+
6+
## ⓐ 제안이 «지금도» 참인가 — 재서 확인했다
7+
8+
원문 `why`: 「`bootstrap_method_indices_resolve` uses `find_map`, which takes the first and never
9+
looks for a second」. ★**착수 시 재측: 그 `find_map` 은 `classfile/src/validation.rs:117` 에 그대로 있다.**
10+
⇒ 제안은 **유효**하다(그 worklog 를 낸 회차 이후 이 자리는 움직이지 않았다).
11+
12+
JVMS 4.7.23 은 ClassFile 속성표에 `BootstrapMethods` 를 ★**최대 한 개**만 허용한다.
13+
14+
## ⓑ 이미 같은 것을 하는 축이 있는가 — 없다. 단 «모양»은 이미 있다
15+
16+
`validate_class` 는 이미 **부재/중복 개수**를 세는 규칙을 둘 갖고 있다:
17+
필드의 `ConstantValue` (`constant_values.len() > 1` → 거부) · 메서드의 `Code` (`code_attributes != 1` → 거부).
18+
★**클래스 «자신»의 속성표에는 그런 규칙이 하나도 없었다.** ⇒ 새 관용을 발명하지 않고 **그 모양을 그대로** 썼다.
19+
20+
## ⓒ 제안이 틀린 부분 — ★**`tradeoff` 의 한 문장은 «과했다»**
21+
22+
원문: 「It also needs a fixture **the generator cannot currently build**, since the attribute list is
23+
assembled per fixture.」 ★**그것은 사실이 아니다.** 속성 목록은 빌더에게 `attributes` 리스트로 **그대로 전달**되므로,
24+
기존 빌더를 감싸 **그 빌더가 쓴 속성을 한 번 더 append** 하는 래퍼 **8줄**이면 된다
25+
(`duplicate_bootstrap_methods(inner)`). 생성기 구조를 바꿀 필요가 없었다.
26+
⇒ ★**제안의 관측은 맞았고, 비용 추정이 틀렸다.** 그 차이를 적어 둔다 — 다음 사람이 같은 이유로 미루지 않게.
27+
28+
## 고친 자리 — 한 곳
29+
30+
`classfile/src/validation.rs` 에 술어 하나를 더하고 `validate_class` 의 거부 조건에 이었다:
31+
32+
```rust
33+
fn at_most_one_bootstrap_methods_attribute(class: &ClassInfo) -> bool {
34+
class.attributes.iter().filter(|a| matches!(a, AttributeInfo::BootstrapMethods(_))).count() <= 1
35+
}
36+
```
37+
38+
★**`bootstrap_method_indices_resolve` 를 «고치지» 않고 «옆에» 뒀다** — 그 함수의 doc 이 스스로
39+
「인덱스가 실재 항목을 가리키는가」라는 **한 문장**임을 선언하고 있고, 「표가 몇 개인가」는 **다른 문장**이다.
40+
안에 접어 넣으면 그 함수를 **이름까지 바꿔야** 하는데 이 회차의 경계가 그것을 금한다.
41+
42+
## 픽스처 — ★**결함이 «하나»가 되도록 지었다**
43+
44+
`test-data/ldc/LdcDynamicDuplicateBSM.class`(생성기 `make_ldc_fixtures.py` · 신규 래퍼 `duplicate_bootstrap_methods`).
45+
★**같은 유효한 표를 «바이트 동일»하게 두 번** 쓴다 — 어느 한 표만 있어도 **정상 파일**이므로
46+
★**거부의 원인이 «둘이라는 사실» 하나로 고정**된다(둘째 표를 다르게 만들면 다른 규칙이 먼저 물어서
47+
테스트가 «이름과 다른 이유»로 통과한다).
48+
구조 확인(측정): 클래스 속성 = `['BootstrapMethods', 'BootstrapMethods']` · **두 본문 바이트 동일 = True**.
49+
재생성 **멱등**: 기존 11개 **전건 바이트 동일** · 신규 1개.
50+
51+
## 전/후 — 실제로 무엇이 바뀌나 (end-to-end)
52+
53+
| 파일 | 고치기 «전» | 고친 «후» | ★참조 JVM(OpenJDK 26.0.1) |
54+
|---|---|---|---|
55+
| `LdcDynamicDuplicateBSM`(표 2개) | `UnsupportedOperationException: ldc of a dynamically-computed constant` | ★**`ClassFormatError: Invalid class file`** | ★**`ClassFormatError: Multiple BootstrapMethods attributes in class file`** |
56+
| `LdcDynamic`(표 1개 · 대조군) | `UnsupportedOperationException` | ★**불변** | 로드 성공(rc=0 · 무출력) |
57+
58+
⇒ ★**이 리니지의 문장 그대로다**: 「이 런타임이 아직 못 한다」 → **「이 파일이 깨졌다」**, 그리고 그 판정이
59+
참조 JVM 과 **일치**한다. ★대조군이 불변이라는 것이 **변경의 좁음**을 보인다.
60+
※`AGENTS.md` 의 허용 축(observable behavior)만 썼다 — OpenJDK **소스 미참조**.
61+
62+
## 개악 대조 (양방향 · 제품 «호출부»)
63+
64+
| 개악 | 결과 |
65+
|---|---|
66+
| **M1** — 호출부에서 `|| !at_most_one_bootstrap_methods_attribute(class)` **제거**(= 제안 이전 상태) | ★**red** — `expected ClassFormatError, got: … UnsupportedOperationException` |
67+
| **M2** — 술어 본문을 **`true`(상수 통과)** 로 | ★**red** — 같은 자리에서 |
68+
| 정상 | **green**(`test_class_format` 12 passed) |
69+
70+
★**M2 를 따로 돌린 이유**: M1 만으로는 「호출을 지웠다」만 잡고 ★**「검사가 상수로 뭉개졌다」는 안 잡힌다**
71+
(이 저장소가 반복해 지적한 「상수 대 상수」 형태). 두 축이 다 red 여야 그 단언이 **실제로 무는 것**이다.
72+
73+
## 잃는 것 (계약 2⒜) — 숨기지 않는다
74+
75+
- ★**지금까지 로드되던 파일 하나가 거부된다.** 다만 그 형상은 ★**어떤 컴파일러도 내지 않는다** —
76+
이 저장소가 바로 어제 잰 바로는 javac·kotlinc·scalac·Lombok 산출물 **5,479 클래스에 0**이고,
77+
ASM 으로도 «일부러» 만들어야 나온다. ⇒ 실사용자를 놀라게 할 확률이 낮다.
78+
- ★**파스 시점 비용**: 클래스 속성표 1회 선형 순회(속성 수는 보통 한 자릿수). 상수 풀은 건드리지 않는다.
79+
- ★**오탐 여지**: 규칙이 «개수»뿐이라 해석 여지가 없다 — JVMS 4.7.23 문면이 그대로 술어다.
80+
81+
## 안 하면 무엇이 나쁜가 (계약 2⒝)
82+
83+
★**두 표 중 «어느 것이 진짜인가»를 파서가 임의로 정한다.** `find_map` 이 첫 번째를 쓰므로
84+
`bootstrap_method_attr_index` 는 **첫 표**에 대해서만 경계 검사되고 둘째 표는 **조용히 무시**된다.
85+
⇒ 같은 파일이 「첫 표 기준으로는 유효」할 수 있고, 그 사실이 **아무 데도 드러나지 않는다**.
86+
그리고 우리는 그 파일을 **「미지원」**이라 불렀다 — ★**참조 JVM 이 «깨졌다»고 답하는 파일에 대해서.**
388 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)