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