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