A package-protected class can be made visible outside the package by extending

This works. Should it?

class ExtensionBase {
    final String field;

    ExtensionBase(String field) {
        this.field = field;
    }

    ExtensionBase() {
        field = "none";
    }

    public String toString() { return field; }
}

class PackageScope(name :String) extends ExtensionBase(name)

package not_root {
	class PublicScope extends PackageScope("Now ExtensionBase is made public")

	object Test extends App {
		println(new PublicScope)
	}
}

Frankly I noticed that quite a while ago, but was sure it’s a bug that would cause the JVM to throw an exception, but it seems it works just fine.

For what it’s worth, it is at least consistent with method overrides, which can also make package-scoped methods visible to the outside by subclasses.

I’m not sure which access you object to, but I did lodge this question with Scala 3, when I was learning about “top-level definitions”.

The terminology is not “root package” but “empty package” (where the name is empty). Things in the empty package are visible to each other, but you can’t import from the empty package. So the additional rule is that things in the empty package are visible in the file where they are defined.

I see there is a code comment in the fix for that issue:

     *    - Members of the empty package can be accessed only from within the empty package.
     *      Note: it would be cleaner to never nest package definitions in empty package definitions,
     *      but then we'd have to give up the fiction that a compilation unit consists of
     *      a single tree (because a source file may have both toplevel classes which go
     *      into the empty package and package definitions, which would have to stay outside).
     *      Since the principle of a single tree per compilation unit is assumed by many
     *      tools, we did not want to take that step.

There are old Scala 2 tickets about things seeming to leak from package privacy, and there are a few puzzlers or limitations in that area. I’m not sure you intend those cases.

That’s what I get for minimizing too far…
This also works:

package priv;

class ExtensionBase {
    final String field;

    ExtensionBase(String field) {
        this.field = field;
    }

    ExtensionBase() {
        field = "none";
    }

    public String toString() { return field; }
}

import priv.PackageScope

package priv {
	class PackageScope(name :String) extends ExtensionBase(name)
}

package not_root {
	class PublicScope extends PackageScope("Now ExtensionBase is made public")

	object Test extends App {
		println(new PublicScope)
	}
}

Also, the name may be ‘empty package’, but it’s actually referred to in code as _root_ where distinction is necessary.

Hi.

I don’t get what you mean with “Now ExtensionBase is made public”?

It is the base class of a public class you are extending from… seems pretty legit to me – should work the same in Java.

Frankly I haven’t tried it in Java, but you right. To me however, a situation in which anything declared as package protected becomes visible outside the package in any way seems like an issue. If I hide something, I don’t want anyone else creating dependencies on it. Here the class (or rather, its API) is made visible for everyone.

While now I can see perhaps some use in this in implementation inheritance, where the user does not depend (at least in the sources) on the protected class, only its methods exposed through references to the public class, I don’t know if it’s the better choice. If it’s per spec, then I guess my question is answered.

1 Like

Just to clarify, _root_ is a kind of pragma for “rooting” an import to ensure that it’s not a package-relative import.

The “empty package” is different. One way the distinction is important is with nested package clauses, which make members of “outside” packages visible. One can’t use package _root_ to access members of the empty package. I just happened to get an error that uses the internal name of the empty package:

call site: object Foo in package <empty>

I went looking for those similar tickets and went down the wrong rabbit hole.

This is similar: https://github.com/scala/bug/issues/4559
or this: https://github.com/scala/bug/issues/10366
or about qualified private: https://github.com/scala/bug/issues/12128

The umbrella ticket is: https://github.com/scala/bug/issues/6794

Scala 3: https://github.com/lampepfl/dotty/issues/13979

I’d argue ExtensionBase and its package-private parts aren’t visible outside it’s package. You can’t access it by its name, nor can you access any of its package-private members on PublicScope (e.g. new PublicScope.field will not compile, val x: ExtensionBase = new PublicScope also won’t).

The client code doesn’t know about the base class per se, it only knows the public API of PackageScope. So you always may remove ExtensionBase, as long as PackageScope implements its public API. I don’t think this kind of indirect dependency is a problem.

1 Like

I agree this trick can be used to bypass information hiding (when it works) and monkey-patch Java libraries — I’ve done it. OTOH, using reflection is probably more powerful, just hard to use.

You’re technically right in a sense, but packages remain an ineffective way to enforce information hiding. If you stick to what’s guaranteed by the compiler, package-private members are effectively part of the public API of a library, and changing them in any way can break source/binary compatibility with existing users, or simply make existing clients misbehave at runtime, even when fully preserving the API of public and protected members. I assume most library authors will refuse to give any support for code playing such tricks, but this is just an unenforced convention.

I’ve heard of a couple of solutions but I have not investigated closely. JVM modules (from Java 9) seemed like they might give stronger guarantees; IIUC, modules align with “collection of code from an author/organization”, so if the authors of module1 make something private to a module, the authors of module2 cannot access it.

Before Java 9, I think OSGi also had some of the benefits: in particular IIUC, OSGi will refuse load modules that extend packages from other components. OTOH, OSGi’s learning curve seemed extremely steep.