1 | //= ScanfFormatString.cpp - Analysis of printf format strings --*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // Handling of format string in scanf and friends. The structure of format |
10 | // strings for fscanf() are described in C99 7.19.6.2. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "clang/AST/FormatString.h" |
15 | #include "FormatStringParsing.h" |
16 | #include "clang/Basic/TargetInfo.h" |
17 | |
18 | using clang::analyze_format_string::ArgType; |
19 | using clang::analyze_format_string::FormatStringHandler; |
20 | using clang::analyze_format_string::LengthModifier; |
21 | using clang::analyze_format_string::OptionalAmount; |
22 | using clang::analyze_format_string::ConversionSpecifier; |
23 | using clang::analyze_scanf::ScanfConversionSpecifier; |
24 | using clang::analyze_scanf::ScanfSpecifier; |
25 | using clang::UpdateOnReturn; |
26 | using namespace clang; |
27 | |
28 | typedef clang::analyze_format_string::SpecifierResult<ScanfSpecifier> |
29 | ScanfSpecifierResult; |
30 | |
31 | static bool ParseScanList(FormatStringHandler &H, |
32 | ScanfConversionSpecifier &CS, |
33 | const char *&Beg, const char *E) { |
34 | const char *I = Beg; |
35 | const char *start = I - 1; |
36 | UpdateOnReturn <const char*> UpdateBeg(Beg, I); |
37 | |
38 | // No more characters? |
39 | if (I == E) { |
40 | H.HandleIncompleteScanList(start, end: I); |
41 | return true; |
42 | } |
43 | |
44 | // Special case: ']' is the first character. |
45 | if (*I == ']') { |
46 | if (++I == E) { |
47 | H.HandleIncompleteScanList(start, end: I - 1); |
48 | return true; |
49 | } |
50 | } |
51 | |
52 | // Special case: "^]" are the first characters. |
53 | if (I + 1 != E && I[0] == '^' && I[1] == ']') { |
54 | I += 2; |
55 | if (I == E) { |
56 | H.HandleIncompleteScanList(start, end: I - 1); |
57 | return true; |
58 | } |
59 | } |
60 | |
61 | // Look for a ']' character which denotes the end of the scan list. |
62 | while (*I != ']') { |
63 | if (++I == E) { |
64 | H.HandleIncompleteScanList(start, end: I - 1); |
65 | return true; |
66 | } |
67 | } |
68 | |
69 | CS.setEndScanList(I); |
70 | return false; |
71 | } |
72 | |
73 | // FIXME: Much of this is copy-paste from ParsePrintfSpecifier. |
74 | // We can possibly refactor. |
75 | static ScanfSpecifierResult ParseScanfSpecifier(FormatStringHandler &H, |
76 | const char *&Beg, |
77 | const char *E, |
78 | unsigned &argIndex, |
79 | const LangOptions &LO, |
80 | const TargetInfo &Target) { |
81 | using namespace clang::analyze_format_string; |
82 | using namespace clang::analyze_scanf; |
83 | const char *I = Beg; |
84 | const char *Start = nullptr; |
85 | UpdateOnReturn <const char*> UpdateBeg(Beg, I); |
86 | |
87 | // Look for a '%' character that indicates the start of a format specifier. |
88 | for ( ; I != E ; ++I) { |
89 | char c = *I; |
90 | if (c == '\0') { |
91 | // Detect spurious null characters, which are likely errors. |
92 | H.HandleNullChar(nullCharacter: I); |
93 | return true; |
94 | } |
95 | if (c == '%') { |
96 | Start = I++; // Record the start of the format specifier. |
97 | break; |
98 | } |
99 | } |
100 | |
101 | // No format specifier found? |
102 | if (!Start) |
103 | return false; |
104 | |
105 | if (I == E) { |
106 | // No more characters left? |
107 | H.HandleIncompleteSpecifier(startSpecifier: Start, specifierLen: E - Start); |
108 | return true; |
109 | } |
110 | |
111 | ScanfSpecifier FS; |
112 | if (ParseArgPosition(H, CS&: FS, Start, Beg&: I, E)) |
113 | return true; |
114 | |
115 | if (I == E) { |
116 | // No more characters left? |
117 | H.HandleIncompleteSpecifier(startSpecifier: Start, specifierLen: E - Start); |
118 | return true; |
119 | } |
120 | |
121 | // Look for '*' flag if it is present. |
122 | if (*I == '*') { |
123 | FS.setSuppressAssignment(I); |
124 | if (++I == E) { |
125 | H.HandleIncompleteSpecifier(startSpecifier: Start, specifierLen: E - Start); |
126 | return true; |
127 | } |
128 | } |
129 | |
130 | // Look for the field width (if any). Unlike printf, this is either |
131 | // a fixed integer or isn't present. |
132 | const OptionalAmount &Amt = clang::analyze_format_string::ParseAmount(Beg&: I, E); |
133 | if (Amt.getHowSpecified() != OptionalAmount::NotSpecified) { |
134 | assert(Amt.getHowSpecified() == OptionalAmount::Constant); |
135 | FS.setFieldWidth(Amt); |
136 | |
137 | if (I == E) { |
138 | // No more characters left? |
139 | H.HandleIncompleteSpecifier(startSpecifier: Start, specifierLen: E - Start); |
140 | return true; |
141 | } |
142 | } |
143 | |
144 | // Look for the length modifier. |
145 | if (ParseLengthModifier(FS, Beg&: I, E, LO, /*IsScanf=*/true) && I == E) { |
146 | // No more characters left? |
147 | H.HandleIncompleteSpecifier(startSpecifier: Start, specifierLen: E - Start); |
148 | return true; |
149 | } |
150 | |
151 | // Detect spurious null characters, which are likely errors. |
152 | if (*I == '\0') { |
153 | H.HandleNullChar(nullCharacter: I); |
154 | return true; |
155 | } |
156 | |
157 | // Finally, look for the conversion specifier. |
158 | const char *conversionPosition = I++; |
159 | ScanfConversionSpecifier::Kind k = ScanfConversionSpecifier::InvalidSpecifier; |
160 | switch (*conversionPosition) { |
161 | default: |
162 | break; |
163 | case '%': k = ConversionSpecifier::PercentArg; break; |
164 | case 'b': k = ConversionSpecifier::bArg; break; |
165 | case 'A': k = ConversionSpecifier::AArg; break; |
166 | case 'E': k = ConversionSpecifier::EArg; break; |
167 | case 'F': k = ConversionSpecifier::FArg; break; |
168 | case 'G': k = ConversionSpecifier::GArg; break; |
169 | case 'X': k = ConversionSpecifier::XArg; break; |
170 | case 'a': k = ConversionSpecifier::aArg; break; |
171 | case 'd': k = ConversionSpecifier::dArg; break; |
172 | case 'e': k = ConversionSpecifier::eArg; break; |
173 | case 'f': k = ConversionSpecifier::fArg; break; |
174 | case 'g': k = ConversionSpecifier::gArg; break; |
175 | case 'i': k = ConversionSpecifier::iArg; break; |
176 | case 'n': k = ConversionSpecifier::nArg; break; |
177 | case 'c': k = ConversionSpecifier::cArg; break; |
178 | case 'C': k = ConversionSpecifier::CArg; break; |
179 | case 'S': k = ConversionSpecifier::SArg; break; |
180 | case '[': k = ConversionSpecifier::ScanListArg; break; |
181 | case 'u': k = ConversionSpecifier::uArg; break; |
182 | case 'x': k = ConversionSpecifier::xArg; break; |
183 | case 'o': k = ConversionSpecifier::oArg; break; |
184 | case 's': k = ConversionSpecifier::sArg; break; |
185 | case 'p': k = ConversionSpecifier::pArg; break; |
186 | // Apple extensions |
187 | // Apple-specific |
188 | case 'D': |
189 | if (Target.getTriple().isOSDarwin()) |
190 | k = ConversionSpecifier::DArg; |
191 | break; |
192 | case 'O': |
193 | if (Target.getTriple().isOSDarwin()) |
194 | k = ConversionSpecifier::OArg; |
195 | break; |
196 | case 'U': |
197 | if (Target.getTriple().isOSDarwin()) |
198 | k = ConversionSpecifier::UArg; |
199 | break; |
200 | } |
201 | ScanfConversionSpecifier CS(conversionPosition, k); |
202 | if (k == ScanfConversionSpecifier::ScanListArg) { |
203 | if (ParseScanList(H, CS, Beg&: I, E)) |
204 | return true; |
205 | } |
206 | FS.setConversionSpecifier(CS); |
207 | if (CS.consumesDataArgument() && !FS.getSuppressAssignment() |
208 | && !FS.usesPositionalArg()) |
209 | FS.setArgIndex(argIndex++); |
210 | |
211 | // FIXME: '%' and '*' doesn't make sense. Issue a warning. |
212 | // FIXME: 'ConsumedSoFar' and '*' doesn't make sense. |
213 | |
214 | if (k == ScanfConversionSpecifier::InvalidSpecifier) { |
215 | unsigned Len = I - Beg; |
216 | if (ParseUTF8InvalidSpecifier(SpecifierBegin: Beg, FmtStrEnd: E, Len)) { |
217 | CS.setEndScanList(Beg + Len); |
218 | FS.setConversionSpecifier(CS); |
219 | } |
220 | // Assume the conversion takes one argument. |
221 | return !H.HandleInvalidScanfConversionSpecifier(FS, startSpecifier: Beg, specifierLen: Len); |
222 | } |
223 | return ScanfSpecifierResult(Start, FS); |
224 | } |
225 | |
226 | ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const { |
227 | const ScanfConversionSpecifier &CS = getConversionSpecifier(); |
228 | |
229 | if (!CS.consumesDataArgument()) |
230 | return ArgType::Invalid(); |
231 | |
232 | switch(CS.getKind()) { |
233 | // Signed int. |
234 | case ConversionSpecifier::dArg: |
235 | case ConversionSpecifier::DArg: |
236 | case ConversionSpecifier::iArg: |
237 | switch (LM.getKind()) { |
238 | case LengthModifier::None: |
239 | return ArgType::PtrTo(A: Ctx.IntTy); |
240 | case LengthModifier::AsChar: |
241 | return ArgType::PtrTo(A: ArgType::AnyCharTy); |
242 | case LengthModifier::AsShort: |
243 | return ArgType::PtrTo(A: Ctx.ShortTy); |
244 | case LengthModifier::AsLong: |
245 | return ArgType::PtrTo(A: Ctx.LongTy); |
246 | case LengthModifier::AsLongLong: |
247 | case LengthModifier::AsQuad: |
248 | return ArgType::PtrTo(A: Ctx.LongLongTy); |
249 | case LengthModifier::AsInt64: |
250 | return ArgType::PtrTo(A: ArgType(Ctx.LongLongTy, "__int64" )); |
251 | case LengthModifier::AsIntMax: |
252 | return ArgType::PtrTo(A: ArgType(Ctx.getIntMaxType(), "intmax_t" )); |
253 | case LengthModifier::AsSizeT: |
254 | return ArgType::PtrTo(A: ArgType(Ctx.getSignedSizeType(), "ssize_t" )); |
255 | case LengthModifier::AsPtrDiff: |
256 | return ArgType::PtrTo(A: ArgType(Ctx.getPointerDiffType(), "ptrdiff_t" )); |
257 | case LengthModifier::AsLongDouble: |
258 | // GNU extension. |
259 | return ArgType::PtrTo(A: Ctx.LongLongTy); |
260 | case LengthModifier::AsAllocate: |
261 | case LengthModifier::AsMAllocate: |
262 | case LengthModifier::AsInt32: |
263 | case LengthModifier::AsInt3264: |
264 | case LengthModifier::AsWide: |
265 | case LengthModifier::AsShortLong: |
266 | return ArgType::Invalid(); |
267 | } |
268 | llvm_unreachable("Unsupported LengthModifier Type" ); |
269 | |
270 | // Unsigned int. |
271 | case ConversionSpecifier::bArg: |
272 | case ConversionSpecifier::oArg: |
273 | case ConversionSpecifier::OArg: |
274 | case ConversionSpecifier::uArg: |
275 | case ConversionSpecifier::UArg: |
276 | case ConversionSpecifier::xArg: |
277 | case ConversionSpecifier::XArg: |
278 | switch (LM.getKind()) { |
279 | case LengthModifier::None: |
280 | return ArgType::PtrTo(A: Ctx.UnsignedIntTy); |
281 | case LengthModifier::AsChar: |
282 | return ArgType::PtrTo(A: Ctx.UnsignedCharTy); |
283 | case LengthModifier::AsShort: |
284 | return ArgType::PtrTo(A: Ctx.UnsignedShortTy); |
285 | case LengthModifier::AsLong: |
286 | return ArgType::PtrTo(A: Ctx.UnsignedLongTy); |
287 | case LengthModifier::AsLongLong: |
288 | case LengthModifier::AsQuad: |
289 | return ArgType::PtrTo(A: Ctx.UnsignedLongLongTy); |
290 | case LengthModifier::AsInt64: |
291 | return ArgType::PtrTo(A: ArgType(Ctx.UnsignedLongLongTy, "unsigned __int64" )); |
292 | case LengthModifier::AsIntMax: |
293 | return ArgType::PtrTo(A: ArgType(Ctx.getUIntMaxType(), "uintmax_t" )); |
294 | case LengthModifier::AsSizeT: |
295 | return ArgType::PtrTo(A: ArgType(Ctx.getSizeType(), "size_t" )); |
296 | case LengthModifier::AsPtrDiff: |
297 | return ArgType::PtrTo( |
298 | A: ArgType(Ctx.getUnsignedPointerDiffType(), "unsigned ptrdiff_t" )); |
299 | case LengthModifier::AsLongDouble: |
300 | // GNU extension. |
301 | return ArgType::PtrTo(A: Ctx.UnsignedLongLongTy); |
302 | case LengthModifier::AsAllocate: |
303 | case LengthModifier::AsMAllocate: |
304 | case LengthModifier::AsInt32: |
305 | case LengthModifier::AsInt3264: |
306 | case LengthModifier::AsWide: |
307 | case LengthModifier::AsShortLong: |
308 | return ArgType::Invalid(); |
309 | } |
310 | llvm_unreachable("Unsupported LengthModifier Type" ); |
311 | |
312 | // Float. |
313 | case ConversionSpecifier::aArg: |
314 | case ConversionSpecifier::AArg: |
315 | case ConversionSpecifier::eArg: |
316 | case ConversionSpecifier::EArg: |
317 | case ConversionSpecifier::fArg: |
318 | case ConversionSpecifier::FArg: |
319 | case ConversionSpecifier::gArg: |
320 | case ConversionSpecifier::GArg: |
321 | switch (LM.getKind()) { |
322 | case LengthModifier::None: |
323 | return ArgType::PtrTo(A: Ctx.FloatTy); |
324 | case LengthModifier::AsLong: |
325 | return ArgType::PtrTo(A: Ctx.DoubleTy); |
326 | case LengthModifier::AsLongDouble: |
327 | return ArgType::PtrTo(A: Ctx.LongDoubleTy); |
328 | default: |
329 | return ArgType::Invalid(); |
330 | } |
331 | |
332 | // Char, string and scanlist. |
333 | case ConversionSpecifier::cArg: |
334 | case ConversionSpecifier::sArg: |
335 | case ConversionSpecifier::ScanListArg: |
336 | switch (LM.getKind()) { |
337 | case LengthModifier::None: |
338 | return ArgType::PtrTo(A: ArgType::AnyCharTy); |
339 | case LengthModifier::AsLong: |
340 | case LengthModifier::AsWide: |
341 | return ArgType::PtrTo(A: ArgType(Ctx.getWideCharType(), "wchar_t" )); |
342 | case LengthModifier::AsAllocate: |
343 | case LengthModifier::AsMAllocate: |
344 | return ArgType::PtrTo(A: ArgType::CStrTy); |
345 | case LengthModifier::AsShort: |
346 | if (Ctx.getTargetInfo().getTriple().isOSMSVCRT()) |
347 | return ArgType::PtrTo(A: ArgType::AnyCharTy); |
348 | [[fallthrough]]; |
349 | default: |
350 | return ArgType::Invalid(); |
351 | } |
352 | case ConversionSpecifier::CArg: |
353 | case ConversionSpecifier::SArg: |
354 | // FIXME: Mac OS X specific? |
355 | switch (LM.getKind()) { |
356 | case LengthModifier::None: |
357 | case LengthModifier::AsWide: |
358 | return ArgType::PtrTo(A: ArgType(Ctx.getWideCharType(), "wchar_t" )); |
359 | case LengthModifier::AsAllocate: |
360 | case LengthModifier::AsMAllocate: |
361 | return ArgType::PtrTo(A: ArgType(ArgType::WCStrTy, "wchar_t *" )); |
362 | case LengthModifier::AsShort: |
363 | if (Ctx.getTargetInfo().getTriple().isOSMSVCRT()) |
364 | return ArgType::PtrTo(A: ArgType::AnyCharTy); |
365 | [[fallthrough]]; |
366 | default: |
367 | return ArgType::Invalid(); |
368 | } |
369 | |
370 | // Pointer. |
371 | case ConversionSpecifier::pArg: |
372 | return ArgType::PtrTo(A: ArgType::CPointerTy); |
373 | |
374 | // Write-back. |
375 | case ConversionSpecifier::nArg: |
376 | switch (LM.getKind()) { |
377 | case LengthModifier::None: |
378 | return ArgType::PtrTo(A: Ctx.IntTy); |
379 | case LengthModifier::AsChar: |
380 | return ArgType::PtrTo(A: Ctx.SignedCharTy); |
381 | case LengthModifier::AsShort: |
382 | return ArgType::PtrTo(A: Ctx.ShortTy); |
383 | case LengthModifier::AsLong: |
384 | return ArgType::PtrTo(A: Ctx.LongTy); |
385 | case LengthModifier::AsLongLong: |
386 | case LengthModifier::AsQuad: |
387 | return ArgType::PtrTo(A: Ctx.LongLongTy); |
388 | case LengthModifier::AsInt64: |
389 | return ArgType::PtrTo(A: ArgType(Ctx.LongLongTy, "__int64" )); |
390 | case LengthModifier::AsIntMax: |
391 | return ArgType::PtrTo(A: ArgType(Ctx.getIntMaxType(), "intmax_t" )); |
392 | case LengthModifier::AsSizeT: |
393 | return ArgType::PtrTo(A: ArgType(Ctx.getSignedSizeType(), "ssize_t" )); |
394 | case LengthModifier::AsPtrDiff: |
395 | return ArgType::PtrTo(A: ArgType(Ctx.getPointerDiffType(), "ptrdiff_t" )); |
396 | case LengthModifier::AsLongDouble: |
397 | return ArgType(); // FIXME: Is this a known extension? |
398 | case LengthModifier::AsAllocate: |
399 | case LengthModifier::AsMAllocate: |
400 | case LengthModifier::AsInt32: |
401 | case LengthModifier::AsInt3264: |
402 | case LengthModifier::AsWide: |
403 | case LengthModifier::AsShortLong: |
404 | return ArgType::Invalid(); |
405 | } |
406 | |
407 | default: |
408 | break; |
409 | } |
410 | |
411 | return ArgType(); |
412 | } |
413 | |
414 | bool ScanfSpecifier::fixType(QualType QT, QualType RawQT, |
415 | const LangOptions &LangOpt, |
416 | ASTContext &Ctx) { |
417 | |
418 | // %n is different from other conversion specifiers; don't try to fix it. |
419 | if (CS.getKind() == ConversionSpecifier::nArg) |
420 | return false; |
421 | |
422 | if (!QT->isPointerType()) |
423 | return false; |
424 | |
425 | QualType PT = QT->getPointeeType(); |
426 | |
427 | // If it's an enum, get its underlying type. |
428 | if (const EnumType *ETy = PT->getAs<EnumType>()) { |
429 | // Don't try to fix incomplete enums. |
430 | if (!ETy->getDecl()->isComplete()) |
431 | return false; |
432 | PT = ETy->getDecl()->getIntegerType(); |
433 | } |
434 | |
435 | const BuiltinType *BT = PT->getAs<BuiltinType>(); |
436 | if (!BT) |
437 | return false; |
438 | |
439 | // Pointer to a character. |
440 | if (PT->isAnyCharacterType()) { |
441 | CS.setKind(ConversionSpecifier::sArg); |
442 | if (PT->isWideCharType()) |
443 | LM.setKind(LengthModifier::AsWideChar); |
444 | else |
445 | LM.setKind(LengthModifier::None); |
446 | |
447 | // If we know the target array length, we can use it as a field width. |
448 | if (const ConstantArrayType *CAT = Ctx.getAsConstantArrayType(T: RawQT)) { |
449 | if (CAT->getSizeModifier() == ArraySizeModifier::Normal) |
450 | FieldWidth = OptionalAmount(OptionalAmount::Constant, |
451 | CAT->getZExtSize() - 1, "" , 0, false); |
452 | } |
453 | return true; |
454 | } |
455 | |
456 | // Figure out the length modifier. |
457 | switch (BT->getKind()) { |
458 | // no modifier |
459 | case BuiltinType::UInt: |
460 | case BuiltinType::Int: |
461 | case BuiltinType::Float: |
462 | LM.setKind(LengthModifier::None); |
463 | break; |
464 | |
465 | // hh |
466 | case BuiltinType::Char_U: |
467 | case BuiltinType::UChar: |
468 | case BuiltinType::Char_S: |
469 | case BuiltinType::SChar: |
470 | LM.setKind(LengthModifier::AsChar); |
471 | break; |
472 | |
473 | // h |
474 | case BuiltinType::Short: |
475 | case BuiltinType::UShort: |
476 | LM.setKind(LengthModifier::AsShort); |
477 | break; |
478 | |
479 | // l |
480 | case BuiltinType::Long: |
481 | case BuiltinType::ULong: |
482 | case BuiltinType::Double: |
483 | LM.setKind(LengthModifier::AsLong); |
484 | break; |
485 | |
486 | // ll |
487 | case BuiltinType::LongLong: |
488 | case BuiltinType::ULongLong: |
489 | LM.setKind(LengthModifier::AsLongLong); |
490 | break; |
491 | |
492 | // L |
493 | case BuiltinType::LongDouble: |
494 | LM.setKind(LengthModifier::AsLongDouble); |
495 | break; |
496 | |
497 | // Don't know. |
498 | default: |
499 | return false; |
500 | } |
501 | |
502 | // Handle size_t, ptrdiff_t, etc. that have dedicated length modifiers in C99. |
503 | if (LangOpt.C99 || LangOpt.CPlusPlus11) |
504 | namedTypeToLengthModifier(QT: PT, LM); |
505 | |
506 | // If fixing the length modifier was enough, we are done. |
507 | if (hasValidLengthModifier(Target: Ctx.getTargetInfo(), LO: LangOpt)) { |
508 | const analyze_scanf::ArgType &AT = getArgType(Ctx); |
509 | if (AT.isValid() && AT.matchesType(C&: Ctx, argTy: QT)) |
510 | return true; |
511 | } |
512 | |
513 | // Figure out the conversion specifier. |
514 | if (PT->isRealFloatingType()) |
515 | CS.setKind(ConversionSpecifier::fArg); |
516 | else if (PT->isSignedIntegerType()) |
517 | CS.setKind(ConversionSpecifier::dArg); |
518 | else if (PT->isUnsignedIntegerType()) |
519 | CS.setKind(ConversionSpecifier::uArg); |
520 | else |
521 | llvm_unreachable("Unexpected type" ); |
522 | |
523 | return true; |
524 | } |
525 | |
526 | void ScanfSpecifier::toString(raw_ostream &os) const { |
527 | os << "%" ; |
528 | |
529 | if (usesPositionalArg()) |
530 | os << getPositionalArgIndex() << "$" ; |
531 | if (SuppressAssignment) |
532 | os << "*" ; |
533 | |
534 | FieldWidth.toString(os); |
535 | os << LM.toString(); |
536 | os << CS.toString(); |
537 | } |
538 | |
539 | bool clang::analyze_format_string::ParseScanfString(FormatStringHandler &H, |
540 | const char *I, |
541 | const char *E, |
542 | const LangOptions &LO, |
543 | const TargetInfo &Target) { |
544 | |
545 | unsigned argIndex = 0; |
546 | |
547 | // Keep looking for a format specifier until we have exhausted the string. |
548 | while (I != E) { |
549 | const ScanfSpecifierResult &FSR = ParseScanfSpecifier(H, Beg&: I, E, argIndex, |
550 | LO, Target); |
551 | // Did a fail-stop error of any kind occur when parsing the specifier? |
552 | // If so, don't do any more processing. |
553 | if (FSR.shouldStop()) |
554 | return true; |
555 | // Did we exhaust the string or encounter an error that |
556 | // we can recover from? |
557 | if (!FSR.hasValue()) |
558 | continue; |
559 | // We have a format specifier. Pass it to the callback. |
560 | if (!H.HandleScanfSpecifier(FS: FSR.getValue(), startSpecifier: FSR.getStart(), |
561 | specifierLen: I - FSR.getStart())) { |
562 | return true; |
563 | } |
564 | } |
565 | assert(I == E && "Format string not exhausted" ); |
566 | return false; |
567 | } |
568 | |