Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cffi/cparser.py: 57%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

676 statements  

1from . import model 

2from .commontypes import COMMON_TYPES, resolve_common_type 

3from .error import FFIError, CDefError 

4try: 

5 from . import _pycparser as pycparser 

6except ImportError: 

7 import pycparser 

8import weakref, re, sys 

9 

10try: 

11 if sys.version_info < (3,): 

12 import thread as _thread 

13 else: 

14 import _thread 

15 lock = _thread.allocate_lock() 

16except ImportError: 

17 lock = None 

18 

19def _workaround_for_static_import_finders(): 

20 # Issue #392: packaging tools like cx_Freeze can not find these 

21 # because pycparser uses exec dynamic import. This is an obscure 

22 # workaround. This function is never called. 

23 import pycparser.yacctab 

24 import pycparser.lextab 

25 

26CDEF_SOURCE_STRING = "<cdef source string>" 

27_r_comment = re.compile(r"/\*.*?\*/|//([^\n\\]|\\.)*?$", 

28 re.DOTALL | re.MULTILINE) 

29_r_define = re.compile(r"^\s*#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)" 

30 r"\b((?:[^\n\\]|\\.)*?)$", 

31 re.DOTALL | re.MULTILINE) 

32_r_line_directive = re.compile(r"^[ \t]*#[ \t]*(?:line|\d+)\b.*$", re.MULTILINE) 

33_r_partial_enum = re.compile(r"=\s*\.\.\.\s*[,}]|\.\.\.\s*\}") 

34_r_enum_dotdotdot = re.compile(r"__dotdotdot\d+__$") 

35_r_partial_array = re.compile(r"\[\s*\.\.\.\s*\]") 

36_r_words = re.compile(r"\w+|\S") 

37_parser_cache = None 

38_r_int_literal = re.compile(r"-?0?x?[0-9a-f]+[lu]*$", re.IGNORECASE) 

39_r_stdcall1 = re.compile(r"\b(__stdcall|WINAPI)\b") 

40_r_stdcall2 = re.compile(r"[(]\s*(__stdcall|WINAPI)\b") 

41_r_cdecl = re.compile(r"\b__cdecl\b") 

42_r_extern_python = re.compile(r'\bextern\s*"' 

43 r'(Python|Python\s*\+\s*C|C\s*\+\s*Python)"\s*.') 

44_r_star_const_space = re.compile( # matches "* const " 

45 r"[*]\s*((const|volatile|restrict)\b\s*)+") 

46_r_int_dotdotdot = re.compile(r"(\b(int|long|short|signed|unsigned|char)\s*)+" 

47 r"\.\.\.") 

48_r_float_dotdotdot = re.compile(r"\b(double|float)\s*\.\.\.") 

49 

50def _get_parser(): 

51 global _parser_cache 

52 if _parser_cache is None: 

53 _parser_cache = pycparser.CParser() 

54 return _parser_cache 

55 

56def _workaround_for_old_pycparser(csource): 

57 # Workaround for a pycparser issue (fixed between pycparser 2.10 and 

58 # 2.14): "char*const***" gives us a wrong syntax tree, the same as 

59 # for "char***(*const)". This means we can't tell the difference 

60 # afterwards. But "char(*const(***))" gives us the right syntax 

61 # tree. The issue only occurs if there are several stars in 

62 # sequence with no parenthesis in between, just possibly qualifiers. 

63 # Attempt to fix it by adding some parentheses in the source: each 

64 # time we see "* const" or "* const *", we add an opening 

65 # parenthesis before each star---the hard part is figuring out where 

66 # to close them. 

67 parts = [] 

68 while True: 

69 match = _r_star_const_space.search(csource) 

70 if not match: 

71 break 

72 #print repr(''.join(parts)+csource), '=>', 

73 parts.append(csource[:match.start()]) 

74 parts.append('('); closing = ')' 

75 parts.append(match.group()) # e.g. "* const " 

76 endpos = match.end() 

77 if csource.startswith('*', endpos): 

78 parts.append('('); closing += ')' 

79 level = 0 

80 i = endpos 

81 while i < len(csource): 

82 c = csource[i] 

83 if c == '(': 

84 level += 1 

85 elif c == ')': 

86 if level == 0: 

87 break 

88 level -= 1 

89 elif c in ',;=': 

90 if level == 0: 

91 break 

92 i += 1 

93 csource = csource[endpos:i] + closing + csource[i:] 

94 #print repr(''.join(parts)+csource) 

95 parts.append(csource) 

96 return ''.join(parts) 

97 

98def _preprocess_extern_python(csource): 

99 # input: `extern "Python" int foo(int);` or 

100 # `extern "Python" { int foo(int); }` 

101 # output: 

102 # void __cffi_extern_python_start; 

103 # int foo(int); 

104 # void __cffi_extern_python_stop; 

105 # 

106 # input: `extern "Python+C" int foo(int);` 

107 # output: 

108 # void __cffi_extern_python_plus_c_start; 

109 # int foo(int); 

110 # void __cffi_extern_python_stop; 

111 parts = [] 

112 while True: 

113 match = _r_extern_python.search(csource) 

114 if not match: 

115 break 

116 endpos = match.end() - 1 

117 #print 

118 #print ''.join(parts)+csource 

119 #print '=>' 

120 parts.append(csource[:match.start()]) 

121 if 'C' in match.group(1): 

122 parts.append('void __cffi_extern_python_plus_c_start; ') 

123 else: 

124 parts.append('void __cffi_extern_python_start; ') 

125 if csource[endpos] == '{': 

126 # grouping variant 

127 closing = csource.find('}', endpos) 

128 if closing < 0: 

129 raise CDefError("'extern \"Python\" {': no '}' found") 

130 if csource.find('{', endpos + 1, closing) >= 0: 

131 raise NotImplementedError("cannot use { } inside a block " 

132 "'extern \"Python\" { ... }'") 

133 parts.append(csource[endpos+1:closing]) 

134 csource = csource[closing+1:] 

135 else: 

136 # non-grouping variant 

137 semicolon = csource.find(';', endpos) 

138 if semicolon < 0: 

139 raise CDefError("'extern \"Python\": no ';' found") 

140 parts.append(csource[endpos:semicolon+1]) 

141 csource = csource[semicolon+1:] 

142 parts.append(' void __cffi_extern_python_stop;') 

143 #print ''.join(parts)+csource 

144 #print 

145 parts.append(csource) 

146 return ''.join(parts) 

147 

148def _warn_for_string_literal(csource): 

149 if '"' not in csource: 

150 return 

151 for line in csource.splitlines(): 

152 if '"' in line and not line.lstrip().startswith('#'): 

153 import warnings 

154 warnings.warn("String literal found in cdef() or type source. " 

155 "String literals are ignored here, but you should " 

156 "remove them anyway because some character sequences " 

157 "confuse pre-parsing.") 

158 break 

159 

160def _warn_for_non_extern_non_static_global_variable(decl): 

161 if not decl.storage: 

162 import warnings 

163 warnings.warn("Global variable '%s' in cdef(): for consistency " 

164 "with C it should have a storage class specifier " 

165 "(usually 'extern')" % (decl.name,)) 

166 

167def _remove_line_directives(csource): 

168 # _r_line_directive matches whole lines, without the final \n, if they 

169 # start with '#line' with some spacing allowed, or '#NUMBER'. This 

170 # function stores them away and replaces them with exactly the string 

171 # '#line@N', where N is the index in the list 'line_directives'. 

172 line_directives = [] 

173 def replace(m): 

174 i = len(line_directives) 

175 line_directives.append(m.group()) 

176 return '#line@%d' % i 

177 csource = _r_line_directive.sub(replace, csource) 

178 return csource, line_directives 

179 

180def _put_back_line_directives(csource, line_directives): 

181 def replace(m): 

182 s = m.group() 

183 if not s.startswith('#line@'): 

184 raise AssertionError("unexpected #line directive " 

185 "(should have been processed and removed") 

186 return line_directives[int(s[6:])] 

187 return _r_line_directive.sub(replace, csource) 

188 

189def _preprocess(csource): 

190 # First, remove the lines of the form '#line N "filename"' because 

191 # the "filename" part could confuse the rest 

192 csource, line_directives = _remove_line_directives(csource) 

193 # Remove comments. NOTE: this only work because the cdef() section 

194 # should not contain any string literals (except in line directives)! 

195 def replace_keeping_newlines(m): 

196 return ' ' + m.group().count('\n') * '\n' 

197 csource = _r_comment.sub(replace_keeping_newlines, csource) 

198 # Remove the "#define FOO x" lines 

199 macros = {} 

200 for match in _r_define.finditer(csource): 

201 macroname, macrovalue = match.groups() 

202 macrovalue = macrovalue.replace('\\\n', '').strip() 

203 macros[macroname] = macrovalue 

204 csource = _r_define.sub('', csource) 

205 # 

206 if pycparser.__version__ < '2.14': 

207 csource = _workaround_for_old_pycparser(csource) 

208 # 

209 # BIG HACK: replace WINAPI or __stdcall with "volatile const". 

210 # It doesn't make sense for the return type of a function to be 

211 # "volatile volatile const", so we abuse it to detect __stdcall... 

212 # Hack number 2 is that "int(volatile *fptr)();" is not valid C 

213 # syntax, so we place the "volatile" before the opening parenthesis. 

214 csource = _r_stdcall2.sub(' volatile volatile const(', csource) 

215 csource = _r_stdcall1.sub(' volatile volatile const ', csource) 

216 csource = _r_cdecl.sub(' ', csource) 

217 # 

218 # Replace `extern "Python"` with start/end markers 

219 csource = _preprocess_extern_python(csource) 

220 # 

221 # Now there should not be any string literal left; warn if we get one 

222 _warn_for_string_literal(csource) 

223 # 

224 # Replace "[...]" with "[__dotdotdotarray__]" 

225 csource = _r_partial_array.sub('[__dotdotdotarray__]', csource) 

226 # 

227 # Replace "...}" with "__dotdotdotNUM__}". This construction should 

228 # occur only at the end of enums; at the end of structs we have "...;}" 

229 # and at the end of vararg functions "...);". Also replace "=...[,}]" 

230 # with ",__dotdotdotNUM__[,}]": this occurs in the enums too, when 

231 # giving an unknown value. 

232 matches = list(_r_partial_enum.finditer(csource)) 

233 for number, match in enumerate(reversed(matches)): 

234 p = match.start() 

235 if csource[p] == '=': 

236 p2 = csource.find('...', p, match.end()) 

237 assert p2 > p 

238 csource = '%s,__dotdotdot%d__ %s' % (csource[:p], number, 

239 csource[p2+3:]) 

240 else: 

241 assert csource[p:p+3] == '...' 

242 csource = '%s __dotdotdot%d__ %s' % (csource[:p], number, 

243 csource[p+3:]) 

244 # Replace "int ..." or "unsigned long int..." with "__dotdotdotint__" 

245 csource = _r_int_dotdotdot.sub(' __dotdotdotint__ ', csource) 

246 # Replace "float ..." or "double..." with "__dotdotdotfloat__" 

247 csource = _r_float_dotdotdot.sub(' __dotdotdotfloat__ ', csource) 

248 # Replace all remaining "..." with the same name, "__dotdotdot__", 

249 # which is declared with a typedef for the purpose of C parsing. 

250 csource = csource.replace('...', ' __dotdotdot__ ') 

251 # Finally, put back the line directives 

252 csource = _put_back_line_directives(csource, line_directives) 

253 return csource, macros 

254 

255def _common_type_names(csource): 

256 # Look in the source for what looks like usages of types from the 

257 # list of common types. A "usage" is approximated here as the 

258 # appearance of the word, minus a "definition" of the type, which 

259 # is the last word in a "typedef" statement. Approximative only 

260 # but should be fine for all the common types. 

261 look_for_words = set(COMMON_TYPES) 

262 look_for_words.add(';') 

263 look_for_words.add(',') 

264 look_for_words.add('(') 

265 look_for_words.add(')') 

266 look_for_words.add('typedef') 

267 words_used = set() 

268 is_typedef = False 

269 paren = 0 

270 previous_word = '' 

271 for word in _r_words.findall(csource): 

272 if word in look_for_words: 

273 if word == ';': 

274 if is_typedef: 

275 words_used.discard(previous_word) 

276 look_for_words.discard(previous_word) 

277 is_typedef = False 

278 elif word == 'typedef': 

279 is_typedef = True 

280 paren = 0 

281 elif word == '(': 

282 paren += 1 

283 elif word == ')': 

284 paren -= 1 

285 elif word == ',': 

286 if is_typedef and paren == 0: 

287 words_used.discard(previous_word) 

288 look_for_words.discard(previous_word) 

289 else: # word in COMMON_TYPES 

290 words_used.add(word) 

291 previous_word = word 

292 return words_used 

293 

294 

295class Parser(object): 

296 

297 def __init__(self): 

298 self._declarations = {} 

299 self._included_declarations = set() 

300 self._anonymous_counter = 0 

301 self._structnode2type = weakref.WeakKeyDictionary() 

302 self._options = {} 

303 self._int_constants = {} 

304 self._recomplete = [] 

305 self._uses_new_feature = None 

306 

307 def _parse(self, csource): 

308 csource, macros = _preprocess(csource) 

309 # XXX: for more efficiency we would need to poke into the 

310 # internals of CParser... the following registers the 

311 # typedefs, because their presence or absence influences the 

312 # parsing itself (but what they are typedef'ed to plays no role) 

313 ctn = _common_type_names(csource) 

314 typenames = [] 

315 for name in sorted(self._declarations): 

316 if name.startswith('typedef '): 

317 name = name[8:] 

318 typenames.append(name) 

319 ctn.discard(name) 

320 typenames += sorted(ctn) 

321 # 

322 csourcelines = [] 

323 csourcelines.append('# 1 "<cdef automatic initialization code>"') 

324 for typename in typenames: 

325 csourcelines.append('typedef int %s;' % typename) 

326 csourcelines.append('typedef int __dotdotdotint__, __dotdotdotfloat__,' 

327 ' __dotdotdot__;') 

328 # this forces pycparser to consider the following in the file 

329 # called <cdef source string> from line 1 

330 csourcelines.append('# 1 "%s"' % (CDEF_SOURCE_STRING,)) 

331 csourcelines.append(csource) 

332 csourcelines.append('') # see test_missing_newline_bug 

333 fullcsource = '\n'.join(csourcelines) 

334 if lock is not None: 

335 lock.acquire() # pycparser is not thread-safe... 

336 try: 

337 ast = _get_parser().parse(fullcsource) 

338 except pycparser.c_parser.ParseError as e: 

339 self.convert_pycparser_error(e, csource) 

340 finally: 

341 if lock is not None: 

342 lock.release() 

343 # csource will be used to find buggy source text 

344 return ast, macros, csource 

345 

346 def _convert_pycparser_error(self, e, csource): 

347 # xxx look for "<cdef source string>:NUM:" at the start of str(e) 

348 # and interpret that as a line number. This will not work if 

349 # the user gives explicit ``# NUM "FILE"`` directives. 

350 line = None 

351 msg = str(e) 

352 match = re.match(r"%s:(\d+):" % (CDEF_SOURCE_STRING,), msg) 

353 if match: 

354 linenum = int(match.group(1), 10) 

355 csourcelines = csource.splitlines() 

356 if 1 <= linenum <= len(csourcelines): 

357 line = csourcelines[linenum-1] 

358 return line 

359 

360 def convert_pycparser_error(self, e, csource): 

361 line = self._convert_pycparser_error(e, csource) 

362 

363 msg = str(e) 

364 if line: 

365 msg = 'cannot parse "%s"\n%s' % (line.strip(), msg) 

366 else: 

367 msg = 'parse error\n%s' % (msg,) 

368 raise CDefError(msg) 

369 

370 def parse(self, csource, override=False, packed=False, pack=None, 

371 dllexport=False): 

372 if packed: 

373 if packed != True: 

374 raise ValueError("'packed' should be False or True; use " 

375 "'pack' to give another value") 

376 if pack: 

377 raise ValueError("cannot give both 'pack' and 'packed'") 

378 pack = 1 

379 elif pack: 

380 if pack & (pack - 1): 

381 raise ValueError("'pack' must be a power of two, not %r" % 

382 (pack,)) 

383 else: 

384 pack = 0 

385 prev_options = self._options 

386 try: 

387 self._options = {'override': override, 

388 'packed': pack, 

389 'dllexport': dllexport} 

390 self._internal_parse(csource) 

391 finally: 

392 self._options = prev_options 

393 

394 def _internal_parse(self, csource): 

395 ast, macros, csource = self._parse(csource) 

396 # add the macros 

397 self._process_macros(macros) 

398 # find the first "__dotdotdot__" and use that as a separator 

399 # between the repeated typedefs and the real csource 

400 iterator = iter(ast.ext) 

401 for decl in iterator: 

402 if decl.name == '__dotdotdot__': 

403 break 

404 else: 

405 assert 0 

406 current_decl = None 

407 # 

408 try: 

409 self._inside_extern_python = '__cffi_extern_python_stop' 

410 for decl in iterator: 

411 current_decl = decl 

412 if isinstance(decl, pycparser.c_ast.Decl): 

413 self._parse_decl(decl) 

414 elif isinstance(decl, pycparser.c_ast.Typedef): 

415 if not decl.name: 

416 raise CDefError("typedef does not declare any name", 

417 decl) 

418 quals = 0 

419 if (isinstance(decl.type.type, pycparser.c_ast.IdentifierType) and 

420 decl.type.type.names[-1].startswith('__dotdotdot')): 

421 realtype = self._get_unknown_type(decl) 

422 elif (isinstance(decl.type, pycparser.c_ast.PtrDecl) and 

423 isinstance(decl.type.type, pycparser.c_ast.TypeDecl) and 

424 isinstance(decl.type.type.type, 

425 pycparser.c_ast.IdentifierType) and 

426 decl.type.type.type.names[-1].startswith('__dotdotdot')): 

427 realtype = self._get_unknown_ptr_type(decl) 

428 else: 

429 realtype, quals = self._get_type_and_quals( 

430 decl.type, name=decl.name, partial_length_ok=True, 

431 typedef_example="*(%s *)0" % (decl.name,)) 

432 self._declare('typedef ' + decl.name, realtype, quals=quals) 

433 elif decl.__class__.__name__ == 'Pragma': 

434 # skip pragma, only in pycparser 2.15 

435 import warnings 

436 warnings.warn( 

437 "#pragma in cdef() are entirely ignored. " 

438 "They should be removed for now, otherwise your " 

439 "code might behave differently in a future version " 

440 "of CFFI if #pragma support gets added. Note that " 

441 "'#pragma pack' needs to be replaced with the " 

442 "'packed' keyword argument to cdef().") 

443 else: 

444 raise CDefError("unexpected <%s>: this construct is valid " 

445 "C but not valid in cdef()" % 

446 decl.__class__.__name__, decl) 

447 except CDefError as e: 

448 if len(e.args) == 1: 

449 e.args = e.args + (current_decl,) 

450 raise 

451 except FFIError as e: 

452 msg = self._convert_pycparser_error(e, csource) 

453 if msg: 

454 e.args = (e.args[0] + "\n *** Err: %s" % msg,) 

455 raise 

456 

457 def _add_constants(self, key, val): 

458 if key in self._int_constants: 

459 if self._int_constants[key] == val: 

460 return # ignore identical double declarations 

461 raise FFIError( 

462 "multiple declarations of constant: %s" % (key,)) 

463 self._int_constants[key] = val 

464 

465 def _add_integer_constant(self, name, int_str): 

466 int_str = int_str.lower().rstrip("ul") 

467 neg = int_str.startswith('-') 

468 if neg: 

469 int_str = int_str[1:] 

470 # "010" is not valid oct in py3 

471 if (int_str.startswith("0") and int_str != '0' 

472 and not int_str.startswith("0x")): 

473 int_str = "0o" + int_str[1:] 

474 pyvalue = int(int_str, 0) 

475 if neg: 

476 pyvalue = -pyvalue 

477 self._add_constants(name, pyvalue) 

478 self._declare('macro ' + name, pyvalue) 

479 

480 def _process_macros(self, macros): 

481 for key, value in macros.items(): 

482 value = value.strip() 

483 if _r_int_literal.match(value): 

484 self._add_integer_constant(key, value) 

485 elif value == '...': 

486 self._declare('macro ' + key, value) 

487 else: 

488 raise CDefError( 

489 'only supports one of the following syntax:\n' 

490 ' #define %s ... (literally dot-dot-dot)\n' 

491 ' #define %s NUMBER (with NUMBER an integer' 

492 ' constant, decimal/hex/octal)\n' 

493 'got:\n' 

494 ' #define %s %s' 

495 % (key, key, key, value)) 

496 

497 def _declare_function(self, tp, quals, decl): 

498 tp = self._get_type_pointer(tp, quals) 

499 if self._options.get('dllexport'): 

500 tag = 'dllexport_python ' 

501 elif self._inside_extern_python == '__cffi_extern_python_start': 

502 tag = 'extern_python ' 

503 elif self._inside_extern_python == '__cffi_extern_python_plus_c_start': 

504 tag = 'extern_python_plus_c ' 

505 else: 

506 tag = 'function ' 

507 self._declare(tag + decl.name, tp) 

508 

509 def _parse_decl(self, decl): 

510 node = decl.type 

511 if isinstance(node, pycparser.c_ast.FuncDecl): 

512 tp, quals = self._get_type_and_quals(node, name=decl.name) 

513 assert isinstance(tp, model.RawFunctionType) 

514 self._declare_function(tp, quals, decl) 

515 else: 

516 if isinstance(node, pycparser.c_ast.Struct): 

517 self._get_struct_union_enum_type('struct', node) 

518 elif isinstance(node, pycparser.c_ast.Union): 

519 self._get_struct_union_enum_type('union', node) 

520 elif isinstance(node, pycparser.c_ast.Enum): 

521 self._get_struct_union_enum_type('enum', node) 

522 elif not decl.name: 

523 raise CDefError("construct does not declare any variable", 

524 decl) 

525 # 

526 if decl.name: 

527 tp, quals = self._get_type_and_quals(node, 

528 partial_length_ok=True) 

529 if tp.is_raw_function: 

530 self._declare_function(tp, quals, decl) 

531 elif (tp.is_integer_type() and 

532 hasattr(decl, 'init') and 

533 hasattr(decl.init, 'value') and 

534 _r_int_literal.match(decl.init.value)): 

535 self._add_integer_constant(decl.name, decl.init.value) 

536 elif (tp.is_integer_type() and 

537 isinstance(decl.init, pycparser.c_ast.UnaryOp) and 

538 decl.init.op == '-' and 

539 hasattr(decl.init.expr, 'value') and 

540 _r_int_literal.match(decl.init.expr.value)): 

541 self._add_integer_constant(decl.name, 

542 '-' + decl.init.expr.value) 

543 elif (tp is model.void_type and 

544 decl.name.startswith('__cffi_extern_python_')): 

545 # hack: `extern "Python"` in the C source is replaced 

546 # with "void __cffi_extern_python_start;" and 

547 # "void __cffi_extern_python_stop;" 

548 self._inside_extern_python = decl.name 

549 else: 

550 if self._inside_extern_python !='__cffi_extern_python_stop': 

551 raise CDefError( 

552 "cannot declare constants or " 

553 "variables with 'extern \"Python\"'") 

554 if (quals & model.Q_CONST) and not tp.is_array_type: 

555 self._declare('constant ' + decl.name, tp, quals=quals) 

556 else: 

557 _warn_for_non_extern_non_static_global_variable(decl) 

558 self._declare('variable ' + decl.name, tp, quals=quals) 

559 

560 def parse_type(self, cdecl): 

561 return self.parse_type_and_quals(cdecl)[0] 

562 

563 def parse_type_and_quals(self, cdecl): 

564 ast, macros = self._parse('void __dummy(\n%s\n);' % cdecl)[:2] 

565 assert not macros 

566 exprnode = ast.ext[-1].type.args.params[0] 

567 if isinstance(exprnode, pycparser.c_ast.ID): 

568 raise CDefError("unknown identifier '%s'" % (exprnode.name,)) 

569 return self._get_type_and_quals(exprnode.type) 

570 

571 def _declare(self, name, obj, included=False, quals=0): 

572 if name in self._declarations: 

573 prevobj, prevquals = self._declarations[name] 

574 if prevobj is obj and prevquals == quals: 

575 return 

576 if not self._options.get('override'): 

577 raise FFIError( 

578 "multiple declarations of %s (for interactive usage, " 

579 "try cdef(xx, override=True))" % (name,)) 

580 assert '__dotdotdot__' not in name.split() 

581 self._declarations[name] = (obj, quals) 

582 if included: 

583 self._included_declarations.add(obj) 

584 

585 def _extract_quals(self, type): 

586 quals = 0 

587 if isinstance(type, (pycparser.c_ast.TypeDecl, 

588 pycparser.c_ast.PtrDecl)): 

589 if 'const' in type.quals: 

590 quals |= model.Q_CONST 

591 if 'volatile' in type.quals: 

592 quals |= model.Q_VOLATILE 

593 if 'restrict' in type.quals: 

594 quals |= model.Q_RESTRICT 

595 return quals 

596 

597 def _get_type_pointer(self, type, quals, declname=None): 

598 if isinstance(type, model.RawFunctionType): 

599 return type.as_function_pointer() 

600 if (isinstance(type, model.StructOrUnionOrEnum) and 

601 type.name.startswith('$') and type.name[1:].isdigit() and 

602 type.forcename is None and declname is not None): 

603 return model.NamedPointerType(type, declname, quals) 

604 return model.PointerType(type, quals) 

605 

606 def _get_type_and_quals(self, typenode, name=None, partial_length_ok=False, 

607 typedef_example=None): 

608 # first, dereference typedefs, if we have it already parsed, we're good 

609 if (isinstance(typenode, pycparser.c_ast.TypeDecl) and 

610 isinstance(typenode.type, pycparser.c_ast.IdentifierType) and 

611 len(typenode.type.names) == 1 and 

612 ('typedef ' + typenode.type.names[0]) in self._declarations): 

613 tp, quals = self._declarations['typedef ' + typenode.type.names[0]] 

614 quals |= self._extract_quals(typenode) 

615 return tp, quals 

616 # 

617 if isinstance(typenode, pycparser.c_ast.ArrayDecl): 

618 # array type 

619 if typenode.dim is None: 

620 length = None 

621 else: 

622 length = self._parse_constant( 

623 typenode.dim, partial_length_ok=partial_length_ok) 

624 # a hack: in 'typedef int foo_t[...][...];', don't use '...' as 

625 # the length but use directly the C expression that would be 

626 # generated by recompiler.py. This lets the typedef be used in 

627 # many more places within recompiler.py 

628 if typedef_example is not None: 

629 if length == '...': 

630 length = '_cffi_array_len(%s)' % (typedef_example,) 

631 typedef_example = "*" + typedef_example 

632 # 

633 tp, quals = self._get_type_and_quals(typenode.type, 

634 partial_length_ok=partial_length_ok, 

635 typedef_example=typedef_example) 

636 return model.ArrayType(tp, length), quals 

637 # 

638 if isinstance(typenode, pycparser.c_ast.PtrDecl): 

639 # pointer type 

640 itemtype, itemquals = self._get_type_and_quals(typenode.type) 

641 tp = self._get_type_pointer(itemtype, itemquals, declname=name) 

642 quals = self._extract_quals(typenode) 

643 return tp, quals 

644 # 

645 if isinstance(typenode, pycparser.c_ast.TypeDecl): 

646 quals = self._extract_quals(typenode) 

647 type = typenode.type 

648 if isinstance(type, pycparser.c_ast.IdentifierType): 

649 # assume a primitive type. get it from .names, but reduce 

650 # synonyms to a single chosen combination 

651 names = list(type.names) 

652 if names != ['signed', 'char']: # keep this unmodified 

653 prefixes = {} 

654 while names: 

655 name = names[0] 

656 if name in ('short', 'long', 'signed', 'unsigned'): 

657 prefixes[name] = prefixes.get(name, 0) + 1 

658 del names[0] 

659 else: 

660 break 

661 # ignore the 'signed' prefix below, and reorder the others 

662 newnames = [] 

663 for prefix in ('unsigned', 'short', 'long'): 

664 for i in range(prefixes.get(prefix, 0)): 

665 newnames.append(prefix) 

666 if not names: 

667 names = ['int'] # implicitly 

668 if names == ['int']: # but kill it if 'short' or 'long' 

669 if 'short' in prefixes or 'long' in prefixes: 

670 names = [] 

671 names = newnames + names 

672 ident = ' '.join(names) 

673 if ident == 'void': 

674 return model.void_type, quals 

675 if ident == '__dotdotdot__': 

676 raise FFIError(':%d: bad usage of "..."' % 

677 typenode.coord.line) 

678 tp0, quals0 = resolve_common_type(self, ident) 

679 return tp0, (quals | quals0) 

680 # 

681 if isinstance(type, pycparser.c_ast.Struct): 

682 # 'struct foobar' 

683 tp = self._get_struct_union_enum_type('struct', type, name) 

684 return tp, quals 

685 # 

686 if isinstance(type, pycparser.c_ast.Union): 

687 # 'union foobar' 

688 tp = self._get_struct_union_enum_type('union', type, name) 

689 return tp, quals 

690 # 

691 if isinstance(type, pycparser.c_ast.Enum): 

692 # 'enum foobar' 

693 tp = self._get_struct_union_enum_type('enum', type, name) 

694 return tp, quals 

695 # 

696 if isinstance(typenode, pycparser.c_ast.FuncDecl): 

697 # a function type 

698 return self._parse_function_type(typenode, name), 0 

699 # 

700 # nested anonymous structs or unions end up here 

701 if isinstance(typenode, pycparser.c_ast.Struct): 

702 return self._get_struct_union_enum_type('struct', typenode, name, 

703 nested=True), 0 

704 if isinstance(typenode, pycparser.c_ast.Union): 

705 return self._get_struct_union_enum_type('union', typenode, name, 

706 nested=True), 0 

707 # 

708 raise FFIError(":%d: bad or unsupported type declaration" % 

709 typenode.coord.line) 

710 

711 def _parse_function_type(self, typenode, funcname=None): 

712 params = list(getattr(typenode.args, 'params', [])) 

713 for i, arg in enumerate(params): 

714 if not hasattr(arg, 'type'): 

715 raise CDefError("%s arg %d: unknown type '%s'" 

716 " (if you meant to use the old C syntax of giving" 

717 " untyped arguments, it is not supported)" 

718 % (funcname or 'in expression', i + 1, 

719 getattr(arg, 'name', '?'))) 

720 ellipsis = ( 

721 len(params) > 0 and 

722 isinstance(params[-1].type, pycparser.c_ast.TypeDecl) and 

723 isinstance(params[-1].type.type, 

724 pycparser.c_ast.IdentifierType) and 

725 params[-1].type.type.names == ['__dotdotdot__']) 

726 if ellipsis: 

727 params.pop() 

728 if not params: 

729 raise CDefError( 

730 "%s: a function with only '(...)' as argument" 

731 " is not correct C" % (funcname or 'in expression')) 

732 args = [self._as_func_arg(*self._get_type_and_quals(argdeclnode.type)) 

733 for argdeclnode in params] 

734 if not ellipsis and args == [model.void_type]: 

735 args = [] 

736 result, quals = self._get_type_and_quals(typenode.type) 

737 # the 'quals' on the result type are ignored. HACK: we absure them 

738 # to detect __stdcall functions: we textually replace "__stdcall" 

739 # with "volatile volatile const" above. 

740 abi = None 

741 if hasattr(typenode.type, 'quals'): # else, probable syntax error anyway 

742 if typenode.type.quals[-3:] == ['volatile', 'volatile', 'const']: 

743 abi = '__stdcall' 

744 return model.RawFunctionType(tuple(args), result, ellipsis, abi) 

745 

746 def _as_func_arg(self, type, quals): 

747 if isinstance(type, model.ArrayType): 

748 return model.PointerType(type.item, quals) 

749 elif isinstance(type, model.RawFunctionType): 

750 return type.as_function_pointer() 

751 else: 

752 return type 

753 

754 def _get_struct_union_enum_type(self, kind, type, name=None, nested=False): 

755 # First, a level of caching on the exact 'type' node of the AST. 

756 # This is obscure, but needed because pycparser "unrolls" declarations 

757 # such as "typedef struct { } foo_t, *foo_p" and we end up with 

758 # an AST that is not a tree, but a DAG, with the "type" node of the 

759 # two branches foo_t and foo_p of the trees being the same node. 

760 # It's a bit silly but detecting "DAG-ness" in the AST tree seems 

761 # to be the only way to distinguish this case from two independent 

762 # structs. See test_struct_with_two_usages. 

763 try: 

764 return self._structnode2type[type] 

765 except KeyError: 

766 pass 

767 # 

768 # Note that this must handle parsing "struct foo" any number of 

769 # times and always return the same StructType object. Additionally, 

770 # one of these times (not necessarily the first), the fields of 

771 # the struct can be specified with "struct foo { ...fields... }". 

772 # If no name is given, then we have to create a new anonymous struct 

773 # with no caching; in this case, the fields are either specified 

774 # right now or never. 

775 # 

776 force_name = name 

777 name = type.name 

778 # 

779 # get the type or create it if needed 

780 if name is None: 

781 # 'force_name' is used to guess a more readable name for 

782 # anonymous structs, for the common case "typedef struct { } foo". 

783 if force_name is not None: 

784 explicit_name = '$%s' % force_name 

785 else: 

786 self._anonymous_counter += 1 

787 explicit_name = '$%d' % self._anonymous_counter 

788 tp = None 

789 else: 

790 explicit_name = name 

791 key = '%s %s' % (kind, name) 

792 tp, _ = self._declarations.get(key, (None, None)) 

793 # 

794 if tp is None: 

795 if kind == 'struct': 

796 tp = model.StructType(explicit_name, None, None, None) 

797 elif kind == 'union': 

798 tp = model.UnionType(explicit_name, None, None, None) 

799 elif kind == 'enum': 

800 if explicit_name == '__dotdotdot__': 

801 raise CDefError("Enums cannot be declared with ...") 

802 tp = self._build_enum_type(explicit_name, type.values) 

803 else: 

804 raise AssertionError("kind = %r" % (kind,)) 

805 if name is not None: 

806 self._declare(key, tp) 

807 else: 

808 if kind == 'enum' and type.values is not None: 

809 raise NotImplementedError( 

810 "enum %s: the '{}' declaration should appear on the first " 

811 "time the enum is mentioned, not later" % explicit_name) 

812 if not tp.forcename: 

813 tp.force_the_name(force_name) 

814 if tp.forcename and '$' in tp.name: 

815 self._declare('anonymous %s' % tp.forcename, tp) 

816 # 

817 self._structnode2type[type] = tp 

818 # 

819 # enums: done here 

820 if kind == 'enum': 

821 return tp 

822 # 

823 # is there a 'type.decls'? If yes, then this is the place in the 

824 # C sources that declare the fields. If no, then just return the 

825 # existing type, possibly still incomplete. 

826 if type.decls is None: 

827 return tp 

828 # 

829 if tp.fldnames is not None: 

830 raise CDefError("duplicate declaration of struct %s" % name) 

831 fldnames = [] 

832 fldtypes = [] 

833 fldbitsize = [] 

834 fldquals = [] 

835 for decl in type.decls: 

836 if (isinstance(decl.type, pycparser.c_ast.IdentifierType) and 

837 ''.join(decl.type.names) == '__dotdotdot__'): 

838 # XXX pycparser is inconsistent: 'names' should be a list 

839 # of strings, but is sometimes just one string. Use 

840 # str.join() as a way to cope with both. 

841 self._make_partial(tp, nested) 

842 continue 

843 if decl.bitsize is None: 

844 bitsize = -1 

845 else: 

846 bitsize = self._parse_constant(decl.bitsize) 

847 self._partial_length = False 

848 type, fqual = self._get_type_and_quals(decl.type, 

849 partial_length_ok=True) 

850 if self._partial_length: 

851 self._make_partial(tp, nested) 

852 if isinstance(type, model.StructType) and type.partial: 

853 self._make_partial(tp, nested) 

854 fldnames.append(decl.name or '') 

855 fldtypes.append(type) 

856 fldbitsize.append(bitsize) 

857 fldquals.append(fqual) 

858 tp.fldnames = tuple(fldnames) 

859 tp.fldtypes = tuple(fldtypes) 

860 tp.fldbitsize = tuple(fldbitsize) 

861 tp.fldquals = tuple(fldquals) 

862 if fldbitsize != [-1] * len(fldbitsize): 

863 if isinstance(tp, model.StructType) and tp.partial: 

864 raise NotImplementedError("%s: using both bitfields and '...;'" 

865 % (tp,)) 

866 tp.packed = self._options.get('packed') 

867 if tp.completed: # must be re-completed: it is not opaque any more 

868 tp.completed = 0 

869 self._recomplete.append(tp) 

870 return tp 

871 

872 def _make_partial(self, tp, nested): 

873 if not isinstance(tp, model.StructOrUnion): 

874 raise CDefError("%s cannot be partial" % (tp,)) 

875 if not tp.has_c_name() and not nested: 

876 raise NotImplementedError("%s is partial but has no C name" %(tp,)) 

877 tp.partial = True 

878 

879 def _parse_constant(self, exprnode, partial_length_ok=False): 

880 # for now, limited to expressions that are an immediate number 

881 # or positive/negative number 

882 if isinstance(exprnode, pycparser.c_ast.Constant): 

883 s = exprnode.value 

884 if '0' <= s[0] <= '9': 

885 s = s.rstrip('uUlL') 

886 try: 

887 if s.startswith('0'): 

888 return int(s, 8) 

889 else: 

890 return int(s, 10) 

891 except ValueError: 

892 if len(s) > 1: 

893 if s.lower()[0:2] == '0x': 

894 return int(s, 16) 

895 elif s.lower()[0:2] == '0b': 

896 return int(s, 2) 

897 raise CDefError("invalid constant %r" % (s,)) 

898 elif s[0] == "'" and s[-1] == "'" and ( 

899 len(s) == 3 or (len(s) == 4 and s[1] == "\\")): 

900 return ord(s[-2]) 

901 else: 

902 raise CDefError("invalid constant %r" % (s,)) 

903 # 

904 if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and 

905 exprnode.op == '+'): 

906 return self._parse_constant(exprnode.expr) 

907 # 

908 if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and 

909 exprnode.op == '-'): 

910 return -self._parse_constant(exprnode.expr) 

911 # load previously defined int constant 

912 if (isinstance(exprnode, pycparser.c_ast.ID) and 

913 exprnode.name in self._int_constants): 

914 return self._int_constants[exprnode.name] 

915 # 

916 if (isinstance(exprnode, pycparser.c_ast.ID) and 

917 exprnode.name == '__dotdotdotarray__'): 

918 if partial_length_ok: 

919 self._partial_length = True 

920 return '...' 

921 raise FFIError(":%d: unsupported '[...]' here, cannot derive " 

922 "the actual array length in this context" 

923 % exprnode.coord.line) 

924 # 

925 if isinstance(exprnode, pycparser.c_ast.BinaryOp): 

926 left = self._parse_constant(exprnode.left) 

927 right = self._parse_constant(exprnode.right) 

928 if exprnode.op == '+': 

929 return left + right 

930 elif exprnode.op == '-': 

931 return left - right 

932 elif exprnode.op == '*': 

933 return left * right 

934 elif exprnode.op == '/': 

935 return self._c_div(left, right) 

936 elif exprnode.op == '%': 

937 return left - self._c_div(left, right) * right 

938 elif exprnode.op == '<<': 

939 return left << right 

940 elif exprnode.op == '>>': 

941 return left >> right 

942 elif exprnode.op == '&': 

943 return left & right 

944 elif exprnode.op == '|': 

945 return left | right 

946 elif exprnode.op == '^': 

947 return left ^ right 

948 # 

949 raise FFIError(":%d: unsupported expression: expected a " 

950 "simple numeric constant" % exprnode.coord.line) 

951 

952 def _c_div(self, a, b): 

953 result = a // b 

954 if ((a < 0) ^ (b < 0)) and (a % b) != 0: 

955 result += 1 

956 return result 

957 

958 def _build_enum_type(self, explicit_name, decls): 

959 if decls is not None: 

960 partial = False 

961 enumerators = [] 

962 enumvalues = [] 

963 nextenumvalue = 0 

964 for enum in decls.enumerators: 

965 if _r_enum_dotdotdot.match(enum.name): 

966 partial = True 

967 continue 

968 if enum.value is not None: 

969 nextenumvalue = self._parse_constant(enum.value) 

970 enumerators.append(enum.name) 

971 enumvalues.append(nextenumvalue) 

972 self._add_constants(enum.name, nextenumvalue) 

973 nextenumvalue += 1 

974 enumerators = tuple(enumerators) 

975 enumvalues = tuple(enumvalues) 

976 tp = model.EnumType(explicit_name, enumerators, enumvalues) 

977 tp.partial = partial 

978 else: # opaque enum 

979 tp = model.EnumType(explicit_name, (), ()) 

980 return tp 

981 

982 def include(self, other): 

983 for name, (tp, quals) in other._declarations.items(): 

984 if name.startswith('anonymous $enum_$'): 

985 continue # fix for test_anonymous_enum_include 

986 kind = name.split(' ', 1)[0] 

987 if kind in ('struct', 'union', 'enum', 'anonymous', 'typedef'): 

988 self._declare(name, tp, included=True, quals=quals) 

989 for k, v in other._int_constants.items(): 

990 self._add_constants(k, v) 

991 

992 def _get_unknown_type(self, decl): 

993 typenames = decl.type.type.names 

994 if typenames == ['__dotdotdot__']: 

995 return model.unknown_type(decl.name) 

996 

997 if typenames == ['__dotdotdotint__']: 

998 if self._uses_new_feature is None: 

999 self._uses_new_feature = "'typedef int... %s'" % decl.name 

1000 return model.UnknownIntegerType(decl.name) 

1001 

1002 if typenames == ['__dotdotdotfloat__']: 

1003 # note: not for 'long double' so far 

1004 if self._uses_new_feature is None: 

1005 self._uses_new_feature = "'typedef float... %s'" % decl.name 

1006 return model.UnknownFloatType(decl.name) 

1007 

1008 raise FFIError(':%d: unsupported usage of "..." in typedef' 

1009 % decl.coord.line) 

1010 

1011 def _get_unknown_ptr_type(self, decl): 

1012 if decl.type.type.type.names == ['__dotdotdot__']: 

1013 return model.unknown_ptr_type(decl.name) 

1014 raise FFIError(':%d: unsupported usage of "..." in typedef' 

1015 % decl.coord.line)