cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

automarkup.py (10610B)


      1# SPDX-License-Identifier: GPL-2.0
      2# Copyright 2019 Jonathan Corbet <corbet@lwn.net>
      3#
      4# Apply kernel-specific tweaks after the initial document processing
      5# has been done.
      6#
      7from docutils import nodes
      8import sphinx
      9from sphinx import addnodes
     10if sphinx.version_info[0] < 2 or \
     11   sphinx.version_info[0] == 2 and sphinx.version_info[1] < 1:
     12    from sphinx.environment import NoUri
     13else:
     14    from sphinx.errors import NoUri
     15import re
     16from itertools import chain
     17
     18#
     19# Python 2 lacks re.ASCII...
     20#
     21try:
     22    ascii_p3 = re.ASCII
     23except AttributeError:
     24    ascii_p3 = 0
     25
     26#
     27# Regex nastiness.  Of course.
     28# Try to identify "function()" that's not already marked up some
     29# other way.  Sphinx doesn't like a lot of stuff right after a
     30# :c:func: block (i.e. ":c:func:`mmap()`s" flakes out), so the last
     31# bit tries to restrict matches to things that won't create trouble.
     32#
     33RE_function = re.compile(r'\b(([a-zA-Z_]\w+)\(\))', flags=ascii_p3)
     34
     35#
     36# Sphinx 2 uses the same :c:type role for struct, union, enum and typedef
     37#
     38RE_generic_type = re.compile(r'\b(struct|union|enum|typedef)\s+([a-zA-Z_]\w+)',
     39                             flags=ascii_p3)
     40
     41#
     42# Sphinx 3 uses a different C role for each one of struct, union, enum and
     43# typedef
     44#
     45RE_struct = re.compile(r'\b(struct)\s+([a-zA-Z_]\w+)', flags=ascii_p3)
     46RE_union = re.compile(r'\b(union)\s+([a-zA-Z_]\w+)', flags=ascii_p3)
     47RE_enum = re.compile(r'\b(enum)\s+([a-zA-Z_]\w+)', flags=ascii_p3)
     48RE_typedef = re.compile(r'\b(typedef)\s+([a-zA-Z_]\w+)', flags=ascii_p3)
     49
     50#
     51# Detects a reference to a documentation page of the form Documentation/... with
     52# an optional extension
     53#
     54RE_doc = re.compile(r'(\bDocumentation/)?((\.\./)*[\w\-/]+)\.(rst|txt)')
     55
     56RE_namespace = re.compile(r'^\s*..\s*c:namespace::\s*(\S+)\s*$')
     57
     58#
     59# Reserved C words that we should skip when cross-referencing
     60#
     61Skipnames = [ 'for', 'if', 'register', 'sizeof', 'struct', 'unsigned' ]
     62
     63
     64#
     65# Many places in the docs refer to common system calls.  It is
     66# pointless to try to cross-reference them and, as has been known
     67# to happen, somebody defining a function by these names can lead
     68# to the creation of incorrect and confusing cross references.  So
     69# just don't even try with these names.
     70#
     71Skipfuncs = [ 'open', 'close', 'read', 'write', 'fcntl', 'mmap',
     72              'select', 'poll', 'fork', 'execve', 'clone', 'ioctl',
     73              'socket' ]
     74
     75c_namespace = ''
     76
     77def markup_refs(docname, app, node):
     78    t = node.astext()
     79    done = 0
     80    repl = [ ]
     81    #
     82    # Associate each regex with the function that will markup its matches
     83    #
     84    markup_func_sphinx2 = {RE_doc: markup_doc_ref,
     85                           RE_function: markup_c_ref,
     86                           RE_generic_type: markup_c_ref}
     87
     88    markup_func_sphinx3 = {RE_doc: markup_doc_ref,
     89                           RE_function: markup_func_ref_sphinx3,
     90                           RE_struct: markup_c_ref,
     91                           RE_union: markup_c_ref,
     92                           RE_enum: markup_c_ref,
     93                           RE_typedef: markup_c_ref}
     94
     95    if sphinx.version_info[0] >= 3:
     96        markup_func = markup_func_sphinx3
     97    else:
     98        markup_func = markup_func_sphinx2
     99
    100    match_iterators = [regex.finditer(t) for regex in markup_func]
    101    #
    102    # Sort all references by the starting position in text
    103    #
    104    sorted_matches = sorted(chain(*match_iterators), key=lambda m: m.start())
    105    for m in sorted_matches:
    106        #
    107        # Include any text prior to match as a normal text node.
    108        #
    109        if m.start() > done:
    110            repl.append(nodes.Text(t[done:m.start()]))
    111
    112        #
    113        # Call the function associated with the regex that matched this text and
    114        # append its return to the text
    115        #
    116        repl.append(markup_func[m.re](docname, app, m))
    117
    118        done = m.end()
    119    if done < len(t):
    120        repl.append(nodes.Text(t[done:]))
    121    return repl
    122
    123#
    124# In sphinx3 we can cross-reference to C macro and function, each one with its
    125# own C role, but both match the same regex, so we try both.
    126#
    127def markup_func_ref_sphinx3(docname, app, match):
    128    class_str = ['c-func', 'c-macro']
    129    reftype_str = ['function', 'macro']
    130
    131    cdom = app.env.domains['c']
    132    #
    133    # Go through the dance of getting an xref out of the C domain
    134    #
    135    base_target = match.group(2)
    136    target_text = nodes.Text(match.group(0))
    137    xref = None
    138    possible_targets = [base_target]
    139    # Check if this document has a namespace, and if so, try
    140    # cross-referencing inside it first.
    141    if c_namespace:
    142        possible_targets.insert(0, c_namespace + "." + base_target)
    143
    144    if base_target not in Skipnames:
    145        for target in possible_targets:
    146            if target not in Skipfuncs:
    147                for class_s, reftype_s in zip(class_str, reftype_str):
    148                    lit_text = nodes.literal(classes=['xref', 'c', class_s])
    149                    lit_text += target_text
    150                    pxref = addnodes.pending_xref('', refdomain = 'c',
    151                                                  reftype = reftype_s,
    152                                                  reftarget = target, modname = None,
    153                                                  classname = None)
    154                    #
    155                    # XXX The Latex builder will throw NoUri exceptions here,
    156                    # work around that by ignoring them.
    157                    #
    158                    try:
    159                        xref = cdom.resolve_xref(app.env, docname, app.builder,
    160                                                 reftype_s, target, pxref,
    161                                                 lit_text)
    162                    except NoUri:
    163                        xref = None
    164
    165                    if xref:
    166                        return xref
    167
    168    return target_text
    169
    170def markup_c_ref(docname, app, match):
    171    class_str = {# Sphinx 2 only
    172                 RE_function: 'c-func',
    173                 RE_generic_type: 'c-type',
    174                 # Sphinx 3+ only
    175                 RE_struct: 'c-struct',
    176                 RE_union: 'c-union',
    177                 RE_enum: 'c-enum',
    178                 RE_typedef: 'c-type',
    179                 }
    180    reftype_str = {# Sphinx 2 only
    181                   RE_function: 'function',
    182                   RE_generic_type: 'type',
    183                   # Sphinx 3+ only
    184                   RE_struct: 'struct',
    185                   RE_union: 'union',
    186                   RE_enum: 'enum',
    187                   RE_typedef: 'type',
    188                   }
    189
    190    cdom = app.env.domains['c']
    191    #
    192    # Go through the dance of getting an xref out of the C domain
    193    #
    194    base_target = match.group(2)
    195    target_text = nodes.Text(match.group(0))
    196    xref = None
    197    possible_targets = [base_target]
    198    # Check if this document has a namespace, and if so, try
    199    # cross-referencing inside it first.
    200    if c_namespace:
    201        possible_targets.insert(0, c_namespace + "." + base_target)
    202
    203    if base_target not in Skipnames:
    204        for target in possible_targets:
    205            if not (match.re == RE_function and target in Skipfuncs):
    206                lit_text = nodes.literal(classes=['xref', 'c', class_str[match.re]])
    207                lit_text += target_text
    208                pxref = addnodes.pending_xref('', refdomain = 'c',
    209                                              reftype = reftype_str[match.re],
    210                                              reftarget = target, modname = None,
    211                                              classname = None)
    212                #
    213                # XXX The Latex builder will throw NoUri exceptions here,
    214                # work around that by ignoring them.
    215                #
    216                try:
    217                    xref = cdom.resolve_xref(app.env, docname, app.builder,
    218                                             reftype_str[match.re], target, pxref,
    219                                             lit_text)
    220                except NoUri:
    221                    xref = None
    222
    223                if xref:
    224                    return xref
    225
    226    return target_text
    227
    228#
    229# Try to replace a documentation reference of the form Documentation/... with a
    230# cross reference to that page
    231#
    232def markup_doc_ref(docname, app, match):
    233    stddom = app.env.domains['std']
    234    #
    235    # Go through the dance of getting an xref out of the std domain
    236    #
    237    absolute = match.group(1)
    238    target = match.group(2)
    239    if absolute:
    240       target = "/" + target
    241    xref = None
    242    pxref = addnodes.pending_xref('', refdomain = 'std', reftype = 'doc',
    243                                  reftarget = target, modname = None,
    244                                  classname = None, refexplicit = False)
    245    #
    246    # XXX The Latex builder will throw NoUri exceptions here,
    247    # work around that by ignoring them.
    248    #
    249    try:
    250        xref = stddom.resolve_xref(app.env, docname, app.builder, 'doc',
    251                                   target, pxref, None)
    252    except NoUri:
    253        xref = None
    254    #
    255    # Return the xref if we got it; otherwise just return the plain text.
    256    #
    257    if xref:
    258        return xref
    259    else:
    260        return nodes.Text(match.group(0))
    261
    262def get_c_namespace(app, docname):
    263    source = app.env.doc2path(docname)
    264    with open(source) as f:
    265        for l in f:
    266            match = RE_namespace.search(l)
    267            if match:
    268                return match.group(1)
    269    return ''
    270
    271def auto_markup(app, doctree, name):
    272    global c_namespace
    273    c_namespace = get_c_namespace(app, name)
    274    def text_but_not_a_reference(node):
    275        # The nodes.literal test catches ``literal text``, its purpose is to
    276        # avoid adding cross-references to functions that have been explicitly
    277        # marked with cc:func:.
    278        if not isinstance(node, nodes.Text) or isinstance(node.parent, nodes.literal):
    279            return False
    280
    281        child_of_reference = False
    282        parent = node.parent
    283        while parent:
    284            if isinstance(parent, nodes.Referential):
    285                child_of_reference = True
    286                break
    287            parent = parent.parent
    288        return not child_of_reference
    289
    290    #
    291    # This loop could eventually be improved on.  Someday maybe we
    292    # want a proper tree traversal with a lot of awareness of which
    293    # kinds of nodes to prune.  But this works well for now.
    294    #
    295    for para in doctree.traverse(nodes.paragraph):
    296        for node in para.traverse(condition=text_but_not_a_reference):
    297            node.parent.replace(node, markup_refs(name, app, node))
    298
    299def setup(app):
    300    app.connect('doctree-resolved', auto_markup)
    301    return {
    302        'parallel_read_safe': True,
    303        'parallel_write_safe': True,
    304        }