1#!/usr/bin/env python3 2""" 3# Created: Mon Jun 20 17:06:22 2022 (-0400) 4# @author: Jacob Faibussowitsch 5""" 6from __future__ import annotations 7 8import itertools 9import clang.cindex as clx # type: ignore[import] 10 11from .._typing import * 12from .._error import ParsingError, ClassidNotRegisteredError 13 14from ..classes._diag import DiagnosticManager, Diagnostic 15from ..classes._cursor import Cursor 16from ..classes._patch import Patch 17from ..classes._src_pos import SourceRange, SourceLocation 18 19from ..util._clang import ( 20 clx_scalar_type_kinds, clx_enum_type_kinds, clx_int_type_kinds, clx_conversion_cursor_kinds, 21 clx_function_type_kinds, clx_var_token_kinds, clx_math_cursor_kinds, clx_pointer_type_kinds, 22 clx_bool_type_kinds 23) 24 25# utilities for checking functions 26 27# a dummy class so that type checkers don't complain about functions not having diags attribute 28@DiagnosticManager.register( 29 ('incompatible-function', 'Verify that the correct function was used for a type'), 30 ('incompatible-type', 'Verify that a particular type matches the expected type'), 31 ('incompatible-type-petscobject', 'Verify that a symbol is a PetscObject'), 32 ('incompatible-classid', 'Verify that the given classid matches the PetscObject type'), 33 ('matching-arg-num', 'Verify that the given argument number matches') 34) 35class _CodeDiags: 36 diags: DiagnosticMap 37 38def add_function_fix_to_bad_source(linter: Linter, obj: Cursor, func_cursor: Cursor, valid_func_name: str) -> None: 39 r"""Shorthand for extracting a fix from a function cursor 40 41 Parameters 42 ---------- 43 linter : 44 the linter instance 45 obj : 46 the cursor representing the object 47 func_cursor : 48 the cursor of the parent function 49 valid_func_name : 50 the name that should be used instead 51 """ 52 call = [ 53 c for c in func_cursor.get_children() if c.type.get_pointee().kind == clx.TypeKind.FUNCTIONPROTO 54 ] 55 assert len(call) == 1 56 mess = f'Incorrect use of {func_cursor.displayname}(), use {valid_func_name}() instead:\n{Cursor.get_formatted_source_from_cursor(func_cursor, num_context=2)}' 57 linter.add_diagnostic_from_cursor( 58 obj, 59 Diagnostic( 60 Diagnostic.Kind.ERROR, _CodeDiags.diags.incompatible_function, mess, 61 func_cursor.extent.start, patch=Patch.from_cursor(call[0], valid_func_name) 62 ).add_note( 63 f'Due to {obj.get_formatted_blurb()}', location=obj.extent.start 64 ) 65 ) 66 return 67 68def convert_to_correct_PetscValidLogicalCollectiveXXX(linter: Linter, obj: Cursor, obj_type: clx.Type, func_cursor: Cursor, **kwargs) -> bool: 69 r"""Try to glean the correct PetscValidLogicalCollectiveXXX from the type. 70 71 Parameters 72 ---------- 73 linter : 74 the linter instance 75 obj : 76 the cursor for the object to check 77 obj_type : 78 the type of obj 79 func_cursor : 80 the cursor representing the parent function 81 82 Notes 83 ----- 84 Used as a failure hook in the validlogicalcollective checks. 85 """ 86 valid_func_name = '' 87 obj_type_kind = obj_type.kind 88 if obj_type_kind in clx_scalar_type_kinds: 89 if 'PetscReal' in obj.derivedtypename: 90 valid_func_name = 'PetscValidLogicalCollectiveReal' 91 elif 'PetscScalar' in obj.derivedtypename: 92 valid_func_name = 'PetscValidLogicalCollectiveScalar' 93 elif obj_type_kind in clx_enum_type_kinds: 94 valid_func_name = 'PetscValidLogicalCollectiveEnum' 95 elif obj_type_kind in clx_bool_type_kinds: 96 valid_func_name = 'PetscValidLogicalCollectiveBool' 97 elif obj_type_kind in clx_int_type_kinds: 98 if 'PetscInt' in obj.derivedtypename: 99 valid_func_name = 'PetscValidLogicalCollectiveInt' 100 elif 'PetscMPIInt' in obj.derivedtypename: 101 valid_func_name = 'PetscValidLogicalCollectiveMPIInt' 102 if valid_func_name: 103 add_function_fix_to_bad_source(linter, obj, func_cursor, valid_func_name) 104 return bool(valid_func_name) 105 106def check_is_type_x_and_not_type_y(type_x: str, type_y: str, linter: Linter, obj: Cursor, obj_type: clx.Type, func_cursor: Optional[Cursor] = None, valid_func: str = '') -> bool: 107 r"""Check that a cursor is at least some form of derived type X and not some form of type Y 108 109 Parameters 110 ---------- 111 type_x : 112 the name of type "x" 113 type_y : 114 the name that `type_x` should not be 115 linter : 116 the linter instance 117 obj : 118 the object which is of `type_x` 119 obj_type : 120 the type of `obj` 121 func_cursor : optional 122 the cursor representing the parent function 123 valid_func : optional 124 the name of the valid function name 125 126 Returns 127 ------- 128 ret : 129 True, as this routine always fixes the problem 130 131 Notes 132 ----- 133 i.e. for 134 135 myInt **********x; 136 137 you may check that 'x' is some form of 'myInt' instead of say 'PetscBool' 138 """ 139 derived_name = obj.derivedtypename 140 if type_x not in derived_name: 141 if type_y in derived_name: 142 assert func_cursor is not None 143 add_function_fix_to_bad_source(linter, obj, func_cursor, valid_func) 144 else: 145 mess = f'Incorrect use of {valid_func}(), {valid_func}() should only be used for {type_x}' 146 linter.add_diagnostic_from_cursor( 147 obj, 148 Diagnostic( 149 Diagnostic.Kind.ERROR, _CodeDiags.diags.incompatible_type, mess, 150 obj.extent.start 151 ) 152 ) 153 return True 154 155def check_is_PetscScalar_and_not_PetscReal(*args, **kwargs) -> bool: 156 r"""Check that a cursor is a PetscScalar and not a PetscReal 157 158 Parameters 159 ---------- 160 *args : 161 positional arguments to `check_is_type_x_and_not_type_y()` 162 **kwargs : 163 keyword arguments to `check_is_type_x_and_not_type_y()` 164 165 Returns 166 ------- 167 ret : 168 the return value of `check_is_type_x_and_not_type_y()` 169 """ 170 return check_is_type_x_and_not_type_y('PetscScalar', 'PetscReal', *args, **kwargs) 171 172def check_is_PetscReal_and_not_PetscScalar(*args, **kwargs) -> bool: 173 r"""Check that a cursor is a PetscReal and not a PetscScalar 174 175 Parameters 176 ---------- 177 *args : 178 positional arguments to `check_is_type_x_and_not_type_y()` 179 **kwargs : 180 keyword arguments to `check_is_type_x_and_not_type_y()` 181 182 Returns 183 ------- 184 ret : 185 the return value of `check_is_type_x_and_not_type_y()` 186 """ 187 return check_is_type_x_and_not_type_y('PetscReal', 'PetscScalar', *args, **kwargs) 188 189def check_is_not_type(typename: str, linter: Linter, obj: Cursor, func_cursor: Cursor, valid_func: str = '') -> bool: 190 r"""Check a cursor is not of type `typename` 191 192 Parameters 193 ---------- 194 typename : 195 the type that the cursor should not be 196 linter : 197 the linter instance 198 obj : 199 the cursor representing the object 200 func_cursor : optional 201 the cursor representing the parent function 202 valid_func : optional 203 the name of the valid function name 204 205 Returns 206 ------- 207 ret : 208 True, since this routine always fixes the problem 209 """ 210 if typename in obj.derivedtypename: 211 add_function_fix_to_bad_source(linter, obj, func_cursor, valid_func) 212 return True 213 214def check_int_is_not_PetscBool(linter: Linter, obj: Cursor, *args, **kwargs) -> bool: 215 r"""Check an int-like object is not a PetscBool 216 217 Parameters 218 ---------- 219 linter : 220 the linter instance 221 obj : 222 the cursor representing the object 223 *args : 224 additional positional arguments to `check_is_not_type()` 225 **kwargs : 226 additional keyword arguments to `check_is_not_type()` 227 228 Returns 229 ------- 230 ret : 231 the return value of `check_is_not_type()` 232 """ 233 return check_is_not_type('PetscBool', linter, obj, **kwargs) 234 235def check_MPIInt_is_not_PetscInt(linter: Linter, obj: Cursor, *args, **kwargs) -> bool: 236 r"""Check a PetscMPIInt object is not a PetscBool 237 238 Parameters 239 ---------- 240 linter : 241 the linter instance 242 obj : 243 the cursor representing the object 244 *args : 245 additional positional arguments to `check_is_not_type()` 246 **kwargs : 247 additional keyword arguments to `check_is_not_type()` 248 249 Returns 250 ------- 251 ret : 252 the return value of `check_is_not_type()` 253 """ 254 return check_is_not_type('PetscInt', linter, obj, **kwargs) 255 256def check_is_PetscBool(linter: Linter, obj: Cursor, obj_type: clx.Type, func_cursor: Optional[Cursor] = None, valid_func: str = '') -> bool: 257 r"""Check that a cursor is exactly a PetscBool 258 259 Parameters 260 ---------- 261 linter : 262 the linter instance 263 obj : 264 the cursor representing the object 265 obj_type : 266 the type of obj 267 func_cursor : optional 268 the cursor representing the parent function 269 valid_func_name : optional 270 the name that should be used instead, unused 271 """ 272 if ('PetscBool' not in obj.derivedtypename) and ('bool' not in obj.typename): 273 assert func_cursor is not None 274 func_name = func_cursor.displayname 275 mess = f'Incorrect use of {func_name}(), {func_name}() should only be used for PetscBool or bool:{Cursor.get_formatted_source_from_cursor(func_cursor, num_context=2)}' 276 linter.add_diagnostic_from_cursor( 277 obj, 278 Diagnostic( 279 Diagnostic.Kind.ERROR, _CodeDiags.diags.incompatible_function, mess, obj.extent.start 280 ) 281 ) 282 return True 283 284def check_is_petsc_object(linter: Linter, obj: Cursor) -> bool: 285 r"""Check if `obj` is a PetscObject 286 287 Parameters 288 ---------- 289 linter : 290 the linter instance 291 obj : 292 the cursor to check 293 294 Returns 295 ------- 296 is_valid : 297 True if `obj` is a valid PetscObject, False otherwise. 298 299 Raises 300 ------ 301 ClassidNotRegisteredError 302 if `obj` is a PetscObject that isn't registered in the classid_map. 303 """ 304 from ._register import classid_map 305 306 if obj.typename not in classid_map: 307 # Raise exception here since this isn't a bad source, moreso a failure of this script 308 # since it should know about all PETSc classes 309 err_message = f"{obj}\nUnknown or invalid PETSc class '{obj.derivedtypename}'. If you are introducing a new class, you must register it with this linter! See lib/petsc/bin/maint/petsclinter/petsclinter/checks/_register.py and search for 'Adding new classes' for more information\n" 310 raise ClassidNotRegisteredError(err_message) 311 petsc_object_type = obj.type.get_canonical().get_pointee() 312 # Must have a struct here, e.g. _p_Vec 313 assert petsc_object_type.kind == clx.TypeKind.RECORD, 'Symbol does not appear to be a struct!' 314 is_valid = True 315 # PetscObject is of course a valid PetscObject, no need to check its members 316 if petsc_object_type.spelling != '_p_PetscObject': 317 struct_fields = [f for f in petsc_object_type.get_fields()] 318 if len(struct_fields) >= 2: 319 if Cursor.get_typename_from_cursor(struct_fields[0]) != '_p_PetscObject': 320 is_valid = False 321 else: 322 is_valid = False 323 324 if not is_valid: 325 obj_decl = Cursor.cast(petsc_object_type.get_declaration()) 326 typename = obj_decl.typename 327 diag = Diagnostic( 328 Diagnostic.Kind.ERROR, _CodeDiags.diags.incompatible_type_petscobject, 329 f'Invalid type \'{typename}\', expected a PetscObject (or derived class):\n{obj_decl.formatted(num_before_context=1, num_after_context=2)}', 330 obj_decl.extent.start 331 ) 332 if typename.startswith('_p_'): 333 if len(struct_fields) == 0: 334 reason = 'cannot determine fields. Likely the header containing definition of the object is in a nonstandard place' 335 # raise a warning instead of an error since this is a failure of the linter 336 diag.kind = Diagnostic.Kind.WARNING 337 else: 338 reason = 'its definition is missing a PETSCHEADER as the first struct member' 339 mess = f'Type \'{typename}\' is prefixed with \'_p_\' to indicate it is a PetscObject but {reason}. Either replace \'_p_\' with \'_n_\' to indicate it is not a PetscObject or add a PETSCHEADER declaration as the first member.' 340 diag.add_note(mess).add_note( 341 f'It is ambiguous whether \'{obj.name}\' is intended to be a PetscObject.' 342 ) 343 linter.add_diagnostic_from_cursor(obj, diag) 344 return is_valid 345 346def check_matching_classid(linter: Linter, obj: Cursor, obj_classid: Cursor) -> None: 347 r"""Does the classid match the particular PETSc type 348 349 Parameters 350 ---------- 351 linter : 352 the linter instance 353 obj : 354 the cursor to check 355 obj_classid : 356 the cursor for the classid 357 """ 358 from ._register import classid_map 359 360 check_is_petsc_object(linter, obj) 361 expected = classid_map[obj.typename] 362 name = obj_classid.name 363 if name != expected: 364 mess = f"Classid doesn't match. Expected '{expected}' found '{name}':\n{obj_classid.formatted(num_context=2)}" 365 diag = Diagnostic( 366 Diagnostic.Kind.ERROR, _CodeDiags.diags.incompatible_classid, mess, obj_classid.extent.start, 367 patch=Patch.from_cursor(obj_classid, expected) 368 ).add_note( 369 f'For {obj.get_formatted_blurb()}', location=obj.extent.start 370 ) 371 linter.add_diagnostic_from_cursor(obj, diag) 372 return 373 374def _do_check_traceable_to_parent_args(obj: Cursor, parent_arg_names: tuple[str, ...], trace: list[CursorLike]) -> tuple[int, list[CursorLike]]: 375 """ 376 The actual workhorse of `check_traceable_to_parent_args()` 377 """ 378 379 potential_parents: list[CursorLike] = [] 380 if def_cursor := obj.get_definition(): 381 assert def_cursor.location != obj.location, 'Object has definition cursor, yet the cursor did not move. This should be handled!' 382 if def_cursor.kind == clx.CursorKind.VAR_DECL: 383 # found definition, so were in business 384 # Parents here is an odd choice of words since on the very same line I loop 385 # over children, but then again clangs AST has an odd semantic for parents/children 386 convert_or_dereference_cursors = clx_conversion_cursor_kinds | {clx.CursorKind.UNARY_OPERATOR} 387 for def_child in def_cursor.get_children(): 388 if def_child.kind in convert_or_dereference_cursors: 389 decl_ref_gen = ( 390 child for child in def_child.walk_preorder() if child.kind == clx.CursorKind.DECL_REF_EXPR 391 ) 392 # Weed out any self-references 393 potential_parents_gen = ( 394 parent for parent in decl_ref_gen if parent.spelling != def_cursor.spelling 395 ) 396 potential_parents.extend(potential_parents_gen) 397 elif def_cursor.kind == clx.CursorKind.FIELD_DECL: 398 # we have deduced that the original cursor may refer to a struct member 399 # reference, so we go back and see if indeed this is the case 400 for member_child in obj.get_children(): 401 if member_child.kind == clx.CursorKind.MEMBER_REF_EXPR: 402 decl_ref_gen = ( 403 c for c in member_child.walk_preorder() if c.kind == clx.CursorKind.DECL_REF_EXPR 404 ) 405 assert member_child.spelling == def_cursor.spelling, f'{member_child.spelling=}, {def_cursor.spelling=}' 406 potential_parents_gen = ( 407 parent for parent in decl_ref_gen if parent.spelling != member_child.spelling 408 ) 409 potential_parents.extend(potential_parents_gen) 410 elif obj.kind in clx_conversion_cursor_kinds: 411 curs = [ 412 Cursor(c, obj.argidx) for c in obj.walk_preorder() if c.kind == clx.CursorKind.DECL_REF_EXPR 413 ] 414 if len(curs) > 1: 415 curs = [c for c in curs if c.displayname == obj.name] 416 assert len(curs) == 1, f'Could not uniquely determine base cursor from conversion cursor {obj}' 417 obj = curs[0] 418 # for cases with casting + struct member reference: 419 # 420 # macro((type *)bar->baz, barIdx); 421 # 422 # the object "name" will (rightly) refer to 'baz', but since this is an inline 423 # "definition" it doesn't show up in get_definition(), thus we check here 424 potential_parents.append(obj) 425 426 if not potential_parents: 427 # this is the if-all-else-fails approach, first we search the __entire__ file for 428 # references to the cursor. Once we have some matches we take the earliest one 429 # as this one is in theory where the current cursor is instantiated. Then we select 430 # the best match for the possible instantiating cursor and recursively call this 431 # function. This section stops when the cursor definition is of type PARM_DECL (i.e. 432 # defined as the function parameter to the parent function). 433 all_possible_references = obj.find_cursor_references() 434 # don't care about uses of object __after__ the macro, and don't want to pick up 435 # the actual macro location either 436 all_possible_references = [ 437 r for r in all_possible_references if r.location.line < obj.location.line 438 ] 439 # we just tried those and they didn't work, also more importantly weeds out the 440 # instantiation line if this is an intermediate cursor in a recursive call to this 441 # function 442 decl_cursor_kinds = {clx.CursorKind.VAR_DECL, clx.CursorKind.FIELD_DECL} 443 arg_refs = [r for r in all_possible_references if r.kind not in decl_cursor_kinds] 444 if not len(arg_refs): 445 # it's not traceable to a function argument, so maybe its a global static variable 446 if len([r for r in all_possible_references if r.storage_class == clx.StorageClass.STATIC]): 447 # a global variable is not a function argumment, so this is unhandleable 448 raise ParsingError('PETSC_CLANG_STATIC_ANALYZER_IGNORE') 449 450 assert len(arg_refs), f'Could not determine the origin of cursor {obj}' 451 # take the first, as this is the earliest 452 first_ref = arg_refs[0] 453 tu, line = first_ref.translation_unit, first_ref.location.line 454 src_len = len(first_ref.raw()) 455 # Why the following song and dance? Because you cannot walk the AST backwards, and in 456 # the case that the current cursor is in a function call we need to access our 457 # co-arguments to the function, i.e. "adjacent" branches since they should link to (or 458 # be) in the parent functions argument list. So we have to essentially reparse this 459 # line to be able to start from the top. 460 line_start = SourceLocation.from_position(tu, line, 1) 461 line_end = SourceLocation.from_position(tu, line, src_len + 1) 462 line_range = SourceRange.from_locations(line_start, line_end).source_range 463 token_group = list(clx.TokenGroup.get_tokens(tu, line_range)) 464 function_prototype = [ 465 i for i, t in enumerate(token_group) if t.cursor.type.get_canonical().kind in clx_function_type_kinds 466 ] 467 if function_prototype: 468 if len(function_prototype) > 1: 469 # nested function calls likely from PetscCall(), so discard that 470 function_prototype = [ 471 i for i in function_prototype if not token_group[i].spelling.startswith('PetscCall') 472 ] 473 assert len(function_prototype) == 1, 'Could not determine unique function prototype from {} for provenance of {}'.format(''.join([t.spelling for t in token_group]), obj) 474 475 idx = function_prototype[0] 476 lambda_expr = lambda t: (t.spelling not in {'(', ')'}) and t.kind in clx_var_token_kinds 477 iterator = (x.cursor for x in itertools.takewhile(lambda_expr, token_group[idx + 2:])) 478 # we now have completely different cursor selected, so we recursively call this 479 # function 480 else: 481 # not a function call, must be an assignment statement, meaning we should now 482 # assert that the current obj is being assigned to 483 assert Cursor.get_name_from_cursor(token_group[0].cursor) == obj.name 484 # find the binary operator, it will contain the most comprehensive AST 485 cursor_gen = token_group[[x.spelling for x in token_group].index('=')].cursor.walk_preorder() 486 iterator = (c for c in cursor_gen if c.kind == clx.CursorKind.DECL_REF_EXPR) 487 488 alternate_cursor = (c for c in iterator if Cursor.get_name_from_cursor(c) != obj.name) 489 potential_parents.extend(alternate_cursor) 490 491 if not potential_parents: 492 raise ParsingError 493 # arguably at this point anything other than len(potential_parents) should be 1, 494 # and anything else can be considered a failure of this routine (therefore a RTE) 495 # as it should be able to detect the definition. 496 assert len(potential_parents) == 1, 'Cannot determine a unique definition cursor for object' 497 # If >1 cursor, probably a bug since we should have weeded something out 498 parent = potential_parents[0] 499 trace.append(parent) 500 par_def = parent.get_definition() 501 if par_def and par_def.kind == clx.CursorKind.PARM_DECL: 502 name = Cursor.get_name_from_cursor(parent) 503 try: 504 loc = parent_arg_names.index(name) 505 except ValueError as ve: 506 # name isn't in the parent arguments, so we raise parsing error from it 507 raise ParsingError from ve 508 else: 509 parent = Cursor(parent, obj.argidx) 510 # deeper into the rabbit hole 511 loc, trace = _do_check_traceable_to_parent_args(parent, parent_arg_names, trace) 512 return loc, trace 513 514def check_traceable_to_parent_args(obj: Cursor, parent_arg_names: tuple[str, ...]) -> tuple[int, list[CursorLike]]: 515 r"""Try and see if the cursor can be linked to parent function arguments. 516 517 Parameters 518 ---------- 519 obj : 520 the cursor to check 521 parent_arg_names : 522 the set of argument names in the parent function 523 524 Returns 525 ------- 526 idx : 527 the index into the `parent_arg_names` to which `obj` has been traced 528 trace : 529 if `obj` is not directly in `parent_arg_names`, then the breadcrumb of cursors that lead from `obj` 530 to `parent_arg_names[idx]` 531 532 Raises 533 ------ 534 ParsingError 535 if `obj` cannot be traced back to `parent_arg_names` 536 537 Notes 538 ----- 539 Traces `foo` back to `bar` for example for these 540 ``` 541 myFunction(barType bar) 542 ... 543 fooType foo = bar->baz; 544 macro(foo, barIdx); 545 /* or */ 546 macro(bar->baz, barIdx); 547 /* or */ 548 initFooFromBar(bar,&foo); 549 macro(foo, barIdx); 550 ``` 551 """ 552 return _do_check_traceable_to_parent_args(obj, parent_arg_names, []) 553 554def check_matching_arg_num(linter: Linter, obj: Cursor, idx_cursor: Cursor, parent_args: tuple[Cursor, ...]) -> None: 555 r"""Is the Arg # correct w.r.t. the function arguments? 556 557 Parameters 558 ---------- 559 linter : 560 the linter instance 561 obj : 562 the cursor to check 563 idx : 564 the cursor of the arg idx 565 parent_args : 566 the set of parent function argument cursors 567 """ 568 diag_name = _CodeDiags.diags.matching_arg_num 569 if idx_cursor.canonical.kind not in clx_math_cursor_kinds: 570 # sometimes it is impossible to tell if the index is correct so this is a warning not 571 # an error. For example in the case of a loop: 572 # for (i = 0; i < n; ++i) PetscAssertPointer(arr+i, i); 573 linter.add_diagnostic_from_cursor( 574 idx_cursor, Diagnostic( 575 Diagnostic.Kind.WARNING, diag_name, 576 f"Index value is of unexpected type '{idx_cursor.canonical.kind}'", obj.extent.start 577 ) 578 ) 579 return 580 try: 581 idx_num = int(idx_cursor.name) 582 except ValueError: 583 linter.add_diagnostic_from_cursor( 584 idx_cursor, Diagnostic( 585 Diagnostic.Kind.WARNING, diag_name, 586 'Potential argument mismatch, could not determine integer value', obj.extent.start 587 ) 588 ) 589 return 590 trace: list[CursorLike] = [] 591 parent_arg_names = tuple(s.name for s in parent_args) 592 try: 593 expected = parent_args[parent_arg_names.index(obj.name)] 594 except ValueError: 595 try: 596 parent_idx, trace = check_traceable_to_parent_args(obj, parent_arg_names) 597 except ParsingError as pe: 598 # If the parent arguments don't contain the symbol and we couldn't determine a 599 # definition then we cannot check for correct numbering, so we cannot do 600 # anything here but emit a warning 601 if 'PETSC_CLANG_STATIC_ANALYZER_IGNORE' in pe.args: 602 return 603 if len(parent_args): 604 parent_func = Cursor(parent_args[0].semantic_parent) 605 parent_func_name = f'{parent_func.name}()' 606 parent_func_src = parent_func.formatted() 607 else: 608 # parent function has no arguments (very likely that "obj" is a global variable) 609 parent_func_name = 'UNKNOWN FUNCTION' 610 parent_func_src = ' <could not determine parent function signature from arguments>' 611 mess = f"Cannot determine index correctness, parent function '{parent_func_name}' seemingly does not contain the object:\n{parent_func_src}" 612 linter.add_diagnostic_from_cursor( 613 obj, Diagnostic(Diagnostic.Kind.WARNING, diag_name, mess, obj.extent.start) 614 ) 615 return 616 else: 617 expected = parent_args[parent_idx] 618 exp_idx = expected.argidx 619 if idx_num != exp_idx: 620 diag = Diagnostic( 621 Diagnostic.Kind.ERROR, diag_name, 622 f"Argument number doesn't match for '{obj.name}'. Expected '{exp_idx}', found '{idx_num}':\n{idx_cursor.formatted(num_context=2)}", 623 idx_cursor.extent.start, patch=Patch.from_cursor(idx_cursor, str(exp_idx)) 624 ).add_note( 625 f'\'{obj.name}\' is traceable to argument #{exp_idx} \'{expected.name}\' in enclosing function here:\n{expected.formatted(num_context=2)}', 626 location=expected.extent.start 627 ) 628 if trace: 629 diag.add_note(f'starting with {obj.get_formatted_blurb().rstrip()}', location=obj.extent.start) 630 for cursor in trace: 631 diag.add_note( 632 f'via {Cursor.get_formatted_blurb_from_cursor(cursor).rstrip()}', location=cursor.extent.start 633 ) 634 linter.add_diagnostic_from_cursor(idx_cursor, diag) 635 return 636 637if TYPE_CHECKING: 638 MatchingTypeCallback = Callable[[Linter, Cursor, Optional[Cursor], str], bool] 639 640def check_matching_specific_type( 641 linter: Linter, obj: Cursor, expected_type_kinds: Collection[clx.TypeKind], pointer: bool, 642 unexpected_not_pointer_function: Optional[MatchingTypeCallback] = None, 643 unexpected_pointer_function: Optional[MatchingTypeCallback] = None, 644 success_function: Optional[MatchingTypeCallback] = None, 645 failure_function: Optional[MatchingTypeCallback] = None, 646 permissive: bool = False, pointer_depth: int = 1, **kwargs 647) -> None: 648 r"""Checks that obj is of a particular kind, for example char. Can optionally handle pointers too. 649 650 Parameters 651 ---------- 652 linter : 653 the linter instance 654 obj : 655 the cursor to check 656 expected_type_kinds : 657 the base `clang.cindex.TypeKind` that you want `obj` to be, e.g. clx.TypeKind.ENUM for PetscBool 658 pointer : optional 659 should `obj` be a pointer to your type? 660 unexpected_not_pointer_function : optional 661 callback for when `pointer` is True, and `obj` matches the base type but IS NOT a pointer 662 unexpected_pointer_function : optional 663 callback for when `pointer` is False`, the object matches the base type but IS a pointer 664 success_function : optional 665 callback for when `obj` matches the type and pointer specification 666 failure_function : optional 667 callback for when `obj` does NOT match the base type 668 permissive : optional 669 allow type mismatch (e.g. when checking generic functions like PetscAssertPointer() which can 670 accept anytype) 671 pointer_depth : optional 672 how many levels of pointer to remove (-1 for no limit) 673 674 Raises 675 ------ 676 RuntimeError 677 if `success_function` returns False, indicating an unhandled error 678 679 Notes 680 ----- 681 The hooks must return True or False to indicate whether they handled the particular situation. 682 This can mean either determining that the object was correct all along (and return True), or that a 683 more helpful error message was logged and/or that a fix was created. 684 685 Returning False indicates that the hook was not successful and that additional error messages should 686 be logged 687 """ 688 def always_false(*args: Any, **kwargs: Any) -> bool: 689 return False 690 691 def always_true(*args: Any, **kwargs: Any) -> bool: 692 return True 693 694 if unexpected_not_pointer_function is None: 695 unexpected_not_pointer_function = always_false 696 697 if unexpected_pointer_function is None: 698 unexpected_pointer_function = always_false 699 700 if success_function is None: 701 success_function = always_true 702 703 if failure_function is None: 704 failure_function = always_false 705 706 if pointer_depth < 0: 707 pointer_depth = 100 708 709 diag_name = _CodeDiags.diags.incompatible_type 710 obj_type = obj.canonical.type.get_canonical() 711 if pointer: 712 if obj_type.kind in expected_type_kinds and not permissive: 713 # expecting a pointer to type, but obj is already that type 714 handled = unexpected_not_pointer_function(linter, obj, obj_type, **kwargs) 715 if not handled: 716 mess = f'Object of clang type {obj_type.kind} is not a pointer. Expected pointer of one of the following types: {expected_type_kinds}' 717 linter.add_diagnostic_from_cursor( 718 obj, Diagnostic(Diagnostic.Kind.ERROR, diag_name, mess, obj.extent.start) 719 ) 720 return 721 722 # get rid of any nested pointer types 723 def cycle_type(obj_type: clx.Type, get_obj_type: Callable[[clx.Type], clx.Type]) -> clx.Type: 724 for _ in range(pointer_depth): 725 tmp_type = get_obj_type(obj_type) 726 if tmp_type == obj_type: 727 break 728 obj_type = tmp_type 729 return obj_type 730 731 if obj_type.kind == clx.TypeKind.INCOMPLETEARRAY: 732 obj_type = cycle_type(obj_type, lambda otype: otype.element_type) 733 elif obj_type.kind == clx.TypeKind.POINTER: 734 obj_type = cycle_type(obj_type, lambda otype: otype.get_pointee()) 735 else: 736 if obj_type.kind in clx_pointer_type_kinds: 737 handled = unexpected_pointer_function(linter, obj, obj_type, **kwargs) 738 if not handled: 739 mess = f'Object of clang type {obj_type.kind} is a pointer when it should not be' 740 linter.add_diagnostic_from_cursor( 741 obj, Diagnostic(Diagnostic.Kind.ERROR, diag_name, mess, obj.extent.start) 742 ) 743 return 744 745 if permissive or obj_type.kind in expected_type_kinds: 746 handled = success_function(linter, obj, obj_type, **kwargs) 747 if not handled: 748 error_message = "{}\nType checker successfully matched object of type {} to (one of) expected types:\n- {}\n\nBut user supplied on-successful-match hook '{}' returned non-truthy value '{}' indicating unhandled error!".format(obj, obj_type.kind, '\n- '.join(map(str, expected_type_kinds)), success_function, handled) 749 raise RuntimeError(error_message) 750 else: 751 handled = failure_function(linter, obj, obj_type, **kwargs) 752 if not handled: 753 mess = f'Object of clang type {obj_type.kind} is not in expected types: {expected_type_kinds}' 754 linter.add_diagnostic_from_cursor( 755 obj, Diagnostic(Diagnostic.Kind.ERROR, diag_name, mess, obj.extent.start) 756 ) 757 return 758