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