1 /**
2  * Enforce visibility contrains such as `public` and `private`.
3  *
4  * Specification: $(LINK2 https://dlang.org/spec/attribute.html#visibility_attributes, Visibility Attributes)
5  *
6  * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
7  * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
8  * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/access.d, _access.d)
10  * Documentation:  https://dlang.org/phobos/dmd_access.html
11  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/access.d
12  */
13 
14 module dmd.access;
15 
16 import dmd.aggregate;
17 import dmd.astenums;
18 import dmd.dclass;
19 import dmd.dmodule;
20 import dmd.dscope;
21 import dmd.dstruct;
22 import dmd.dsymbol;
23 import dmd.errors;
24 import dmd.expression;
25 import dmd.location;
26 import dmd.tokens;
27 
28 private enum LOG = false;
29 
30 
31 /*******************************
32  * Do access check for member of this class, this class being the
33  * type of the 'this' pointer used to access smember.
34  * Returns true if the member is not accessible.
35  */
36 bool checkAccess(AggregateDeclaration ad, Loc loc, Scope* sc, Dsymbol smember)
37 {
38     static if (LOG)
39     {
40         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);
41     }
42 
43     const p = smember.toParent();
44     if (p && p.isTemplateInstance())
45     {
46         return false; // for backward compatibility
47     }
48 
49     if (!symbolIsVisible(sc, smember))
50     {
51         error(loc, "%s `%s` %s `%s` is not accessible", ad.kind(), ad.toPrettyChars(), smember.kind(), smember.toChars());
52         //printf("smember = %s %s, vis = %d, semanticRun = %d\n",
53         //        smember.kind(), smember.toPrettyChars(), smember.visible() smember.semanticRun);
54         return true;
55     }
56     return false;
57 }
58 
59 /****************************************
60  * Determine if scope sc has package level access to s.
61  */
62 private bool hasPackageAccess(Scope* sc, Dsymbol s)
63 {
64     return hasPackageAccess(sc._module, s);
65 }
66 
67 private bool hasPackageAccess(Module mod, Dsymbol s)
68 {
69     static if (LOG)
70     {
71         printf("hasPackageAccess(s = '%s', mod = '%s', s.visibility.pkg = '%s')\n", s.toChars(), mod.toChars(), s.visible().pkg ? s.visible().pkg.toChars() : "NULL");
72     }
73     Package pkg = null;
74     if (s.visible().pkg)
75         pkg = s.visible().pkg;
76     else
77     {
78         // no explicit package for visibility, inferring most qualified one
79         for (; s; s = s.parent)
80         {
81             if (auto m = s.isModule())
82             {
83                 DsymbolTable dst = Package.resolve(m.md ? m.md.packages : null, null, null);
84                 assert(dst);
85                 Dsymbol s2 = dst.lookup(m.ident);
86                 assert(s2);
87                 Package p = s2.isPackage();
88                 if (p && p.isPackageMod())
89                 {
90                     pkg = p;
91                     break;
92                 }
93             }
94             else if ((pkg = s.isPackage()) !is null)
95                 break;
96         }
97     }
98     static if (LOG)
99     {
100         if (pkg)
101             printf("\tsymbol access binds to package '%s'\n", pkg.toChars());
102     }
103     if (pkg)
104     {
105         if (pkg == mod.parent)
106         {
107             static if (LOG)
108             {
109                 printf("\tsc is in permitted package for s\n");
110             }
111             return true;
112         }
113         if (pkg.isPackageMod() == mod)
114         {
115             static if (LOG)
116             {
117                 printf("\ts is in same package.d module as sc\n");
118             }
119             return true;
120         }
121         Dsymbol ancestor = mod.parent;
122         for (; ancestor; ancestor = ancestor.parent)
123         {
124             if (ancestor == pkg)
125             {
126                 static if (LOG)
127                 {
128                     printf("\tsc is in permitted ancestor package for s\n");
129                 }
130                 return true;
131             }
132         }
133     }
134     static if (LOG)
135     {
136         printf("\tno package access\n");
137     }
138     return false;
139 }
140 
141 /****************************************
142  * Determine if scope sc has protected level access to cd.
143  */
144 private bool hasProtectedAccess(Scope *sc, Dsymbol s)
145 {
146     if (auto cd = s.isClassMember()) // also includes interfaces
147     {
148         for (auto scx = sc; scx; scx = scx.enclosing)
149         {
150             if (!scx.scopesym)
151                 continue;
152             auto cd2 = scx.scopesym.isClassDeclaration();
153             if (cd2 && cd.isBaseOf(cd2, null))
154                 return true;
155         }
156     }
157     return sc._module == s.getAccessModule();
158 }
159 
160 /****************************************
161  * Check access to d for expression e.d
162  * Returns true if the declaration is not accessible.
163  */
164 bool checkAccess(Loc loc, Scope* sc, Expression e, Dsymbol d)
165 {
166     if (sc.flags & SCOPE.noaccesscheck)
167         return false;
168     static if (LOG)
169     {
170         if (e)
171         {
172             printf("checkAccess(%s . %s)\n", e.toChars(), d.toChars());
173             printf("\te.type = %s\n", e.type.toChars());
174         }
175         else
176         {
177             printf("checkAccess(%s)\n", d.toPrettyChars());
178         }
179     }
180     if (d.isUnitTestDeclaration())
181     {
182         // Unittests are always accessible.
183         return false;
184     }
185 
186     if (!e)
187         return false;
188 
189     if (auto tc = e.type.isTypeClass())
190     {
191         // Do access check
192         ClassDeclaration cd = tc.sym;
193         if (e.op == EXP.super_)
194         {
195             if (ClassDeclaration cd2 = sc.func.toParent().isClassDeclaration())
196                 cd = cd2;
197         }
198         return checkAccess(cd, loc, sc, d);
199     }
200     else if (auto ts = e.type.isTypeStruct())
201     {
202         // Do access check
203         StructDeclaration cd = ts.sym;
204         return checkAccess(cd, loc, sc, d);
205     }
206     return false;
207 }
208 
209 /****************************************
210  * Check access to package/module `p` from scope `sc`.
211  *
212  * Params:
213  *   sc = scope from which to access to a fully qualified package name
214  *   p = the package/module to check access for
215  * Returns: true if the package is not accessible.
216  *
217  * Because a global symbol table tree is used for imported packages/modules,
218  * access to them needs to be checked based on the imports in the scope chain
219  * (see https://issues.dlang.org/show_bug.cgi?id=313).
220  *
221  */
222 bool checkAccess(Scope* sc, Package p)
223 {
224     if (sc._module == p)
225         return false;
226     for (; sc; sc = sc.enclosing)
227     {
228         if (sc.scopesym && sc.scopesym.isPackageAccessible(p, Visibility(Visibility.Kind.private_)))
229             return false;
230     }
231 
232     return true;
233 }
234 
235 /**
236  * Check whether symbols `s` is visible in `mod`.
237  *
238  * Params:
239  *  mod = lookup origin
240  *  s = symbol to check for visibility
241  * Returns: true if s is visible in mod
242  */
243 bool symbolIsVisible(Module mod, Dsymbol s)
244 {
245     // should sort overloads by ascending visibility instead of iterating here
246     s = mostVisibleOverload(s);
247     final switch (s.visible().kind)
248     {
249     case Visibility.Kind.undefined: return true;
250     case Visibility.Kind.none: return false; // no access
251     case Visibility.Kind.private_: return s.getAccessModule() == mod;
252     case Visibility.Kind.package_: return s.getAccessModule() == mod || hasPackageAccess(mod, s);
253     case Visibility.Kind.protected_: return s.getAccessModule() == mod;
254     case Visibility.Kind.public_, Visibility.Kind.export_: return true;
255     }
256 }
257 
258 /**
259  * Same as above, but determines the lookup module from symbols `origin`.
260  */
261 bool symbolIsVisible(Dsymbol origin, Dsymbol s)
262 {
263     return symbolIsVisible(origin.getAccessModule(), s);
264 }
265 
266 /**
267  * Same as above but also checks for protected symbols visible from scope `sc`.
268  * Used for qualified name lookup.
269  *
270  * Params:
271  *  sc = lookup scope
272  *  s = symbol to check for visibility
273  * Returns: true if s is visible by origin
274  */
275 bool symbolIsVisible(Scope *sc, Dsymbol s)
276 {
277     s = mostVisibleOverload(s);
278     return checkSymbolAccess(sc, s);
279 }
280 
281 /**
282  * Check if a symbol is visible from a given scope without taking
283  * into account the most visible overload.
284  *
285  * Params:
286  *  sc = lookup scope
287  *  s = symbol to check for visibility
288  * Returns: true if s is visible by origin
289  */
290 bool checkSymbolAccess(Scope *sc, Dsymbol s)
291 {
292     final switch (s.visible().kind)
293     {
294     case Visibility.Kind.undefined: return true;
295     case Visibility.Kind.none: return false; // no access
296     case Visibility.Kind.private_: return sc._module == s.getAccessModule();
297     case Visibility.Kind.package_: return sc._module == s.getAccessModule() || hasPackageAccess(sc._module, s);
298     case Visibility.Kind.protected_: return hasProtectedAccess(sc, s);
299     case Visibility.Kind.public_, Visibility.Kind.export_: return true;
300     }
301 }
302 
303 /**
304  * Use the most visible overload to check visibility. Later perform an access
305  * check on the resolved overload.  This function is similar to overloadApply,
306  * but doesn't recurse nor resolve aliases because visibility is an
307  * attribute of the alias not the aliasee.
308  */
309 public Dsymbol mostVisibleOverload(Dsymbol s, Module mod = null)
310 {
311     if (!s.isOverloadable())
312         return s;
313 
314     Dsymbol next, fstart = s, mostVisible = s;
315     for (; s; s = next)
316     {
317         // void func() {}
318         // private void func(int) {}
319         if (auto fd = s.isFuncDeclaration())
320             next = fd.overnext;
321         // template temp(T) {}
322         // private template temp(T:int) {}
323         else if (auto td = s.isTemplateDeclaration())
324             next = td.overnext;
325         // alias common = mod1.func1;
326         // alias common = mod2.func2;
327         else if (auto fa = s.isFuncAliasDeclaration())
328             next = fa.overnext;
329         // alias common = mod1.templ1;
330         // alias common = mod2.templ2;
331         else if (auto od = s.isOverDeclaration())
332             next = od.overnext;
333         // alias name = sym;
334         // private void name(int) {}
335         else if (auto ad = s.isAliasDeclaration())
336         {
337             assert(ad.isOverloadable || ad.type && ad.type.ty == Terror,
338                 "Non overloadable Aliasee in overload list");
339             // Yet unresolved aliases store overloads in overnext.
340             if (ad.semanticRun < PASS.semanticdone)
341                 next = ad.overnext;
342             else
343             {
344                 /* This is a bit messy due to the complicated implementation of
345                  * alias.  Aliases aren't overloadable themselves, but if their
346                  * Aliasee is overloadable they can be converted to an overloadable
347                  * alias.
348                  *
349                  * This is done by replacing the Aliasee w/ FuncAliasDeclaration
350                  * (for functions) or OverDeclaration (for templates) which are
351                  * simply overloadable aliases w/ weird names.
352                  *
353                  * Usually aliases should not be resolved for visibility checking
354                  * b/c public aliases to private symbols are public. But for the
355                  * overloadable alias situation, the Alias (_ad_) has been moved
356                  * into its own Aliasee, leaving a shell that we peel away here.
357                  */
358                 auto aliasee = ad.toAlias();
359                 if (aliasee.isFuncAliasDeclaration || aliasee.isOverDeclaration)
360                     next = aliasee;
361                 else
362                 {
363                     /* A simple alias can be at the end of a function or template overload chain.
364                      * It can't have further overloads b/c it would have been
365                      * converted to an overloadable alias.
366                      */
367                     assert(ad.overnext is null, "Unresolved overload of alias");
368                     break;
369                 }
370             }
371             // handled by dmd.func.overloadApply for unknown reason
372             assert(next !is ad); // should not alias itself
373             assert(next !is fstart); // should not alias the overload list itself
374         }
375         else
376             break;
377 
378         /**
379         * Return the "effective" visibility attribute of a symbol when accessed in a module.
380         * The effective visibility attribute is the same as the regular visibility attribute,
381         * except package() is "private" if the module is outside the package;
382         * otherwise, "public".
383         */
384         static Visibility visibilitySeenFromModule(Dsymbol d, Module mod = null)
385         {
386             Visibility vis = d.visible();
387             if (mod && vis.kind == Visibility.Kind.package_)
388             {
389                 return hasPackageAccess(mod, d) ? Visibility(Visibility.Kind.public_) : Visibility(Visibility.Kind.private_);
390             }
391             return vis;
392         }
393 
394         if (next &&
395             visibilitySeenFromModule(mostVisible, mod) < visibilitySeenFromModule(next, mod))
396             mostVisible = next;
397     }
398     return mostVisible;
399 }