Lichen

resolving.py

641:c71722209a3d
2017-03-01 Paul Boddie Added support for preserving attribute and parameter locations, avoiding misidentification issues in existing, preserved object code.
     1 #!/usr/bin/env python     2      3 """     4 Name resolution.     5      6 Copyright (C) 2016, 2017 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 from common import init_item    23 from results import AccessRef, InstanceRef, InvocationRef, LocalNameRef, \    24                     NameRef, ResolvedNameRef    25 from referencing import Reference    26 import sys    27     28 class NameResolving:    29     30     "Resolving names mix-in for inspected modules."    31     32     # Post-inspection resolution activities.    33     34     def resolve(self):    35     36         "Resolve dependencies and complete definitions."    37     38         self.resolve_class_bases()    39         self.check_special()    40         self.check_names_used()    41         self.check_invocations()    42         self.resolve_initialisers()    43         self.resolve_literals()    44     45     def resolve_class_bases(self):    46     47         "Resolve all class bases since some of them may have been deferred."    48     49         for name, bases in self.classes.items():    50             resolved = []    51             bad = []    52     53             for base in bases:    54                 ref = self.importer.identify(base.get_origin())    55     56                 # Obtain the origin of the base class reference.    57     58                 if not ref or not ref.has_kind("<class>"):    59                     bad.append(base)    60                     break    61     62                 resolved.append(ref)    63     64             if bad:    65                 print >>sys.stderr, "Bases of class %s were not classes: %s" % (name, ", ".join(map(str, bad)))    66             else:    67                 self.importer.classes[name] = self.classes[name] = resolved    68     69     def check_special(self):    70     71         "Check special names."    72     73         for name, value in self.special.items():    74             ref, paths = value    75             self.special[name] = self.importer.identify(ref.get_origin()), paths    76     77     def check_names_used(self):    78     79         "Check the external names used by each scope."    80     81         for key, ref in self.name_references.items():    82             path, name = key.rsplit(".", 1)    83             self.resolve_accesses(path, name, ref)    84     85     def check_invocations(self):    86     87         "Find invocations amongst module data and replace their results."    88     89         # Find members and replace invocation results with values. This is    90         # effectively the same as is done for initialised names, but refers to    91         # any unchanging value after initialisation.    92     93         for key, ref in self.objects.items():    94             if ref.has_kind("<invoke>"):    95                 ref = self.convert_invocation(ref)    96                 self.importer.objects[key] = self.objects[key] = ref    97     98         # Convert name references.    99    100         for key, ref in self.name_references.items():   101             if ref.has_kind("<invoke>"):   102                 ref = self.convert_invocation(ref)   103                 self.importer.all_name_references[key] = self.name_references[key] = ref   104    105         # Convert function defaults, which are effectively extra members of the   106         # module, and function locals.   107    108         for fname, parameters in self.function_defaults.items():   109             l = []   110             for pname, ref in parameters:   111                 if ref.has_kind("<invoke>"):   112                     ref = self.convert_invocation(ref)   113                 l.append((pname, ref))   114             self.importer.function_defaults[fname] = self.function_defaults[fname] = l   115    116         # Convert function locals referencing invocations.   117    118         for fname, names in self.function_locals.items():   119             for name, ref in names.items():   120                 if ref.has_kind("<invoke>"):   121                     ref = self.convert_invocation(ref)   122                     names[name] = ref   123    124     def convert_invocation(self, ref):   125    126         "Convert the given invocation 'ref', handling instantiation."   127    128         alias = ref.get_name()   129         ref = self.importer.identify(ref.get_origin())   130         ref = ref and ref.has_kind("<class>") and ref.instance_of() or Reference("<var>")   131         return ref and ref.alias(alias) or None   132    133     def resolve_accesses(self, path, name, ref):   134    135         """   136         Resolve any unresolved accesses in the function at the given 'path'   137         for the given 'name' corresponding to the indicated 'ref'. Note that   138         this mechanism cannot resolve things like inherited methods because   139         they are not recorded as program objects in their inherited locations.   140         """   141    142         attr_accesses = self.global_attr_accesses.get(path)   143         all_attrnames = attr_accesses and attr_accesses.get(name)   144    145         if not all_attrnames:   146             return   147    148         # Insist on constant accessors.   149    150         if not ref.has_kind(["<class>", "<module>"]):   151             return   152    153         found_attrnames = set()   154    155         for attrnames in all_attrnames:   156    157             # Start with the resolved name, adding attributes.   158    159             attrs = ref.get_path()   160             remaining = attrnames.split(".")   161             last_ref = ref   162    163             # Add each component, testing for a constant object.   164    165             while remaining:   166                 attrname = remaining[0]   167                 attrs.append(attrname)   168                 del remaining[0]   169    170                 # Find any constant object reference.   171    172                 attr_ref = self.get_resolved_object(".".join(attrs))   173    174                 # Non-constant accessors terminate the traversal.   175    176                 if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]):   177    178                     # Provide the furthest constant accessor unless the final   179                     # access can be resolved.   180    181                     if remaining:   182                         remaining.insert(0, attrs.pop())   183                     else:   184                         last_ref = attr_ref   185                     break   186    187                 # Follow any reference to a constant object.   188                 # Where the given name refers to an object in another location,   189                 # switch to the other location in order to be able to test its   190                 # attributes.   191    192                 last_ref = attr_ref   193                 attrs = attr_ref.get_path()   194    195             # Record a constant accessor only if an object was found   196             # that is different from the namespace involved.   197    198             if last_ref:   199                 objpath = ".".join(attrs)   200                 if objpath != path:   201    202                     if last_ref.has_kind("<invoke>"):   203                         last_ref = self.convert_invocation(last_ref)   204    205                     # Establish a constant access.   206    207                     init_item(self.const_accesses, path, dict)   208                     self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining))   209    210                     if len(attrs) > 1:   211                         found_attrnames.add(attrs[1])   212    213         # Remove any usage records for the name.   214    215         if found_attrnames:   216    217             # NOTE: Should be only one name version.   218    219             versions = []   220             for version in self.attr_usage[path][name]:   221                 new_usage = set()   222                 for usage in version:   223                     if found_attrnames.intersection(usage):   224                         new_usage.add(tuple(set(usage).difference(found_attrnames)))   225                     else:   226                         new_usage.add(usage)   227                 versions.append(new_usage)   228    229             self.attr_usage[path][name] = versions   230    231     def resolve_initialisers(self):   232    233         "Resolve initialiser values for names."   234    235         # Get the initialisers in each namespace.   236    237         for path, name_initialisers in self.name_initialisers.items():   238             const_accesses = self.const_accesses.get(path)   239    240             # Resolve values for each name in a scope.   241    242             for name, values in name_initialisers.items():   243                 if path == self.name:   244                     assigned_path = name   245                 else:   246                     assigned_path = "%s.%s" % (path, name)   247    248                 initialised_names = {}   249                 aliased_names = {}   250    251                 for i, name_ref in enumerate(values):   252    253                     # Unwrap invocations.   254    255                     if isinstance(name_ref, InvocationRef):   256                         invocation = True   257                         name_ref = name_ref.name_ref   258                     else:   259                         invocation = False   260    261                     # Obtain a usable reference from names or constants.   262    263                     if isinstance(name_ref, ResolvedNameRef):   264                         if not name_ref.reference():   265                             continue   266                         ref = name_ref.reference()   267    268                     # Obtain a reference from instances.   269    270                     elif isinstance(name_ref, InstanceRef):   271                         if not name_ref.reference():   272                             continue   273                         ref = name_ref.reference()   274    275                     # Resolve accesses that employ constants.   276    277                     elif isinstance(name_ref, AccessRef):   278                         ref = None   279    280                         if const_accesses:   281                             resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))   282                             if resolved_access:   283                                 objpath, ref, remaining_attrnames = resolved_access   284                                 if remaining_attrnames:   285                                     ref = None   286    287                         # Accesses that do not employ constants cannot be resolved,   288                         # but they may be resolvable later.   289    290                         if not ref:   291                             if not invocation:   292    293                                 # Record the path used for tracking purposes   294                                 # alongside original name, attribute and access   295                                 # number details.   296    297                                 aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number   298    299                             continue   300    301                     # Attempt to resolve a plain name reference.   302    303                     elif isinstance(name_ref, LocalNameRef):   304                         key = "%s.%s" % (path, name_ref.name)   305                         origin = self.name_references.get(key)   306    307                         # Accesses that do not refer to known static objects   308                         # cannot be resolved, but they may be resolvable later.   309    310                         if not origin:   311                             if not invocation:   312    313                                 # Record the path used for tracking purposes   314                                 # alongside original name, attribute and access   315                                 # number details.   316    317                                 aliased_names[i] = path, name_ref.name, None, name_ref.number   318    319                             continue   320    321                         ref = self.get_resolved_object(origin)   322                         if not ref:   323                             continue   324    325                     elif isinstance(name_ref, NameRef):   326                         key = "%s.%s" % (path, name_ref.name)   327                         origin = self.name_references.get(key)   328                         if not origin:   329                             continue   330    331                         ref = self.get_resolved_object(origin)   332                         if not ref:   333                             continue   334    335                     else:   336                         continue   337    338                     # Resolve any hidden dependencies involving external objects   339                     # or unresolved names referring to globals or built-ins.   340    341                     if ref.has_kind("<depends>"):   342                         ref = self.importer.identify(ref.get_origin())   343    344                     # Convert class invocations to instances.   345    346                     if ref and invocation:   347                         ref = self.convert_invocation(ref)   348    349                     if ref and not ref.has_kind("<var>"):   350                         initialised_names[i] = ref   351    352                 if initialised_names:   353                     self.initialised_names[assigned_path] = initialised_names   354                 if aliased_names:   355                     self.aliased_names[assigned_path] = aliased_names   356    357     def resolve_literals(self):   358    359         "Resolve constant value types."   360    361         # Get the constants defined in each namespace.   362    363         for path, constants in self.constants.items():   364             for constant, n in constants.items():   365                 objpath = "%s.$c%d" % (path, n)   366                 _constant, value_type, encoding = self.constant_values[objpath]   367                 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}   368    369         # Get the literals defined in each namespace.   370    371         for path, literals in self.literals.items():   372             for n in range(0, literals):   373                 objpath = "%s.$C%d" % (path, n)   374                 value_type = self.literal_types[objpath]   375                 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}   376    377     # Object resolution.   378    379     def get_resolved_object(self, path):   380    381         """   382         Get the details of an object with the given 'path' within this module.   383         Where the object has not been resolved, None is returned. This differs   384         from the get_object method used elsewhere in that it does not return an   385         unresolved object reference.   386         """   387    388         if self.objects.has_key(path):   389             ref = self.objects[path]   390             if ref.has_kind("<depends>"):   391                 return None   392             else:   393                 return ref   394         else:   395             return None   396    397 # vim: tabstop=4 expandtab shiftwidth=4