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 }