| /** |
| * Enforce visibility contrains such as `public` and `private`. |
| * |
| * Specification: $(LINK2 https://dlang.org/spec/attribute.html#visibility_attributes, Visibility Attributes) |
| * |
| * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved |
| * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/access.d, _access.d) |
| * Documentation: https://dlang.org/phobos/dmd_access.html |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/access.d |
| */ |
| |
| module dmd.access; |
| |
| import dmd.aggregate; |
| import dmd.astenums; |
| import dmd.dclass; |
| import dmd.declaration; |
| import dmd.dmodule; |
| import dmd.dscope; |
| import dmd.dstruct; |
| import dmd.dsymbol; |
| import dmd.errors; |
| import dmd.expression; |
| import dmd.func; |
| import dmd.globals; |
| import dmd.mtype; |
| import dmd.tokens; |
| |
| private enum LOG = false; |
| |
| |
| /******************************* |
| * Do access check for member of this class, this class being the |
| * type of the 'this' pointer used to access smember. |
| * Returns true if the member is not accessible. |
| */ |
| bool checkAccess(AggregateDeclaration ad, Loc loc, Scope* sc, Dsymbol smember) |
| { |
| static if (LOG) |
| { |
| printf("AggregateDeclaration::checkAccess() for %s.%s in function %s() in scope %s\n", ad.toChars(), smember.toChars(), f ? f.toChars() : null, cdscope ? cdscope.toChars() : null); |
| } |
| |
| const p = smember.toParent(); |
| if (p && p.isTemplateInstance()) |
| { |
| return false; // for backward compatibility |
| } |
| |
| if (!symbolIsVisible(sc, smember)) |
| { |
| ad.error(loc, "%s `%s` is not accessible", smember.kind(), smember.toChars()); |
| //printf("smember = %s %s, vis = %d, semanticRun = %d\n", |
| // smember.kind(), smember.toPrettyChars(), smember.visible() smember.semanticRun); |
| return true; |
| } |
| return false; |
| } |
| |
| /**************************************** |
| * Determine if scope sc has package level access to s. |
| */ |
| private bool hasPackageAccess(Scope* sc, Dsymbol s) |
| { |
| return hasPackageAccess(sc._module, s); |
| } |
| |
| private bool hasPackageAccess(Module mod, Dsymbol s) |
| { |
| static if (LOG) |
| { |
| printf("hasPackageAccess(s = '%s', mod = '%s', s.visibility.pkg = '%s')\n", s.toChars(), mod.toChars(), s.visible().pkg ? s.visible().pkg.toChars() : "NULL"); |
| } |
| Package pkg = null; |
| if (s.visible().pkg) |
| pkg = s.visible().pkg; |
| else |
| { |
| // no explicit package for visibility, inferring most qualified one |
| for (; s; s = s.parent) |
| { |
| if (auto m = s.isModule()) |
| { |
| DsymbolTable dst = Package.resolve(m.md ? m.md.packages : null, null, null); |
| assert(dst); |
| Dsymbol s2 = dst.lookup(m.ident); |
| assert(s2); |
| Package p = s2.isPackage(); |
| if (p && p.isPackageMod()) |
| { |
| pkg = p; |
| break; |
| } |
| } |
| else if ((pkg = s.isPackage()) !is null) |
| break; |
| } |
| } |
| static if (LOG) |
| { |
| if (pkg) |
| printf("\tsymbol access binds to package '%s'\n", pkg.toChars()); |
| } |
| if (pkg) |
| { |
| if (pkg == mod.parent) |
| { |
| static if (LOG) |
| { |
| printf("\tsc is in permitted package for s\n"); |
| } |
| return true; |
| } |
| if (pkg.isPackageMod() == mod) |
| { |
| static if (LOG) |
| { |
| printf("\ts is in same package.d module as sc\n"); |
| } |
| return true; |
| } |
| Dsymbol ancestor = mod.parent; |
| for (; ancestor; ancestor = ancestor.parent) |
| { |
| if (ancestor == pkg) |
| { |
| static if (LOG) |
| { |
| printf("\tsc is in permitted ancestor package for s\n"); |
| } |
| return true; |
| } |
| } |
| } |
| static if (LOG) |
| { |
| printf("\tno package access\n"); |
| } |
| return false; |
| } |
| |
| /**************************************** |
| * Determine if scope sc has protected level access to cd. |
| */ |
| private bool hasProtectedAccess(Scope *sc, Dsymbol s) |
| { |
| if (auto cd = s.isClassMember()) // also includes interfaces |
| { |
| for (auto scx = sc; scx; scx = scx.enclosing) |
| { |
| if (!scx.scopesym) |
| continue; |
| auto cd2 = scx.scopesym.isClassDeclaration(); |
| if (cd2 && cd.isBaseOf(cd2, null)) |
| return true; |
| } |
| } |
| return sc._module == s.getAccessModule(); |
| } |
| |
| /**************************************** |
| * Check access to d for expression e.d |
| * Returns true if the declaration is not accessible. |
| */ |
| bool checkAccess(Loc loc, Scope* sc, Expression e, Dsymbol d) |
| { |
| if (sc.flags & SCOPE.noaccesscheck) |
| return false; |
| static if (LOG) |
| { |
| if (e) |
| { |
| printf("checkAccess(%s . %s)\n", e.toChars(), d.toChars()); |
| printf("\te.type = %s\n", e.type.toChars()); |
| } |
| else |
| { |
| printf("checkAccess(%s)\n", d.toPrettyChars()); |
| } |
| } |
| if (d.isUnitTestDeclaration()) |
| { |
| // Unittests are always accessible. |
| return false; |
| } |
| |
| if (!e) |
| return false; |
| |
| if (auto tc = e.type.isTypeClass()) |
| { |
| // Do access check |
| ClassDeclaration cd = tc.sym; |
| if (e.op == EXP.super_) |
| { |
| if (ClassDeclaration cd2 = sc.func.toParent().isClassDeclaration()) |
| cd = cd2; |
| } |
| return checkAccess(cd, loc, sc, d); |
| } |
| else if (auto ts = e.type.isTypeStruct()) |
| { |
| // Do access check |
| StructDeclaration cd = ts.sym; |
| return checkAccess(cd, loc, sc, d); |
| } |
| return false; |
| } |
| |
| /**************************************** |
| * Check access to package/module `p` from scope `sc`. |
| * |
| * Params: |
| * sc = scope from which to access to a fully qualified package name |
| * p = the package/module to check access for |
| * Returns: true if the package is not accessible. |
| * |
| * Because a global symbol table tree is used for imported packages/modules, |
| * access to them needs to be checked based on the imports in the scope chain |
| * (see https://issues.dlang.org/show_bug.cgi?id=313). |
| * |
| */ |
| bool checkAccess(Scope* sc, Package p) |
| { |
| if (sc._module == p) |
| return false; |
| for (; sc; sc = sc.enclosing) |
| { |
| if (sc.scopesym && sc.scopesym.isPackageAccessible(p, Visibility(Visibility.Kind.private_))) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Check whether symbols `s` is visible in `mod`. |
| * |
| * Params: |
| * mod = lookup origin |
| * s = symbol to check for visibility |
| * Returns: true if s is visible in mod |
| */ |
| bool symbolIsVisible(Module mod, Dsymbol s) |
| { |
| // should sort overloads by ascending visibility instead of iterating here |
| s = mostVisibleOverload(s); |
| final switch (s.visible().kind) |
| { |
| case Visibility.Kind.undefined: return true; |
| case Visibility.Kind.none: return false; // no access |
| case Visibility.Kind.private_: return s.getAccessModule() == mod; |
| case Visibility.Kind.package_: return s.getAccessModule() == mod || hasPackageAccess(mod, s); |
| case Visibility.Kind.protected_: return s.getAccessModule() == mod; |
| case Visibility.Kind.public_, Visibility.Kind.export_: return true; |
| } |
| } |
| |
| /** |
| * Same as above, but determines the lookup module from symbols `origin`. |
| */ |
| bool symbolIsVisible(Dsymbol origin, Dsymbol s) |
| { |
| return symbolIsVisible(origin.getAccessModule(), s); |
| } |
| |
| /** |
| * Same as above but also checks for protected symbols visible from scope `sc`. |
| * Used for qualified name lookup. |
| * |
| * Params: |
| * sc = lookup scope |
| * s = symbol to check for visibility |
| * Returns: true if s is visible by origin |
| */ |
| bool symbolIsVisible(Scope *sc, Dsymbol s) |
| { |
| s = mostVisibleOverload(s); |
| return checkSymbolAccess(sc, s); |
| } |
| |
| /** |
| * Check if a symbol is visible from a given scope without taking |
| * into account the most visible overload. |
| * |
| * Params: |
| * sc = lookup scope |
| * s = symbol to check for visibility |
| * Returns: true if s is visible by origin |
| */ |
| bool checkSymbolAccess(Scope *sc, Dsymbol s) |
| { |
| final switch (s.visible().kind) |
| { |
| case Visibility.Kind.undefined: return true; |
| case Visibility.Kind.none: return false; // no access |
| case Visibility.Kind.private_: return sc._module == s.getAccessModule(); |
| case Visibility.Kind.package_: return sc._module == s.getAccessModule() || hasPackageAccess(sc._module, s); |
| case Visibility.Kind.protected_: return hasProtectedAccess(sc, s); |
| case Visibility.Kind.public_, Visibility.Kind.export_: return true; |
| } |
| } |
| |
| /** |
| * Use the most visible overload to check visibility. Later perform an access |
| * check on the resolved overload. This function is similar to overloadApply, |
| * but doesn't recurse nor resolve aliases because visibility is an |
| * attribute of the alias not the aliasee. |
| */ |
| public Dsymbol mostVisibleOverload(Dsymbol s, Module mod = null) |
| { |
| if (!s.isOverloadable()) |
| return s; |
| |
| Dsymbol next, fstart = s, mostVisible = s; |
| for (; s; s = next) |
| { |
| // void func() {} |
| // private void func(int) {} |
| if (auto fd = s.isFuncDeclaration()) |
| next = fd.overnext; |
| // template temp(T) {} |
| // private template temp(T:int) {} |
| else if (auto td = s.isTemplateDeclaration()) |
| next = td.overnext; |
| // alias common = mod1.func1; |
| // alias common = mod2.func2; |
| else if (auto fa = s.isFuncAliasDeclaration()) |
| next = fa.overnext; |
| // alias common = mod1.templ1; |
| // alias common = mod2.templ2; |
| else if (auto od = s.isOverDeclaration()) |
| next = od.overnext; |
| // alias name = sym; |
| // private void name(int) {} |
| else if (auto ad = s.isAliasDeclaration()) |
| { |
| assert(ad.isOverloadable || ad.type && ad.type.ty == Terror, |
| "Non overloadable Aliasee in overload list"); |
| // Yet unresolved aliases store overloads in overnext. |
| if (ad.semanticRun < PASS.semanticdone) |
| next = ad.overnext; |
| else |
| { |
| /* This is a bit messy due to the complicated implementation of |
| * alias. Aliases aren't overloadable themselves, but if their |
| * Aliasee is overloadable they can be converted to an overloadable |
| * alias. |
| * |
| * This is done by replacing the Aliasee w/ FuncAliasDeclaration |
| * (for functions) or OverDeclaration (for templates) which are |
| * simply overloadable aliases w/ weird names. |
| * |
| * Usually aliases should not be resolved for visibility checking |
| * b/c public aliases to private symbols are public. But for the |
| * overloadable alias situation, the Alias (_ad_) has been moved |
| * into its own Aliasee, leaving a shell that we peel away here. |
| */ |
| auto aliasee = ad.toAlias(); |
| if (aliasee.isFuncAliasDeclaration || aliasee.isOverDeclaration) |
| next = aliasee; |
| else |
| { |
| /* A simple alias can be at the end of a function or template overload chain. |
| * It can't have further overloads b/c it would have been |
| * converted to an overloadable alias. |
| */ |
| assert(ad.overnext is null, "Unresolved overload of alias"); |
| break; |
| } |
| } |
| // handled by dmd.func.overloadApply for unknown reason |
| assert(next !is ad); // should not alias itself |
| assert(next !is fstart); // should not alias the overload list itself |
| } |
| else |
| break; |
| |
| /** |
| * Return the "effective" visibility attribute of a symbol when accessed in a module. |
| * The effective visibility attribute is the same as the regular visibility attribute, |
| * except package() is "private" if the module is outside the package; |
| * otherwise, "public". |
| */ |
| static Visibility visibilitySeenFromModule(Dsymbol d, Module mod = null) |
| { |
| Visibility vis = d.visible(); |
| if (mod && vis.kind == Visibility.Kind.package_) |
| { |
| return hasPackageAccess(mod, d) ? Visibility(Visibility.Kind.public_) : Visibility(Visibility.Kind.private_); |
| } |
| return vis; |
| } |
| |
| if (next && |
| visibilitySeenFromModule(mostVisible, mod) < visibilitySeenFromModule(next, mod)) |
| mostVisible = next; |
| } |
| return mostVisible; |
| } |