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