| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784 |
- #!/usr/bin/env python3
- # SPDX-License-Identifier: GPL-2.0
- # Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
- #
- # pylint: disable=C0301,C0302,R0904,R0912,R0913,R0914,R0915,R0917,R1702
- """
- Classes and functions related to reading a C language source or header FILE
- and extract embedded documentation comments from it.
- """
- import sys
- import re
- from pprint import pformat
- from kdoc.kdoc_re import NestedMatch, KernRe
- from kdoc.kdoc_item import KdocItem
- #
- # Regular expressions used to parse kernel-doc markups at KernelDoc class.
- #
- # Let's declare them in lowercase outside any class to make it easier to
- # convert from the Perl script.
- #
- # As those are evaluated at the beginning, no need to cache them
- #
- # Allow whitespace at end of comment start.
- doc_start = KernRe(r'^/\*\*\s*$', cache=False)
- doc_end = KernRe(r'\*/', cache=False)
- doc_com = KernRe(r'\s*\*\s*', cache=False)
- doc_com_body = KernRe(r'\s*\* ?', cache=False)
- doc_decl = doc_com + KernRe(r'(\w+)', cache=False)
- # @params and a strictly limited set of supported section names
- # Specifically:
- # Match @word:
- # @...:
- # @{section-name}:
- # while trying to not match literal block starts like "example::"
- #
- known_section_names = 'description|context|returns?|notes?|examples?'
- known_sections = KernRe(known_section_names, flags = re.I)
- doc_sect = doc_com + \
- KernRe(r'\s*(@[.\w]+|@\.\.\.|' + known_section_names + r')\s*:([^:].*)?$',
- flags=re.I, cache=False)
- doc_content = doc_com_body + KernRe(r'(.*)', cache=False)
- doc_inline_start = KernRe(r'^\s*/\*\*\s*$', cache=False)
- doc_inline_sect = KernRe(r'\s*\*\s*(@\s*[\w][\w\.]*\s*):(.*)', cache=False)
- doc_inline_end = KernRe(r'^\s*\*/\s*$', cache=False)
- doc_inline_oneline = KernRe(r'^\s*/\*\*\s*(@\s*[\w][\w\.]*\s*):\s*(.*)\s*\*/\s*$', cache=False)
- export_symbol = KernRe(r'^\s*EXPORT_SYMBOL(_GPL)?\s*\(\s*(\w+)\s*\)\s*', cache=False)
- export_symbol_ns = KernRe(r'^\s*EXPORT_SYMBOL_NS(_GPL)?\s*\(\s*(\w+)\s*,\s*"\S+"\)\s*', cache=False)
- type_param = KernRe(r"@(\w*((\.\w+)|(->\w+))*(\.\.\.)?)", cache=False)
- #
- # Tests for the beginning of a kerneldoc block in its various forms.
- #
- doc_block = doc_com + KernRe(r'DOC:\s*(.*)?', cache=False)
- doc_begin_data = KernRe(r"^\s*\*?\s*(struct|union|enum|typedef|var)\b\s*(\w*)", cache = False)
- doc_begin_func = KernRe(str(doc_com) + # initial " * '
- r"(?:\w+\s*\*\s*)?" + # type (not captured)
- r'(?:define\s+)?' + # possible "define" (not captured)
- r'(\w+)\s*(?:\(\w*\))?\s*' + # name and optional "(...)"
- r'(?:[-:].*)?$', # description (not captured)
- cache = False)
- #
- # Here begins a long set of transformations to turn structure member prefixes
- # and macro invocations into something we can parse and generate kdoc for.
- #
- struct_args_pattern = r'([^,)]+)'
- struct_xforms = [
- # Strip attributes
- (KernRe(r"__attribute__\s*\(\([a-z0-9,_\*\s\(\)]*\)\)", flags=re.I | re.S, cache=False), ' '),
- (KernRe(r'\s*__aligned\s*\([^;]*\)', re.S), ' '),
- (KernRe(r'\s*__counted_by\s*\([^;]*\)', re.S), ' '),
- (KernRe(r'\s*__counted_by_(le|be)\s*\([^;]*\)', re.S), ' '),
- (KernRe(r'\s*__packed\s*', re.S), ' '),
- (KernRe(r'\s*CRYPTO_MINALIGN_ATTR', re.S), ' '),
- (KernRe(r'\s*__private', re.S), ' '),
- (KernRe(r'\s*__rcu', re.S), ' '),
- (KernRe(r'\s*____cacheline_aligned_in_smp', re.S), ' '),
- (KernRe(r'\s*____cacheline_aligned', re.S), ' '),
- (KernRe(r'\s*__cacheline_group_(begin|end)\([^\)]+\);'), ''),
- #
- # Unwrap struct_group macros based on this definition:
- # __struct_group(TAG, NAME, ATTRS, MEMBERS...)
- # which has variants like: struct_group(NAME, MEMBERS...)
- # Only MEMBERS arguments require documentation.
- #
- # Parsing them happens on two steps:
- #
- # 1. drop struct group arguments that aren't at MEMBERS,
- # storing them as STRUCT_GROUP(MEMBERS)
- #
- # 2. remove STRUCT_GROUP() ancillary macro.
- #
- # The original logic used to remove STRUCT_GROUP() using an
- # advanced regex:
- #
- # \bSTRUCT_GROUP(\(((?:(?>[^)(]+)|(?1))*)\))[^;]*;
- #
- # with two patterns that are incompatible with
- # Python re module, as it has:
- #
- # - a recursive pattern: (?1)
- # - an atomic grouping: (?>...)
- #
- # I tried a simpler version: but it didn't work either:
- # \bSTRUCT_GROUP\(([^\)]+)\)[^;]*;
- #
- # As it doesn't properly match the end parenthesis on some cases.
- #
- # So, a better solution was crafted: there's now a NestedMatch
- # class that ensures that delimiters after a search are properly
- # matched. So, the implementation to drop STRUCT_GROUP() will be
- # handled in separate.
- #
- (KernRe(r'\bstruct_group\s*\(([^,]*,)', re.S), r'STRUCT_GROUP('),
- (KernRe(r'\bstruct_group_attr\s*\(([^,]*,){2}', re.S), r'STRUCT_GROUP('),
- (KernRe(r'\bstruct_group_tagged\s*\(([^,]*),([^,]*),', re.S), r'struct \1 \2; STRUCT_GROUP('),
- (KernRe(r'\b__struct_group\s*\(([^,]*,){3}', re.S), r'STRUCT_GROUP('),
- #
- # Replace macros
- #
- # TODO: use NestedMatch for FOO($1, $2, ...) matches
- #
- # it is better to also move those to the NestedMatch logic,
- # to ensure that parentheses will be properly matched.
- #
- (KernRe(r'__ETHTOOL_DECLARE_LINK_MODE_MASK\s*\(([^\)]+)\)', re.S),
- r'DECLARE_BITMAP(\1, __ETHTOOL_LINK_MODE_MASK_NBITS)'),
- (KernRe(r'DECLARE_PHY_INTERFACE_MASK\s*\(([^\)]+)\)', re.S),
- r'DECLARE_BITMAP(\1, PHY_INTERFACE_MODE_MAX)'),
- (KernRe(r'DECLARE_BITMAP\s*\(' + struct_args_pattern + r',\s*' + struct_args_pattern + r'\)',
- re.S), r'unsigned long \1[BITS_TO_LONGS(\2)]'),
- (KernRe(r'DECLARE_HASHTABLE\s*\(' + struct_args_pattern + r',\s*' + struct_args_pattern + r'\)',
- re.S), r'unsigned long \1[1 << ((\2) - 1)]'),
- (KernRe(r'DECLARE_KFIFO\s*\(' + struct_args_pattern + r',\s*' + struct_args_pattern +
- r',\s*' + struct_args_pattern + r'\)', re.S), r'\2 *\1'),
- (KernRe(r'DECLARE_KFIFO_PTR\s*\(' + struct_args_pattern + r',\s*' +
- struct_args_pattern + r'\)', re.S), r'\2 *\1'),
- (KernRe(r'(?:__)?DECLARE_FLEX_ARRAY\s*\(' + struct_args_pattern + r',\s*' +
- struct_args_pattern + r'\)', re.S), r'\1 \2[]'),
- (KernRe(r'DEFINE_DMA_UNMAP_ADDR\s*\(' + struct_args_pattern + r'\)', re.S), r'dma_addr_t \1'),
- (KernRe(r'DEFINE_DMA_UNMAP_LEN\s*\(' + struct_args_pattern + r'\)', re.S), r'__u32 \1'),
- ]
- #
- # Regexes here are guaranteed to have the end delimiter matching
- # the start delimiter. Yet, right now, only one replace group
- # is allowed.
- #
- struct_nested_prefixes = [
- (re.compile(r'\bSTRUCT_GROUP\('), r'\1'),
- ]
- #
- # Transforms for function prototypes
- #
- function_xforms = [
- (KernRe(r"^static +"), ""),
- (KernRe(r"^extern +"), ""),
- (KernRe(r"^asmlinkage +"), ""),
- (KernRe(r"^inline +"), ""),
- (KernRe(r"^__inline__ +"), ""),
- (KernRe(r"^__inline +"), ""),
- (KernRe(r"^__always_inline +"), ""),
- (KernRe(r"^noinline +"), ""),
- (KernRe(r"^__FORTIFY_INLINE +"), ""),
- (KernRe(r"__init +"), ""),
- (KernRe(r"__init_or_module +"), ""),
- (KernRe(r"__exit +"), ""),
- (KernRe(r"__deprecated +"), ""),
- (KernRe(r"__flatten +"), ""),
- (KernRe(r"__meminit +"), ""),
- (KernRe(r"__must_check +"), ""),
- (KernRe(r"__weak +"), ""),
- (KernRe(r"__sched +"), ""),
- (KernRe(r"_noprof"), ""),
- (KernRe(r"__always_unused *"), ""),
- (KernRe(r"__printf\s*\(\s*\d*\s*,\s*\d*\s*\) +"), ""),
- (KernRe(r"__(?:re)?alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\) +"), ""),
- (KernRe(r"__diagnose_as\s*\(\s*\S+\s*(?:,\s*\d+\s*)*\) +"), ""),
- (KernRe(r"DECL_BUCKET_PARAMS\s*\(\s*(\S+)\s*,\s*(\S+)\s*\)"), r"\1, \2"),
- (KernRe(r"__attribute_const__ +"), ""),
- (KernRe(r"__attribute__\s*\(\((?:[\w\s]+(?:\([^)]*\))?\s*,?)+\)\)\s+"), ""),
- ]
- #
- # Ancillary functions
- #
- def apply_transforms(xforms, text):
- """
- Apply a set of transforms to a block of text.
- """
- for search, subst in xforms:
- text = search.sub(subst, text)
- return text
- multi_space = KernRe(r'\s\s+')
- def trim_whitespace(s):
- """
- A little helper to get rid of excess white space.
- """
- return multi_space.sub(' ', s.strip())
- def trim_private_members(text):
- """
- Remove ``struct``/``enum`` members that have been marked "private".
- """
- # First look for a "public:" block that ends a private region, then
- # handle the "private until the end" case.
- #
- text = KernRe(r'/\*\s*private:.*?/\*\s*public:.*?\*/', flags=re.S).sub('', text)
- text = KernRe(r'/\*\s*private:.*', flags=re.S).sub('', text)
- #
- # We needed the comments to do the above, but now we can take them out.
- #
- return KernRe(r'\s*/\*.*?\*/\s*', flags=re.S).sub('', text).strip()
- class state:
- """
- States used by the parser's state machine.
- """
- # Parser states
- NORMAL = 0 #: Normal code.
- NAME = 1 #: Looking for function name.
- DECLARATION = 2 #: We have seen a declaration which might not be done.
- BODY = 3 #: The body of the comment.
- SPECIAL_SECTION = 4 #: Doc section ending with a blank line.
- PROTO = 5 #: Scanning prototype.
- DOCBLOCK = 6 #: Documentation block.
- INLINE_NAME = 7 #: Gathering doc outside main block.
- INLINE_TEXT = 8 #: Reading the body of inline docs.
- #: Names for each parser state.
- name = [
- "NORMAL",
- "NAME",
- "DECLARATION",
- "BODY",
- "SPECIAL_SECTION",
- "PROTO",
- "DOCBLOCK",
- "INLINE_NAME",
- "INLINE_TEXT",
- ]
- SECTION_DEFAULT = "Description" #: Default section.
- class KernelEntry:
- """
- Encapsulates a Kernel documentation entry.
- """
- def __init__(self, config, fname, ln):
- self.config = config
- self.fname = fname
- self._contents = []
- self.prototype = ""
- self.warnings = []
- self.parameterlist = []
- self.parameterdescs = {}
- self.parametertypes = {}
- self.parameterdesc_start_lines = {}
- self.section_start_lines = {}
- self.sections = {}
- self.anon_struct_union = False
- self.leading_space = None
- self.fname = fname
- # State flags
- self.brcount = 0
- self.declaration_start_line = ln + 1
- #
- # Management of section contents
- #
- def add_text(self, text):
- """Add a new text to the entry contents list."""
- self._contents.append(text)
- def contents(self):
- """Returns a string with all content texts that were added."""
- return '\n'.join(self._contents) + '\n'
- # TODO: rename to emit_message after removal of kernel-doc.pl
- def emit_msg(self, ln, msg, *, warning=True):
- """Emit a message."""
- log_msg = f"{self.fname}:{ln} {msg}"
- if not warning:
- self.config.log.info(log_msg)
- return
- # Delegate warning output to output logic, as this way it
- # will report warnings/info only for symbols that are output
- self.warnings.append(log_msg)
- return
- def begin_section(self, line_no, title = SECTION_DEFAULT, dump = False):
- """
- Begin a new section.
- """
- if dump:
- self.dump_section(start_new = True)
- self.section = title
- self.new_start_line = line_no
- def dump_section(self, start_new=True):
- """
- Dumps section contents to arrays/hashes intended for that purpose.
- """
- #
- # If we have accumulated no contents in the default ("description")
- # section, don't bother.
- #
- if self.section == SECTION_DEFAULT and not self._contents:
- return
- name = self.section
- contents = self.contents()
- if type_param.match(name):
- name = type_param.group(1)
- self.parameterdescs[name] = contents
- self.parameterdesc_start_lines[name] = self.new_start_line
- self.new_start_line = 0
- else:
- if name in self.sections and self.sections[name] != "":
- # Only warn on user-specified duplicate section names
- if name != SECTION_DEFAULT:
- self.emit_msg(self.new_start_line,
- f"duplicate section name '{name}'")
- # Treat as a new paragraph - add a blank line
- self.sections[name] += '\n' + contents
- else:
- self.sections[name] = contents
- self.section_start_lines[name] = self.new_start_line
- self.new_start_line = 0
- # self.config.log.debug("Section: %s : %s", name, pformat(vars(self)))
- if start_new:
- self.section = SECTION_DEFAULT
- self._contents = []
- python_warning = False
- class KernelDoc:
- """
- Read a C language source or header FILE and extract embedded
- documentation comments.
- """
- #: Name of context section.
- section_context = "Context"
- #: Name of return section.
- section_return = "Return"
- #: String to write when a parameter is not described.
- undescribed = "-- undescribed --"
- def __init__(self, config, fname):
- """Initialize internal variables"""
- self.fname = fname
- self.config = config
- # Initial state for the state machines
- self.state = state.NORMAL
- # Store entry currently being processed
- self.entry = None
- # Place all potential outputs into an array
- self.entries = []
- #
- # We need Python 3.7 for its "dicts remember the insertion
- # order" guarantee
- #
- global python_warning
- if (not python_warning and
- sys.version_info.major == 3 and sys.version_info.minor < 7):
- self.emit_msg(0,
- 'Python 3.7 or later is required for correct results')
- python_warning = True
- def emit_msg(self, ln, msg, *, warning=True):
- """Emit a message"""
- if self.entry:
- self.entry.emit_msg(ln, msg, warning=warning)
- return
- log_msg = f"{self.fname}:{ln} {msg}"
- if warning:
- self.config.log.warning(log_msg)
- else:
- self.config.log.info(log_msg)
- def dump_section(self, start_new=True):
- """
- Dump section contents to arrays/hashes intended for that purpose.
- """
- if self.entry:
- self.entry.dump_section(start_new)
- # TODO: rename it to store_declaration after removal of kernel-doc.pl
- def output_declaration(self, dtype, name, **args):
- """
- Store the entry into an entry array.
- The actual output and output filters will be handled elsewhere.
- """
- item = KdocItem(name, self.fname, dtype,
- self.entry.declaration_start_line, **args)
- item.warnings = self.entry.warnings
- # Drop empty sections
- # TODO: improve empty sections logic to emit warnings
- sections = self.entry.sections
- for section in ["Description", "Return"]:
- if section in sections and not sections[section].rstrip():
- del sections[section]
- item.set_sections(sections, self.entry.section_start_lines)
- item.set_params(self.entry.parameterlist, self.entry.parameterdescs,
- self.entry.parametertypes,
- self.entry.parameterdesc_start_lines)
- self.entries.append(item)
- self.config.log.debug("Output: %s:%s = %s", dtype, name, pformat(args))
- def emit_unused_warnings(self):
- """
- When the parser fails to produce a valid entry, it places some
- warnings under `entry.warnings` that will be discarded when resetting
- the state.
- Ensure that those warnings are not lost.
- .. note::
- Because we are calling `config.warning()` here, those
- warnings are not filtered by the `-W` parameters: they will all
- be produced even when `-Wreturn`, `-Wshort-desc`, and/or
- `-Wcontents-before-sections` are used.
- Allowing those warnings to be filtered is complex, because it
- would require storing them in a buffer and then filtering them
- during the output step of the code, depending on the
- selected symbols.
- """
- if self.entry and self.entry not in self.entries:
- for log_msg in self.entry.warnings:
- self.config.warning(log_msg)
- def reset_state(self, ln):
- """
- Ancillary routine to create a new entry. It initializes all
- variables used by the state machine.
- """
- self.emit_unused_warnings()
- self.entry = KernelEntry(self.config, self.fname, ln)
- # State flags
- self.state = state.NORMAL
- def push_parameter(self, ln, decl_type, param, dtype,
- org_arg, declaration_name):
- """
- Store parameters and their descriptions at self.entry.
- """
- if self.entry.anon_struct_union and dtype == "" and param == "}":
- return # Ignore the ending }; from anonymous struct/union
- self.entry.anon_struct_union = False
- param = KernRe(r'[\[\)].*').sub('', param, count=1)
- #
- # Look at various "anonymous type" cases.
- #
- if dtype == '':
- if param.endswith("..."):
- if len(param) > 3: # there is a name provided, use that
- param = param[:-3]
- if not self.entry.parameterdescs.get(param):
- self.entry.parameterdescs[param] = "variable arguments"
- elif (not param) or param == "void":
- param = "void"
- self.entry.parameterdescs[param] = "no arguments"
- elif param in ["struct", "union"]:
- # Handle unnamed (anonymous) union or struct
- dtype = param
- param = "{unnamed_" + param + "}"
- self.entry.parameterdescs[param] = "anonymous\n"
- self.entry.anon_struct_union = True
- # Warn if parameter has no description
- # (but ignore ones starting with # as these are not parameters
- # but inline preprocessor statements)
- if param not in self.entry.parameterdescs and not param.startswith("#"):
- self.entry.parameterdescs[param] = self.undescribed
- if "." not in param:
- if decl_type == 'function':
- dname = f"{decl_type} parameter"
- else:
- dname = f"{decl_type} member"
- self.emit_msg(ln,
- f"{dname} '{param}' not described in '{declaration_name}'")
- # Strip spaces from param so that it is one continuous string on
- # parameterlist. This fixes a problem where check_sections()
- # cannot find a parameter like "addr[6 + 2]" because it actually
- # appears as "addr[6", "+", "2]" on the parameter list.
- # However, it's better to maintain the param string unchanged for
- # output, so just weaken the string compare in check_sections()
- # to ignore "[blah" in a parameter string.
- self.entry.parameterlist.append(param)
- org_arg = KernRe(r'\s\s+').sub(' ', org_arg)
- self.entry.parametertypes[param] = org_arg
- def create_parameter_list(self, ln, decl_type, args,
- splitter, declaration_name):
- """
- Creates a list of parameters, storing them at self.entry.
- """
- # temporarily replace all commas inside function pointer definition
- arg_expr = KernRe(r'(\([^\),]+),')
- while arg_expr.search(args):
- args = arg_expr.sub(r"\1#", args)
- for arg in args.split(splitter):
- # Ignore argument attributes
- arg = KernRe(r'\sPOS0?\s').sub(' ', arg)
- # Strip leading/trailing spaces
- arg = arg.strip()
- arg = KernRe(r'\s+').sub(' ', arg, count=1)
- if arg.startswith('#'):
- # Treat preprocessor directive as a typeless variable just to fill
- # corresponding data structures "correctly". Catch it later in
- # output_* subs.
- # Treat preprocessor directive as a typeless variable
- self.push_parameter(ln, decl_type, arg, "",
- "", declaration_name)
- #
- # The pointer-to-function case.
- #
- elif KernRe(r'\(.+\)\s*\(').search(arg):
- arg = arg.replace('#', ',')
- r = KernRe(r'[^\(]+\(\*?\s*' # Everything up to "(*"
- r'([\w\[\].]*)' # Capture the name and possible [array]
- r'\s*\)') # Make sure the trailing ")" is there
- if r.match(arg):
- param = r.group(1)
- else:
- self.emit_msg(ln, f"Invalid param: {arg}")
- param = arg
- dtype = arg.replace(param, '')
- self.push_parameter(ln, decl_type, param, dtype, arg, declaration_name)
- #
- # The array-of-pointers case. Dig the parameter name out from the middle
- # of the declaration.
- #
- elif KernRe(r'\(.+\)\s*\[').search(arg):
- r = KernRe(r'[^\(]+\(\s*\*\s*' # Up to "(" and maybe "*"
- r'([\w.]*?)' # The actual pointer name
- r'\s*(\[\s*\w+\s*\]\s*)*\)') # The [array portion]
- if r.match(arg):
- param = r.group(1)
- else:
- self.emit_msg(ln, f"Invalid param: {arg}")
- param = arg
- dtype = arg.replace(param, '')
- self.push_parameter(ln, decl_type, param, dtype, arg, declaration_name)
- elif arg:
- #
- # Clean up extraneous spaces and split the string at commas; the first
- # element of the resulting list will also include the type information.
- #
- arg = KernRe(r'\s*:\s*').sub(":", arg)
- arg = KernRe(r'\s*\[').sub('[', arg)
- args = KernRe(r'\s*,\s*').split(arg)
- args[0] = re.sub(r'(\*+)\s*', r' \1', args[0])
- #
- # args[0] has a string of "type a". If "a" includes an [array]
- # declaration, we want to not be fooled by any white space inside
- # the brackets, so detect and handle that case specially.
- #
- r = KernRe(r'^([^[\]]*\s+)(.*)$')
- if r.match(args[0]):
- args[0] = r.group(2)
- dtype = r.group(1)
- else:
- # No space in args[0]; this seems wrong but preserves previous behavior
- dtype = ''
- bitfield_re = KernRe(r'(.*?):(\w+)')
- for param in args:
- #
- # For pointers, shift the star(s) from the variable name to the
- # type declaration.
- #
- r = KernRe(r'^(\*+)\s*(.*)')
- if r.match(param):
- self.push_parameter(ln, decl_type, r.group(2),
- f"{dtype} {r.group(1)}",
- arg, declaration_name)
- #
- # Perform a similar shift for bitfields.
- #
- elif bitfield_re.search(param):
- if dtype != "": # Skip unnamed bit-fields
- self.push_parameter(ln, decl_type, bitfield_re.group(1),
- f"{dtype}:{bitfield_re.group(2)}",
- arg, declaration_name)
- else:
- self.push_parameter(ln, decl_type, param, dtype,
- arg, declaration_name)
- def check_sections(self, ln, decl_name, decl_type):
- """
- Check for errors inside sections, emitting warnings if not found
- parameters are described.
- """
- for section in self.entry.sections:
- if section not in self.entry.parameterlist and \
- not known_sections.search(section):
- if decl_type == 'function':
- dname = f"{decl_type} parameter"
- else:
- dname = f"{decl_type} member"
- self.emit_msg(ln,
- f"Excess {dname} '{section}' description in '{decl_name}'")
- def check_return_section(self, ln, declaration_name, return_type):
- """
- If the function doesn't return void, warns about the lack of a
- return description.
- """
- if not self.config.wreturn:
- return
- # Ignore an empty return type (It's a macro)
- # Ignore functions with a "void" return type (but not "void *")
- if not return_type or KernRe(r'void\s*\w*\s*$').search(return_type):
- return
- if not self.entry.sections.get("Return", None):
- self.emit_msg(ln,
- f"No description found for return value of '{declaration_name}'")
- def split_struct_proto(self, proto):
- """
- Split apart a structure prototype; returns (struct|union, name,
- members) or ``None``.
- """
- type_pattern = r'(struct|union)'
- qualifiers = [
- "__attribute__",
- "__packed",
- "__aligned",
- "____cacheline_aligned_in_smp",
- "____cacheline_aligned",
- ]
- definition_body = r'\{(.*)\}\s*' + "(?:" + '|'.join(qualifiers) + ")?"
- r = KernRe(type_pattern + r'\s+(\w+)\s*' + definition_body)
- if r.search(proto):
- return (r.group(1), r.group(2), r.group(3))
- else:
- r = KernRe(r'typedef\s+' + type_pattern + r'\s*' + definition_body + r'\s*(\w+)\s*;')
- if r.search(proto):
- return (r.group(1), r.group(3), r.group(2))
- return None
- def rewrite_struct_members(self, members):
- """
- Process ``struct``/``union`` members from the most deeply nested
- outward.
- Rewrite the members of a ``struct`` or ``union`` for easier formatting
- later on. Among other things, this function will turn a member like::
- struct { inner_members; } foo;
- into::
- struct foo; inner_members;
- """
- #
- # The trick is in the ``^{`` below - it prevents a match of an outer
- # ``struct``/``union`` until the inner one has been munged
- # (removing the ``{`` in the process).
- #
- struct_members = KernRe(r'(struct|union)' # 0: declaration type
- r'([^\{\};]+)' # 1: possible name
- r'(\{)'
- r'([^\{\}]*)' # 3: Contents of declaration
- r'(\})'
- r'([^\{\};]*)(;)') # 5: Remaining stuff after declaration
- tuples = struct_members.findall(members)
- while tuples:
- for t in tuples:
- newmember = ""
- oldmember = "".join(t) # Reconstruct the original formatting
- dtype, name, lbr, content, rbr, rest, semi = t
- #
- # Pass through each field name, normalizing the form and formatting.
- #
- for s_id in rest.split(','):
- s_id = s_id.strip()
- newmember += f"{dtype} {s_id}; "
- #
- # Remove bitfield/array/pointer info, getting the bare name.
- #
- s_id = KernRe(r'[:\[].*').sub('', s_id)
- s_id = KernRe(r'^\s*\**(\S+)\s*').sub(r'\1', s_id)
- #
- # Pass through the members of this inner structure/union.
- #
- for arg in content.split(';'):
- arg = arg.strip()
- #
- # Look for (type)(*name)(args) - pointer to function
- #
- r = KernRe(r'^([^\(]+\(\*?\s*)([\w.]*)(\s*\).*)')
- if r.match(arg):
- dtype, name, extra = r.group(1), r.group(2), r.group(3)
- # Pointer-to-function
- if not s_id:
- # Anonymous struct/union
- newmember += f"{dtype}{name}{extra}; "
- else:
- newmember += f"{dtype}{s_id}.{name}{extra}; "
- #
- # Otherwise a non-function member.
- #
- else:
- #
- # Remove bitmap and array portions and spaces around commas
- #
- arg = KernRe(r':\s*\d+\s*').sub('', arg)
- arg = KernRe(r'\[.*\]').sub('', arg)
- arg = KernRe(r'\s*,\s*').sub(',', arg)
- #
- # Look for a normal decl - "type name[,name...]"
- #
- r = KernRe(r'(.*)\s+([\S+,]+)')
- if r.search(arg):
- for name in r.group(2).split(','):
- name = KernRe(r'^\s*\**(\S+)\s*').sub(r'\1', name)
- if not s_id:
- # Anonymous struct/union
- newmember += f"{r.group(1)} {name}; "
- else:
- newmember += f"{r.group(1)} {s_id}.{name}; "
- else:
- newmember += f"{arg}; "
- #
- # At the end of the s_id loop, replace the original declaration with
- # the munged version.
- #
- members = members.replace(oldmember, newmember)
- #
- # End of the tuple loop - search again and see if there are outer members
- # that now turn up.
- #
- tuples = struct_members.findall(members)
- return members
- def format_struct_decl(self, declaration):
- """
- Format the ``struct`` declaration into a standard form for inclusion
- in the resulting docs.
- """
- #
- # Insert newlines, get rid of extra spaces.
- #
- declaration = KernRe(r'([\{;])').sub(r'\1\n', declaration)
- declaration = KernRe(r'\}\s+;').sub('};', declaration)
- #
- # Format inline enums with each member on its own line.
- #
- r = KernRe(r'(enum\s+\{[^\}]+),([^\n])')
- while r.search(declaration):
- declaration = r.sub(r'\1,\n\2', declaration)
- #
- # Now go through and supply the right number of tabs
- # for each line.
- #
- def_args = declaration.split('\n')
- level = 1
- declaration = ""
- for clause in def_args:
- clause = KernRe(r'\s+').sub(' ', clause.strip(), count=1)
- if clause:
- if '}' in clause and level > 1:
- level -= 1
- if not clause.startswith('#'):
- declaration += "\t" * level
- declaration += "\t" + clause + "\n"
- if "{" in clause and "}" not in clause:
- level += 1
- return declaration
- def dump_struct(self, ln, proto):
- """
- Store an entry for a ``struct`` or ``union``
- """
- #
- # Do the basic parse to get the pieces of the declaration.
- #
- struct_parts = self.split_struct_proto(proto)
- if not struct_parts:
- self.emit_msg(ln, f"{proto} error: Cannot parse struct or union!")
- return
- decl_type, declaration_name, members = struct_parts
- if self.entry.identifier != declaration_name:
- self.emit_msg(ln, f"expecting prototype for {decl_type} {self.entry.identifier}. "
- f"Prototype was for {decl_type} {declaration_name} instead\n")
- return
- #
- # Go through the list of members applying all of our transformations.
- #
- members = trim_private_members(members)
- members = apply_transforms(struct_xforms, members)
- nested = NestedMatch()
- for search, sub in struct_nested_prefixes:
- members = nested.sub(search, sub, members)
- #
- # Deal with embedded struct and union members, and drop enums entirely.
- #
- declaration = members
- members = self.rewrite_struct_members(members)
- members = re.sub(r'(\{[^\{\}]*\})', '', members)
- #
- # Output the result and we are done.
- #
- self.create_parameter_list(ln, decl_type, members, ';',
- declaration_name)
- self.check_sections(ln, declaration_name, decl_type)
- self.output_declaration(decl_type, declaration_name,
- definition=self.format_struct_decl(declaration),
- purpose=self.entry.declaration_purpose)
- def dump_enum(self, ln, proto):
- """
- Store an ``enum`` inside self.entries array.
- """
- #
- # Strip preprocessor directives. Note that this depends on the
- # trailing semicolon we added in process_proto_type().
- #
- proto = KernRe(r'#\s*((define|ifdef|if)\s+|endif)[^;]*;', flags=re.S).sub('', proto)
- #
- # Parse out the name and members of the enum. Typedef form first.
- #
- r = KernRe(r'typedef\s+enum\s*\{(.*)\}\s*(\w*)\s*;')
- if r.search(proto):
- declaration_name = r.group(2)
- members = trim_private_members(r.group(1))
- #
- # Failing that, look for a straight enum
- #
- else:
- r = KernRe(r'enum\s+(\w*)\s*\{(.*)\}')
- if r.match(proto):
- declaration_name = r.group(1)
- members = trim_private_members(r.group(2))
- #
- # OK, this isn't going to work.
- #
- else:
- self.emit_msg(ln, f"{proto}: error: Cannot parse enum!")
- return
- #
- # Make sure we found what we were expecting.
- #
- if self.entry.identifier != declaration_name:
- if self.entry.identifier == "":
- self.emit_msg(ln,
- f"{proto}: wrong kernel-doc identifier on prototype")
- else:
- self.emit_msg(ln,
- f"expecting prototype for enum {self.entry.identifier}. "
- f"Prototype was for enum {declaration_name} instead")
- return
- if not declaration_name:
- declaration_name = "(anonymous)"
- #
- # Parse out the name of each enum member, and verify that we
- # have a description for it.
- #
- member_set = set()
- members = KernRe(r'\([^;)]*\)').sub('', members)
- for arg in members.split(','):
- if not arg:
- continue
- arg = KernRe(r'^\s*(\w+).*').sub(r'\1', arg)
- self.entry.parameterlist.append(arg)
- if arg not in self.entry.parameterdescs:
- self.entry.parameterdescs[arg] = self.undescribed
- self.emit_msg(ln,
- f"Enum value '{arg}' not described in enum '{declaration_name}'")
- member_set.add(arg)
- #
- # Ensure that every described member actually exists in the enum.
- #
- for k in self.entry.parameterdescs:
- if k not in member_set:
- self.emit_msg(ln,
- f"Excess enum value '@{k}' description in '{declaration_name}'")
- self.output_declaration('enum', declaration_name,
- purpose=self.entry.declaration_purpose)
- def dump_var(self, ln, proto):
- """
- Store variables that are part of kAPI.
- """
- VAR_ATTRIBS = [
- "extern",
- ]
- OPTIONAL_VAR_ATTR = "^(?:" + "|".join(VAR_ATTRIBS) + ")?"
- sub_prefixes = [
- (KernRe(r"__read_mostly"), ""),
- (KernRe(r"__ro_after_init"), ""),
- (KernRe(r"(?://.*)$"), ""),
- (KernRe(r"(?:/\*.*\*/)"), ""),
- (KernRe(r";$"), ""),
- (KernRe(r"=.*"), ""),
- ]
- #
- # Store the full prototype before modifying it
- #
- full_proto = proto
- declaration_name = None
- #
- # Handle macro definitions
- #
- macro_prefixes = [
- KernRe(r"DEFINE_[\w_]+\s*\(([\w_]+)\)"),
- ]
- for r in macro_prefixes:
- match = r.search(proto)
- if match:
- declaration_name = match.group(1)
- break
- #
- # Drop comments and macros to have a pure C prototype
- #
- if not declaration_name:
- for r, sub in sub_prefixes:
- proto = r.sub(sub, proto)
- proto = proto.rstrip()
- #
- # Variable name is at the end of the declaration
- #
- default_val = None
- r= KernRe(OPTIONAL_VAR_ATTR + r"\w.*\s+(?:\*+)?([\w_]+)\s*[\d\]\[]*\s*(=.*)?")
- if r.match(proto):
- if not declaration_name:
- declaration_name = r.group(1)
- default_val = r.group(2)
- else:
- r= KernRe(OPTIONAL_VAR_ATTR + r"(?:\w.*)?\s+(?:\*+)?(?:[\w_]+)\s*[\d\]\[]*\s*(=.*)?")
- if r.match(proto):
- default_val = r.group(1)
- if not declaration_name:
- self.emit_msg(ln,f"{proto}: can't parse variable")
- return
- if default_val:
- default_val = default_val.lstrip("=").strip()
- self.output_declaration("var", declaration_name,
- full_proto=full_proto,
- default_val=default_val,
- purpose=self.entry.declaration_purpose)
- def dump_declaration(self, ln, prototype):
- """
- Store a data declaration inside self.entries array.
- """
- if self.entry.decl_type == "enum":
- self.dump_enum(ln, prototype)
- elif self.entry.decl_type == "typedef":
- self.dump_typedef(ln, prototype)
- elif self.entry.decl_type in ["union", "struct"]:
- self.dump_struct(ln, prototype)
- elif self.entry.decl_type == "var":
- self.dump_var(ln, prototype)
- else:
- # This would be a bug
- self.emit_message(ln, f'Unknown declaration type: {self.entry.decl_type}')
- def dump_function(self, ln, prototype):
- """
- Store a function or function macro inside self.entries array.
- """
- found = func_macro = False
- return_type = ''
- decl_type = 'function'
- #
- # Apply the initial transformations.
- #
- prototype = apply_transforms(function_xforms, prototype)
- #
- # If we have a macro, remove the "#define" at the front.
- #
- new_proto = KernRe(r"^#\s*define\s+").sub("", prototype)
- if new_proto != prototype:
- prototype = new_proto
- #
- # Dispense with the simple "#define A B" case here; the key
- # is the space after the name of the symbol being defined.
- # NOTE that the seemingly misnamed "func_macro" indicates a
- # macro *without* arguments.
- #
- r = KernRe(r'^(\w+)\s+')
- if r.search(prototype):
- return_type = ''
- declaration_name = r.group(1)
- func_macro = True
- found = True
- # Yes, this truly is vile. We are looking for:
- # 1. Return type (may be nothing if we're looking at a macro)
- # 2. Function name
- # 3. Function parameters.
- #
- # All the while we have to watch out for function pointer parameters
- # (which IIRC is what the two sections are for), C types (these
- # regexps don't even start to express all the possibilities), and
- # so on.
- #
- # If you mess with these regexps, it's a good idea to check that
- # the following functions' documentation still comes out right:
- # - parport_register_device (function pointer parameters)
- # - atomic_set (macro)
- # - pci_match_device, __copy_to_user (long return type)
- name = r'\w+'
- type1 = r'(?:[\w\s]+)?'
- type2 = r'(?:[\w\s]+\*+)+'
- #
- # Attempt to match first on (args) with no internal parentheses; this
- # lets us easily filter out __acquires() and other post-args stuff. If
- # that fails, just grab the rest of the line to the last closing
- # parenthesis.
- #
- proto_args = r'\(([^\(]*|.*)\)'
- #
- # (Except for the simple macro case) attempt to split up the prototype
- # in the various ways we understand.
- #
- if not found:
- patterns = [
- rf'^()({name})\s*{proto_args}',
- rf'^({type1})\s+({name})\s*{proto_args}',
- rf'^({type2})\s*({name})\s*{proto_args}',
- ]
- for p in patterns:
- r = KernRe(p)
- if r.match(prototype):
- return_type = r.group(1)
- declaration_name = r.group(2)
- args = r.group(3)
- self.create_parameter_list(ln, decl_type, args, ',',
- declaration_name)
- found = True
- break
- #
- # Parsing done; make sure that things are as we expect.
- #
- if not found:
- self.emit_msg(ln,
- f"cannot understand function prototype: '{prototype}'")
- return
- if self.entry.identifier != declaration_name:
- self.emit_msg(ln, f"expecting prototype for {self.entry.identifier}(). "
- f"Prototype was for {declaration_name}() instead")
- return
- self.check_sections(ln, declaration_name, "function")
- self.check_return_section(ln, declaration_name, return_type)
- #
- # Store the result.
- #
- self.output_declaration(decl_type, declaration_name,
- typedef=('typedef' in return_type),
- functiontype=return_type,
- purpose=self.entry.declaration_purpose,
- func_macro=func_macro)
- def dump_typedef(self, ln, proto):
- """
- Store a ``typedef`` inside self.entries array.
- """
- #
- # We start by looking for function typedefs.
- #
- typedef_type = r'typedef((?:\s+[\w*]+\b){0,7}\s+(?:\w+\b|\*+))\s*'
- typedef_ident = r'\*?\s*(\w\S+)\s*'
- typedef_args = r'\s*\((.*)\);'
- typedef1 = KernRe(typedef_type + r'\(' + typedef_ident + r'\)' + typedef_args)
- typedef2 = KernRe(typedef_type + typedef_ident + typedef_args)
- # Parse function typedef prototypes
- for r in [typedef1, typedef2]:
- if not r.match(proto):
- continue
- return_type = r.group(1).strip()
- declaration_name = r.group(2)
- args = r.group(3)
- if self.entry.identifier != declaration_name:
- self.emit_msg(ln,
- f"expecting prototype for typedef {self.entry.identifier}. Prototype was for typedef {declaration_name} instead\n")
- return
- self.create_parameter_list(ln, 'function', args, ',', declaration_name)
- self.output_declaration('function', declaration_name,
- typedef=True,
- functiontype=return_type,
- purpose=self.entry.declaration_purpose)
- return
- #
- # Not a function, try to parse a simple typedef.
- #
- r = KernRe(r'typedef.*\s+(\w+)\s*;')
- if r.match(proto):
- declaration_name = r.group(1)
- if self.entry.identifier != declaration_name:
- self.emit_msg(ln,
- f"expecting prototype for typedef {self.entry.identifier}. Prototype was for typedef {declaration_name} instead\n")
- return
- self.output_declaration('typedef', declaration_name,
- purpose=self.entry.declaration_purpose)
- return
- self.emit_msg(ln, "error: Cannot parse typedef!")
- @staticmethod
- def process_export(function_set, line):
- """
- process ``EXPORT_SYMBOL*`` tags
- This method doesn't use any variable from the class, so declare it
- with a staticmethod decorator.
- """
- # We support documenting some exported symbols with different
- # names. A horrible hack.
- suffixes = [ '_noprof' ]
- # Note: it accepts only one EXPORT_SYMBOL* per line, as having
- # multiple export lines would violate Kernel coding style.
- if export_symbol.search(line):
- symbol = export_symbol.group(2)
- elif export_symbol_ns.search(line):
- symbol = export_symbol_ns.group(2)
- else:
- return False
- #
- # Found an export, trim out any special suffixes
- #
- for suffix in suffixes:
- # Be backward compatible with Python < 3.9
- if symbol.endswith(suffix):
- symbol = symbol[:-len(suffix)]
- function_set.add(symbol)
- return True
- def process_normal(self, ln, line):
- """
- STATE_NORMAL: looking for the ``/**`` to begin everything.
- """
- if not doc_start.match(line):
- return
- # start a new entry
- self.reset_state(ln)
- # next line is always the function name
- self.state = state.NAME
- def process_name(self, ln, line):
- """
- STATE_NAME: Looking for the "name - description" line
- """
- #
- # Check for a DOC: block and handle them specially.
- #
- if doc_block.search(line):
- if not doc_block.group(1):
- self.entry.begin_section(ln, "Introduction")
- else:
- self.entry.begin_section(ln, doc_block.group(1))
- self.entry.identifier = self.entry.section
- self.state = state.DOCBLOCK
- #
- # Otherwise we're looking for a normal kerneldoc declaration line.
- #
- elif doc_decl.search(line):
- self.entry.identifier = doc_decl.group(1)
- # Test for data declaration
- if doc_begin_data.search(line):
- self.entry.decl_type = doc_begin_data.group(1)
- self.entry.identifier = doc_begin_data.group(2)
- #
- # Look for a function description
- #
- elif doc_begin_func.search(line):
- self.entry.identifier = doc_begin_func.group(1)
- self.entry.decl_type = "function"
- #
- # We struck out.
- #
- else:
- self.emit_msg(ln,
- f"This comment starts with '/**', but isn't a kernel-doc comment. Refer to Documentation/doc-guide/kernel-doc.rst\n{line}")
- self.state = state.NORMAL
- return
- #
- # OK, set up for a new kerneldoc entry.
- #
- self.state = state.BODY
- self.entry.identifier = self.entry.identifier.strip(" ")
- # if there's no @param blocks need to set up default section here
- self.entry.begin_section(ln + 1)
- #
- # Find the description portion, which *should* be there but
- # isn't always.
- # (We should be able to capture this from the previous parsing - someday)
- #
- r = KernRe("[-:](.*)")
- if r.search(line):
- self.entry.declaration_purpose = trim_whitespace(r.group(1))
- self.state = state.DECLARATION
- else:
- self.entry.declaration_purpose = ""
- if not self.entry.declaration_purpose and self.config.wshort_desc:
- self.emit_msg(ln,
- f"missing initial short description on line:\n{line}")
- if not self.entry.identifier and self.entry.decl_type != "enum":
- self.emit_msg(ln,
- f"wrong kernel-doc identifier on line:\n{line}")
- self.state = state.NORMAL
- if self.config.verbose:
- self.emit_msg(ln,
- f"Scanning doc for {self.entry.decl_type} {self.entry.identifier}",
- warning=False)
- #
- # Failed to find an identifier. Emit a warning
- #
- else:
- self.emit_msg(ln, f"Cannot find identifier on line:\n{line}")
- def is_new_section(self, ln, line):
- """
- Helper function to determine if a new section is being started.
- """
- if doc_sect.search(line):
- self.state = state.BODY
- #
- # Pick out the name of our new section, tweaking it if need be.
- #
- newsection = doc_sect.group(1)
- if newsection.lower() == 'description':
- newsection = 'Description'
- elif newsection.lower() == 'context':
- newsection = 'Context'
- self.state = state.SPECIAL_SECTION
- elif newsection.lower() in ["@return", "@returns",
- "return", "returns"]:
- newsection = "Return"
- self.state = state.SPECIAL_SECTION
- elif newsection[0] == '@':
- self.state = state.SPECIAL_SECTION
- #
- # Initialize the contents, and get the new section going.
- #
- newcontents = doc_sect.group(2)
- if not newcontents:
- newcontents = ""
- self.dump_section()
- self.entry.begin_section(ln, newsection)
- self.entry.leading_space = None
- self.entry.add_text(newcontents.lstrip())
- return True
- return False
- def is_comment_end(self, ln, line):
- """
- Helper function to detect (and effect) the end of a kerneldoc comment.
- """
- if doc_end.search(line):
- self.dump_section()
- # Look for doc_com + <text> + doc_end:
- r = KernRe(r'\s*\*\s*[a-zA-Z_0-9:.]+\*/')
- if r.match(line):
- self.emit_msg(ln, f"suspicious ending line: {line}")
- self.entry.prototype = ""
- self.entry.new_start_line = ln + 1
- self.state = state.PROTO
- return True
- return False
- def process_decl(self, ln, line):
- """
- STATE_DECLARATION: We've seen the beginning of a declaration.
- """
- if self.is_new_section(ln, line) or self.is_comment_end(ln, line):
- return
- #
- # Look for anything with the " * " line beginning.
- #
- if doc_content.search(line):
- cont = doc_content.group(1)
- #
- # A blank line means that we have moved out of the declaration
- # part of the comment (without any "special section" parameter
- # descriptions).
- #
- if cont == "":
- self.state = state.BODY
- #
- # Otherwise we have more of the declaration section to soak up.
- #
- else:
- self.entry.declaration_purpose = \
- trim_whitespace(self.entry.declaration_purpose + ' ' + cont)
- else:
- # Unknown line, ignore
- self.emit_msg(ln, f"bad line: {line}")
- def process_special(self, ln, line):
- """
- STATE_SPECIAL_SECTION: a section ending with a blank line.
- """
- #
- # If we have hit a blank line (only the " * " marker), then this
- # section is done.
- #
- if KernRe(r"\s*\*\s*$").match(line):
- self.entry.begin_section(ln, dump = True)
- self.state = state.BODY
- return
- #
- # Not a blank line, look for the other ways to end the section.
- #
- if self.is_new_section(ln, line) or self.is_comment_end(ln, line):
- return
- #
- # OK, we should have a continuation of the text for this section.
- #
- if doc_content.search(line):
- cont = doc_content.group(1)
- #
- # If the lines of text after the first in a special section have
- # leading white space, we need to trim it out or Sphinx will get
- # confused. For the second line (the None case), see what we
- # find there and remember it.
- #
- if self.entry.leading_space is None:
- r = KernRe(r'^(\s+)')
- if r.match(cont):
- self.entry.leading_space = len(r.group(1))
- else:
- self.entry.leading_space = 0
- #
- # Otherwise, before trimming any leading chars, be *sure*
- # that they are white space. We should maybe warn if this
- # isn't the case.
- #
- for i in range(0, self.entry.leading_space):
- if cont[i] != " ":
- self.entry.leading_space = i
- break
- #
- # Add the trimmed result to the section and we're done.
- #
- self.entry.add_text(cont[self.entry.leading_space:])
- else:
- # Unknown line, ignore
- self.emit_msg(ln, f"bad line: {line}")
- def process_body(self, ln, line):
- """
- STATE_BODY: the bulk of a kerneldoc comment.
- """
- if self.is_new_section(ln, line) or self.is_comment_end(ln, line):
- return
- if doc_content.search(line):
- cont = doc_content.group(1)
- self.entry.add_text(cont)
- else:
- # Unknown line, ignore
- self.emit_msg(ln, f"bad line: {line}")
- def process_inline_name(self, ln, line):
- """STATE_INLINE_NAME: beginning of docbook comments within a prototype."""
- if doc_inline_sect.search(line):
- self.entry.begin_section(ln, doc_inline_sect.group(1))
- self.entry.add_text(doc_inline_sect.group(2).lstrip())
- self.state = state.INLINE_TEXT
- elif doc_inline_end.search(line):
- self.dump_section()
- self.state = state.PROTO
- elif doc_content.search(line):
- self.emit_msg(ln, f"Incorrect use of kernel-doc format: {line}")
- self.state = state.PROTO
- # else ... ??
- def process_inline_text(self, ln, line):
- """STATE_INLINE_TEXT: docbook comments within a prototype."""
- if doc_inline_end.search(line):
- self.dump_section()
- self.state = state.PROTO
- elif doc_content.search(line):
- self.entry.add_text(doc_content.group(1))
- # else ... ??
- def syscall_munge(self, ln, proto): # pylint: disable=W0613
- """
- Handle syscall definitions.
- """
- is_void = False
- # Strip newlines/CR's
- proto = re.sub(r'[\r\n]+', ' ', proto)
- # Check if it's a SYSCALL_DEFINE0
- if 'SYSCALL_DEFINE0' in proto:
- is_void = True
- # Replace SYSCALL_DEFINE with correct return type & function name
- proto = KernRe(r'SYSCALL_DEFINE.*\(').sub('long sys_', proto)
- r = KernRe(r'long\s+(sys_.*?),')
- if r.search(proto):
- proto = KernRe(',').sub('(', proto, count=1)
- elif is_void:
- proto = KernRe(r'\)').sub('(void)', proto, count=1)
- # Now delete all of the odd-numbered commas in the proto
- # so that argument types & names don't have a comma between them
- count = 0
- length = len(proto)
- if is_void:
- length = 0 # skip the loop if is_void
- for ix in range(length):
- if proto[ix] == ',':
- count += 1
- if count % 2 == 1:
- proto = proto[:ix] + ' ' + proto[ix + 1:]
- return proto
- def tracepoint_munge(self, ln, proto):
- """
- Handle tracepoint definitions.
- """
- tracepointname = None
- tracepointargs = None
- # Match tracepoint name based on different patterns
- r = KernRe(r'TRACE_EVENT\((.*?),')
- if r.search(proto):
- tracepointname = r.group(1)
- r = KernRe(r'DEFINE_SINGLE_EVENT\((.*?),')
- if r.search(proto):
- tracepointname = r.group(1)
- r = KernRe(r'DEFINE_EVENT\((.*?),(.*?),')
- if r.search(proto):
- tracepointname = r.group(2)
- if tracepointname:
- tracepointname = tracepointname.lstrip()
- r = KernRe(r'TP_PROTO\((.*?)\)')
- if r.search(proto):
- tracepointargs = r.group(1)
- if not tracepointname or not tracepointargs:
- self.emit_msg(ln,
- f"Unrecognized tracepoint format:\n{proto}\n")
- else:
- proto = f"static inline void trace_{tracepointname}({tracepointargs})"
- self.entry.identifier = f"trace_{self.entry.identifier}"
- return proto
- def process_proto_function(self, ln, line):
- """Ancillary routine to process a function prototype."""
- # strip C99-style comments to end of line
- line = KernRe(r"//.*$", re.S).sub('', line)
- #
- # Soak up the line's worth of prototype text, stopping at { or ; if present.
- #
- if KernRe(r'\s*#\s*define').match(line):
- self.entry.prototype = line
- elif not line.startswith('#'): # skip other preprocessor stuff
- r = KernRe(r'([^\{]*)')
- if r.match(line):
- self.entry.prototype += r.group(1) + " "
- #
- # If we now have the whole prototype, clean it up and declare victory.
- #
- if '{' in line or ';' in line or KernRe(r'\s*#\s*define').match(line):
- # strip comments and surrounding spaces
- self.entry.prototype = KernRe(r'/\*.*\*/').sub('', self.entry.prototype).strip()
- #
- # Handle self.entry.prototypes for function pointers like:
- # int (*pcs_config)(struct foo)
- # by turning it into
- # int pcs_config(struct foo)
- #
- r = KernRe(r'^(\S+\s+)\(\s*\*(\S+)\)')
- self.entry.prototype = r.sub(r'\1\2', self.entry.prototype)
- #
- # Handle special declaration syntaxes
- #
- if 'SYSCALL_DEFINE' in self.entry.prototype:
- self.entry.prototype = self.syscall_munge(ln,
- self.entry.prototype)
- else:
- r = KernRe(r'TRACE_EVENT|DEFINE_EVENT|DEFINE_SINGLE_EVENT')
- if r.search(self.entry.prototype):
- self.entry.prototype = self.tracepoint_munge(ln,
- self.entry.prototype)
- #
- # ... and we're done
- #
- self.dump_function(ln, self.entry.prototype)
- self.reset_state(ln)
- def process_proto_type(self, ln, line):
- """
- Ancillary routine to process a type.
- """
- # Strip C99-style comments and surrounding whitespace
- line = KernRe(r"//.*$", re.S).sub('', line).strip()
- if not line:
- return # nothing to see here
- # To distinguish preprocessor directive from regular declaration later.
- if line.startswith('#'):
- line += ";"
- #
- # Split the declaration on any of { } or ;, and accumulate pieces
- # until we hit a semicolon while not inside {brackets}
- #
- r = KernRe(r'(.*?)([{};])')
- for chunk in r.split(line):
- if chunk: # Ignore empty matches
- self.entry.prototype += chunk
- #
- # This cries out for a match statement ... someday after we can
- # drop Python 3.9 ...
- #
- if chunk == '{':
- self.entry.brcount += 1
- elif chunk == '}':
- self.entry.brcount -= 1
- elif chunk == ';' and self.entry.brcount <= 0:
- self.dump_declaration(ln, self.entry.prototype)
- self.reset_state(ln)
- return
- #
- # We hit the end of the line while still in the declaration; put
- # in a space to represent the newline.
- #
- self.entry.prototype += ' '
- def process_proto(self, ln, line):
- """STATE_PROTO: reading a function/whatever prototype."""
- if doc_inline_oneline.search(line):
- self.entry.begin_section(ln, doc_inline_oneline.group(1))
- self.entry.add_text(doc_inline_oneline.group(2))
- self.dump_section()
- elif doc_inline_start.search(line):
- self.state = state.INLINE_NAME
- elif self.entry.decl_type == 'function':
- self.process_proto_function(ln, line)
- else:
- self.process_proto_type(ln, line)
- def process_docblock(self, ln, line):
- """STATE_DOCBLOCK: within a ``DOC:`` block."""
- if doc_end.search(line):
- self.dump_section()
- self.output_declaration("doc", self.entry.identifier)
- self.reset_state(ln)
- elif doc_content.search(line):
- self.entry.add_text(doc_content.group(1))
- def parse_export(self):
- """
- Parses ``EXPORT_SYMBOL*`` macros from a single Kernel source file.
- """
- export_table = set()
- try:
- with open(self.fname, "r", encoding="utf8",
- errors="backslashreplace") as fp:
- for line in fp:
- self.process_export(export_table, line)
- except IOError:
- return None
- return export_table
- #: The state/action table telling us which function to invoke in each state.
- state_actions = {
- state.NORMAL: process_normal,
- state.NAME: process_name,
- state.BODY: process_body,
- state.DECLARATION: process_decl,
- state.SPECIAL_SECTION: process_special,
- state.INLINE_NAME: process_inline_name,
- state.INLINE_TEXT: process_inline_text,
- state.PROTO: process_proto,
- state.DOCBLOCK: process_docblock,
- }
- def parse_kdoc(self):
- """
- Open and process each line of a C source file.
- The parsing is controlled via a state machine, and the line is passed
- to a different process function depending on the state. The process
- function may update the state as needed.
- Besides parsing kernel-doc tags, it also parses export symbols.
- """
- prev = ""
- prev_ln = None
- export_table = set()
- try:
- with open(self.fname, "r", encoding="utf8",
- errors="backslashreplace") as fp:
- for ln, line in enumerate(fp):
- line = line.expandtabs().strip("\n")
- # Group continuation lines on prototypes
- if self.state == state.PROTO:
- if line.endswith("\\"):
- prev += line.rstrip("\\")
- if not prev_ln:
- prev_ln = ln
- continue
- if prev:
- ln = prev_ln
- line = prev + line
- prev = ""
- prev_ln = None
- self.config.log.debug("%d %s: %s",
- ln, state.name[self.state],
- line)
- # This is an optimization over the original script.
- # There, when export_file was used for the same file,
- # it was read twice. Here, we use the already-existing
- # loop to parse exported symbols as well.
- #
- if (self.state != state.NORMAL) or \
- not self.process_export(export_table, line):
- # Hand this line to the appropriate state handler
- self.state_actions[self.state](self, ln, line)
- self.emit_unused_warnings()
- except OSError:
- self.config.log.error(f"Error: Cannot open file {self.fname}")
- return export_table, self.entries
|