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