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