# # # The Nim Compiler # (c) Copyright 2017 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## Computes hash values for routine (proc, method etc) signatures. import ast, ropes, modulegraphs, options, msgs, pathutils from lineinfos import FileIndex from std/hashes import Hash import std/tables import types import ../dist/checksums/src/checksums/md5 when defined(nimPreviewSlimSystem): import std/assertions proc `&=`(c: var MD5Context, s: string) = md5Update(c, s, s.len) proc `&=`(c: var MD5Context, ch: char) = # XXX suspicious code here; relies on ch being zero terminated? md5Update(c, cast[cstring](unsafeAddr ch), 1) proc `&=`(c: var MD5Context, i: BiggestInt) = md5Update(c, cast[cstring](unsafeAddr i), sizeof(i)) proc `&=`(c: var MD5Context, f: BiggestFloat) = md5Update(c, cast[cstring](unsafeAddr f), sizeof(f)) proc `&=`(c: var MD5Context, s: SigHash) = md5Update(c, cast[cstring](unsafeAddr s), sizeof(s)) template lowlevel(v) = md5Update(c, cast[cstring](unsafeAddr(v)), sizeof(v)) type ConsiderFlag* = enum CoProc CoType CoOwnerSig CoIgnoreRange CoIgnoreRangeInArray CoConsiderOwned CoDistinct CoHashTypeInsideNode proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) proc hashSym(c: var MD5Context, s: PSym) = if sfAnon in s.flags or s.kind == skGenericParam: c &= ":anon" else: var it = s when defined(icDbgHash): var ownerSteps = 0 while it != nil: when defined(icDbgHash): inc ownerSteps if ownerSteps >= 1000 and ownerSteps <= 1030: echo "OWNERLOOP(hashSym) n=", ownerSteps, " sym=", it.name.s, " kind=", it.kind, " id=", it.itemId, " flags=", it.flags, " state=", it.state, " start=", s.name.s, " startId=", s.itemId elif ownerSteps == 1031: raiseAssert "owner-chain cycle detected, see OWNERLOOP dump above" c &= it.name.s c &= "." it = it.owner c &= "#" c &= s.disamb proc hashTypeSym(c: var MD5Context, s: PSym; conf: ConfigRef) = if sfAnon in s.flags or s.kind == skGenericParam: c &= ":anon" else: var it = s # The source file path disambiguates same-named object types from different # modules whose owner-chain names also coincide (e.g. libp2p kademlia/protobuf # `Message` vs rendezvous/protobuf `Message`, both modules named `protobuf`). # A type sym that reaches the backend as a `Complete` stub never individually # loaded carries `unknownLineInfo` (fileIndex -1), which `toFullPath` collapses # to the `???` placeholder — so the two would hash to ONE mangled C name and the # wrong struct gets emitted. Fall back to the sym's HOME module file (its # per-module NIF-suffix path, stable+unique) for the path. Only fires on a -1 # fileIndex; non-IC type syms always have a real `info`, so the fast path is # taken and the hash is unchanged (koch boot byte-equal). let infoFi = s.info.fileIndex let pathFi = if infoFi.int32 >= 0'i32: infoFi else: s.itemId.module.int32.FileIndex c &= customPath(conf.toFullPath(pathFi)) when defined(icDbgHash): var ownerSteps = 0 while it != nil: when defined(icDbgHash): inc ownerSteps if ownerSteps >= 1000 and ownerSteps <= 1030: echo "OWNERLOOP n=", ownerSteps, " sym=", it.name.s, " kind=", it.kind, " id=", it.itemId, " flags=", it.flags, " state=", it.state, " start=", s.name.s, " startId=", s.itemId elif ownerSteps == 1031: raiseAssert "owner-chain cycle detected, see OWNERLOOP dump above" if sfFromGeneric in it.flags and it.kind in routineKinds and it.typ != nil: hashType c, it.typ, {CoProc}, conf c &= it.name.s c &= "." it = it.owner c &= "#" c &= s.disamb proc hashTree(c: var MD5Context, n: PNode; flags: set[ConsiderFlag]; conf: ConfigRef) = if n == nil: c &= "\255" return let k = n.kind c &= char(k) # we really must not hash line information. 'n.typ' is debatable but # shouldn't be necessary for now and avoids potential infinite recursions. case n.kind of nkEmpty, nkNilLit, nkType: discard of nkIdent: c &= n.ident.s of nkSym: hashSym(c, n.sym) if CoHashTypeInsideNode in flags and n.sym.typ != nil: hashType(c, n.sym.typ, flags, conf) of nkCharLit..nkUInt64Lit: let v = n.intVal lowlevel v of nkFloatLit..nkFloat64Lit: let v = n.floatVal lowlevel v of nkStrLit..nkTripleStrLit: c &= n.strVal else: for i in 0.. hashMaxDepth: hashMaxDepth = hashDepth if hashCalls >= 500_000_000 and hashCalls <= 500_000_300: echo "HASHLOOP n=", hashCalls, " d=", hashDepth, " kind=", t.kind, " id=", t.itemId, " uniq=", t.uniqueId, " sym=", (if t.sym != nil: t.sym.name.s else: "NIL"), " state=", t.state, " owner=", (if t.owner != nil: t.owner.name.s else: "NIL") elif hashCalls == 500_000_301: echo "HASHLOOP maxDepth=", hashMaxDepth raiseAssert "hashType runaway detected, see HASHLOOP dump above" defer: dec hashDepth # Ensure type is fully loaded before hashing to avoid hash changing # as properties are accessed and trigger lazy loading. backendEnsureMutable(t) # Bare type-class keywords used as a typedesc without arguments (e.g. `array`, # `range`, `distinct` passed to `signatureHash`) have no children, so the # structural branches below would index a non-existent `elementType`. Hash them # by kind (+ sym for an extra, stable distinction) — enough for a stable, # distinct identity. (`seq`/`openArray`/`tuple` already fall through the empty # `else` loop unharmed; this covers the branches that index `elementType`.) if t.kind in {tyArray, tyRange, tyDistinct} and not t.hasElementType: c &= char(t.kind) if t.sym != nil: c.hashSym(t.sym) return case t.kind of tyGenericInvocation: for a in t.kids: c.hashType a, flags, conf of tyDistinct: if CoDistinct in flags: if t.sym != nil: c.hashSym(t.sym) if t.sym == nil or tfFromGeneric in t.flags: c.hashType t.elementType, flags, conf elif CoType in flags or t.sym == nil: c.hashType t.elementType, flags, conf else: c.hashSym(t.sym) of tyGenericInst: if sfInfixCall in t.base.sym.flags: # This is an imported C++ generic type. # We cannot trust the `lastSon` to hold a properly populated and unique # value for each instantiation, so we hash the generic parameters here: let normalizedType = t.skipGenericAlias c.hashType normalizedType.genericHead, flags, conf for _, a in normalizedType.genericInstParams: c.hashType a, flags, conf else: c.hashType t.skipModifier, flags, conf of tyAlias, tySink, tyUserTypeClasses, tyInferred: c.hashType t.skipModifier, flags, conf of tyOwned: if CoConsiderOwned in flags: c &= char(t.kind) c.hashType t.skipModifier, flags, conf of tyBool, tyChar, tyPointer, tyCstring, tyInt..tyUInt64: # no canonicalization for builtin scalar-ish / pointer-like types, so # that e.g. ``pid_t`` or an imported ``pointer`` alias keep their # backend spelling instead of collapsing into the generic Nim builtin: c &= char(t.kind) if t.sym != nil and {sfImportc, sfExportc} * t.sym.flags != {}: c.hashSym(t.sym) of tyObject, tyEnum: if t.typeInstImpl != nil: # prevent against infinite recursions here, see bug #8883: let inst = t.typeInstImpl t.typeInstImpl = nil # IC: spurious writes are ok since we set it back immediately assert inst.kind == tyGenericInst c.hashType inst.genericHead, flags, conf for _, a in inst.genericInstParams: c.hashType a, flags+{CoDistinct}, conf t.typeInstImpl = inst return c &= char(t.kind) # Every cyclic type in Nim need to be constructed via some 't.sym', so this # is actually safe without an infinite recursion check: if t.sym != nil: if {sfCompilerProc} * t.sym.flags != {}: doAssert t.sym.loc.snippet != "" # The user has set a specific name for this type c &= t.sym.loc.snippet elif CoOwnerSig in flags: c.hashTypeSym(t.sym, conf) else: c.hashSym(t.sym) var symWithFlags: PSym = nil template hasFlag(sym): bool = let ret = {sfAnon, sfGenSym} * sym.flags != {} if ret: symWithFlags = sym ret if hasFlag(t.sym) or (t.kind == tyObject and t.owner.kind == skType and t.owner.typ.kind == tyRef and hasFlag(t.owner)): # for `PFoo:ObjectType`, arising from `type PFoo = ref object` # Generated object names can be identical, so we need to # disambiguate furthermore by hashing the field types and names. if t.n.len > 0: let oldFlags = symWithFlags.flags # Hack to prevent endless recursion # xxx instead, use a hash table to indicate we've already visited a type, which # would also be more efficient. symWithFlags.flagsImpl.excl {sfAnon, sfGenSym} hashTree(c, t.n, flags + {CoHashTypeInsideNode}, conf) symWithFlags.flagsImpl = oldFlags else: # The object has no fields: we _must_ add something here in order to # make the hash different from the one we produce by hashing only the # type name. c &= ".empty" else: c &= t.id if t.hasElementType and t.baseClass != nil: hashType c, t.baseClass, flags, conf of tyRef, tyPtr, tyVar: c &= char(t.kind) if t.hasElementType: c.hashType t.elementType, flags, conf if tfVarIsPtr in t.flags: c &= ".varisptr" of tyGenericBody: c &= char(t.kind) if t.hasElementType: c.hashType t.typeBodyImpl, flags, conf of tyFromExpr: c &= char(t.kind) c.hashTree(t.n, {}, conf) of tyTuple: c &= char(t.kind) c &= t.len if t.n != nil and CoType notin flags: for i in 0..