| /** | |
| * 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; | |
| } |