Bug: Label_2P_FM3 / FM4 / FM5 missing-header guard is dead (split.length is never 0); FM3 then throws on header[1].length, FM4/FM5 emit undefined fields
bug
## Summary
All three `Label_2P_FM*` plugins try to detect a missing `FM3 ` / `FM4` / `FM5 ` preamble with `if (header.length == 0)` after splitting `parts[0]`. `String.prototype.split` always returns at least one element, so the guard is **never true** — it's dead code. When the preamble token actually is missing, `header[1]` is `undefined`:
- **FM3** dereferences `header[1].length` → throws `TypeError: Cannot read properties of undefined (reading 'length')`. Because the dispatch in `MessageDecoder.decode` does not wrap individual plugins in `try`/`catch`, the exception propagates out and aborts the whole decode.
- **FM4** and **FM5** silently pass `undefined` to `ResultFormatter.departureAirport`, producing a "successfully decoded" result with `departureAirport: { value: undefined }`.
All three plugins are reachable from `MessageDecoder` for label `2P` (see `MessageDecoder.ts:40-42`), and any 2P message whose comma-count matches the plugin's expectation but whose `parts[0]` doesn't actually contain the FMx token hits this path.
This is not a duplicate of #459 (different file, different handlers).
## Affected code
Same shape in three files. FM3:
```ts
// lib/plugins/Label_2P_FM3.ts:24-40
if (parts.length === 7) {
const header = parts[0].split('FM3 ');
if (header.length == 0) { // never true
ResultFormatter.unknown(decodeResult, message.text);
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
return decodeResult;
}
if (header[0].length > 0) {
ResultFormatter.unknown(decodeResult, header[0].substring(0, 4));
ResultFormatter.flightNumber(decodeResult, header[0].substring(4));
}
if (header[1].length === 4) { ... } // throws when header[1] is undefined
```
FM4 (`lib/plugins/Label_2P_FM4.ts:23-37`) and FM5 (`lib/plugins/Label_2P_FM5.ts:23-34`) have the same dead guard and then pass `header[1]` straight into `ResultFormatter.departureAirport` without checking.
Note FM3 and FM5 split on `'FM3 '`/`'FM5 '` (with trailing space) while FM4 splits on `'FM4'` (no space). The fix is the same shape in all three.
## Reproduction
```ts
const decoder = new MessageDecoder();
// FM3 throws
decoder.decode({
label: '2P',
text: 'noFMheader,1454,N 45.206,E 17.726,34030, 440,98',
});
// → TypeError: Cannot read properties of undefined (reading 'length')
// FM4 silently produces undefined fields (10-field text, no 'FM4' token)
decoder.decode({
label: '2P',
text: 'noFMheader,KORD,2114000,1518,N 45.206,E 17.726,34030,180,foo,bar',
});
// → decoded: true, but result includes departureAirport.value === undefined
// FM5 silently produces undefined fields (12-field text, no 'FM5 ' token)
decoder.decode({
label: '2P',
text: 'noFMhdr,KORD,142000,1518,N 45.206,E 17.726,34030,a,b,c,FLT123,d',
});
// → decoded: true, but result includes departureAirport.value === undefined
```
## Suggested fix
Change the guard in each plugin to check for the case the comment actually describes — the FMx token wasn't present:
```ts
if (header.length < 2) { // or: if (parts[0].indexOf('FM3 ') === -1)
ResultFormatter.unknown(decodeResult, message.text);
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
return decodeResult;
}
```
(Replace `'FM3 '` / `'FM4'` / `'FM5 '` accordingly per file.)
## Test coverage
The existing `Label_2P_FM3` test only covers well-formed `FM3 …` input, so the FM3 throw is uncovered. Adding a "no preamble" case per plugin that asserts the result has `decoded === false` and (for FM3) that the call does not throw would lock the fix in.
0 条评论