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