How do generics (Vector) work inside the AVM? - apache-flex

Support for generics (currently only Vector.<*>, and called 'postfix type parameters' by Adobe) was added in Flash Player 10, but the only AVM2 documentation does not describe how these objects are accessed.
Specifically, I noticed a new opcode (0x53) and a new multiname kind (0x1D) that seem relevant, but their usage is not documented.
NB: This question was created with the answer already known as it is more easily found here than on my blog or the Adobe Bug DB.

The reverse engineering work I did on this did not include declaring your own generic types, though it's very likely possible.
References to the declaring (parameterless) generic type (Vector) are made through a regular qualified name (though any multiname should do).
References to a typed generic type (Vector.<int> as opposed to Vector.<>) are made by a new multiname kind (0x1D), which I call GenericName. GenericName has a format like so:
[Kind] [TypeDefinition] [ParamCount] [Param1] [Param2] [ParamN]
Where:
[TypeDefinition] is a U30 into the multiname table
[ParamCount] is a U8 (U30?) of how many type parameters there are
[ParamX] is a U30 into the multiname table.
Obviously generics are not generally supported yet, so ParamCount will always be 1 (for Vector.<*>).
The other interesting thing is how instances of the class are created. A new opcode was added in Flash 10 (0x53), which I will call MakeGenericType. MakeGenericType is declared with the following stack:
TypeDefinition, ParameterType1, ParameterTypeN -> GenericType
It also has one parameter, a U8 (U30?) specifying how many parameters are on the stack. You will generally see MakeGenericType being used like this:
GetLex [TypeDefinitionMultiname]
GetLex [ParameterTypeMultiname]
MakeGeneric [ParamCount]
Coerce [GenericNameMultiname]
Construct [ConstructorParamCount]
So if you had the following...
GetLex __AS3__.vec::Vector
GetLex int
MakeGeneric 1
Coerce __AS3__.vec::Vector.<int>
Construct 0
You would now have an instance of Vector.<int>

Related

Julialang: Enforcing interfaces on Abstract Types

I have been trying to understand the type system for Julialang but some design aspects are still confusing me. I was hoping someone could clarify.
So the question here is about Abstract Types and their concrete implementations. From what I understand Julia Abstract types do not enforce any constraints on their concrete implementations. So there is no guarantee that a method that works on the Abstract type will work on a concrete implementation of that type.
I get that Julia does not use classes or follow inheritance. But I just want to avoid generating all sorts of bugs in my code. If there is a different design paradigm, then could someone please answer question 2 below.
So I have 2 questions.
Is this still the way that the language works? Just to confirm nothing has changed since the blog post.
How do users design their software around this seeming vulnerability?
And example of the issue from the linked post:
abstract type AbstractPerson end
abstract type AbstractStudent <: AbstractPerson end
abstract type AbstractTeacher <: AbstractPerson end
struct Person <: AbstractPerson
name::String
end
struct Student <: AbstractStudent
name::String
grade::Int
hobby::String
end
struct MusicStudent <: AbstractStudent
grade::Int
end
Now if I create some methods on the abstract type.
get_name(x::AbstractPerson) = x.name
p1 = Person("elroy")
get_name(p1)
>"elroy"
So even if MusicStudent is a subtype of AbstractPerson, the MusicStudent DOES NOT have a name attribute. That means that to following behavior is observed.
m1 = MusicStudent(10)
get_name(m1)
ERROR: type MusicStudent has no field name
Stacktrace:
[1] getproperty(::Any, ::Symbol) at ./sysimg.jl:18
[2] get_name(::MusicStudent) at ./In[2]:1
[3] top-level scope at In[13]:2
So the problem here is that Julia allows me to instantiate the type variable m1 with essentially an incomplete constructor. And it only gives me an error when I try to run that function.
So that means if I write a function for the Abstract Type, I can't guarantee every concrete implementation of that type has the same interface. That seems like it will make very fragile code as the developer won't know which types implement which attributes and methods.
Isn't this kind of behavior just a bug in the implementation of the Persons? If you really want the behavior to go without exception you can define a default method:
julia> get_name(p::AbstractPerson) = try return p.name catch y return "" end
get_name (generic function with 1 method)
julia> m1 = MusicStudent(10)
MusicStudent(10)
julia> get_name(m1)
""
I think the underlying struggle may be that in Julia you cannot inherit the data field called "name" as part of the object hierarchy. There is a nice discussion of that genuine issue here (see the mention of the #forward macro):
https://discourse.julialang.org/t/composition-and-inheritance-the-julian-way/11231
The basic answer is that in julia the interface of a method is thought of as the methods that are defined to take an element of that type. AbstractArray for example specifies that implementations should implement getIndex and size. The reason to not make fields part of the interface is that not doing so allows for memory efficient code, since each type can define the methods in the most sensible way. For example if I want to make a type called Bob that is a subtype for all people named bob, I don't want to store his name every time. by using methods, Julia allows much more potential for future expansion in unexpected ways.
Technically this approach loses "safety", but the only way it does is if you write code using fields that might not exist, in which case you will get an error. This type of safety isn't that useful since it just gives you a compile error that slows down development.

Define a jsonable type using mypy / PEP-526

Values that can be converted to a JSON string via json.dumps are:
Scalars: Numbers and strings
Containers: Mapping and Iterable
Union[str, int, float, Mapping, Iterable]
Do you have a better suggestion?
Long story short, you have the following options:
If you have zero idea how your JSON is structured and must support arbitrary JSON blobs, you can:
Wait for mypy to support recursive types.
If you can't wait, just use object or Dict[str, object]. It ends up being nearly identical to using recursive types in practice.
If you don't want to constantly have to type-check your code, use Any or Dict[str, Any]. Doing this lets you avoid needing to sprinkle in a bunch of isinstance checks or casts at the expense of type safety.
If you know precisely what your JSON data looks like, you can:
Use a TypedDict
Use a library like Pydantic to deserialize your JSON into an object
More discussion follows below.
Case 1: You do not know how your JSON is structured
Properly typing arbitrary JSON blobs is unfortunately awkward to do with PEP 484 types. This is partly because mypy (currently) lacks recursive types: this means that the best we can do is use types similar to the one you constructed.
(We can, however, make a few refinements to your type. In particular, json.Dumps(...) actually does not accept arbitrary iterables. A generator is a subtype of Iterable, for example, but json.dumps(...) will refuse to serialize generators. You probably want to use something like Sequence instead.)
That said, having access to recursive types may not end up helping that much either: in order to use such a type, you would need to start sprinkling in isinstance checks or casts into your code. For example:
JsonType = Union[None, int, str, bool, List[JsonType], Dict[JsonType]]
def load_config() -> JsonType:
# ...snip...
config = load_config()
assert isinstance(config, dict)
name = config["name"]
assert isinstance(name, str)
So if that's the case, do we really need the full precision of recursive types? In most cases, we can just use object or Dict[str, object] instead: the code we write at runtime is going to be nearly the same in either case.
For example, if we changed the example above to use JsonType = object, we would still end up needing both asserts.
Alternatively, if you find sprinkling in assert/isinstance checks to be unnecessary for your use case, a third option is to use Any or Dict[str, Any] and have your JSON be dynamically typed.
It's obviously less precise than the options presented above, but asking mypy to not type check uses of your JSON dict and relying on runtime exceptions instead can sometimes be more ergonomic in practice.
Case 2: You know how your JSON data will be structured
If you do not need to support arbitrary JSON blobs and can assume it forms a particular shape, we have a few more options.
The first option is to use TypedDicts instead. Basically, you construct a type explicitly specifying what a particular JSON blob is expected to look like and use that instead. This is more work to do, but can let you gain more type-safety.
The main disadvantage of using TypedDicts is that it's basically the equivalent of a giant cast in the end. For example, if you do:
from typing import TypedDict
import json
class Config(TypedDict):
name: str
env: str
with open("my-config.txt") as f:
config: Config = json.load(f)
...how do we know that my-config.txt actually matches this TypedDict?
Well, we don't, not for certain.
This can be fine if you have full control over where the JSON is coming from. In this case, it might be fine to not bother validating the incoming data: just having mypy check uses of your dict is good enough.
But if having runtime validation is important to you, your options are to either implement that validation logic yourself or use a 3rd party library that can do it on your behalf, such as Pydantic:
from pydantic import BaseModel
import json
class Config(BaseModel):
name: str
env: str
with open("my-config.txt") as f:
# The constructor will raise an exception at runtime
# if the input data does not match the schema
config = Config(**json.load(f))
The main advantage of using these types of libraries is that you get full type safety. You can also use object attribute syntax instead of dict lookups (e.g. do config.name instead of config["name"]), which is arguably more ergonomic.
The main disadvantage is doing this validation does add some runtime cost, since you're now scanning over the entire JSON blob. This might end up introducing some non-trivial slowdowns to your code if your JSON happens to contain a large quantity of data.
Converting your data into an object can also sometimes be a bit inconvenient, especially if you plan on converting it back into a dict later on.
There has been a lengthy discussion (https://github.com/python/typing/issues/182) about the possibility of introducing a JSONType; however, no definitive conclusion has yet been reached.
The current suggestion is to just define JSONType = t.Union[str, int, float, bool, None, t.Dict[str, t.Any], t.List[t.Any]] or something similar in your own code.

Ada Function Parameter as Access Type or Not

I am refactoring some code originally written using access types, but not yet tested. I found access types to be troublesome in Ada because they can only refer to dynamically allocated items, and referring to items defined at compile time is apparently not allowed. (This is Ada83.)
But now I come to a function like this one:
function Get_Next(stateInfo : State_Info_Access_Type) return integer;
I know that I can easily pass parameter "contents" of an access type rather than the access pointer itself, so I am considering writing this as
function Get_Next(stateInfoPtr : State_Info_Type) return integer;
where State_Info_Type is the type that State_Info_Access_Type refers to.
With this refactor, for all intents and purposes I think I'm still really passing what amounts to an implicit pointer back to the contents (using the .all) syntax).
I want to refactor and test starting with the lowest level functions, working my way up the call chains. My goal is to push the access types out of the code as I go.
Am I understanding this correctly or am I missing something?
I think original author(s), and possibly OP are missing a point, that is, how Ada parameter modes work.
To quote #T.E.D
Every Ada compiler I know of under the hood passes objects larger than fit in a machine register by reference. It is the compiler, not the details of your parameter passing mechanisim, that enforces not writing data back out of the routine.
Ada does this automatically, and leaves the parameter modes as a way of describing the flow of information (Its NOT the C style reference / value conundrum). See the useful wikibook.
What worries me is that the code you have inherited looks like the author has used the explicit access parameter type as a way of getting functions to have side effects (usually considered a bad thing in Ada - World).
My recommendation is to change your functions to:
function Get_Next(State_Info : in State_Info_Type) return Integer;
and see if the compiler tells you if you are trying to modify State_Info. If so, you may need to change your functions to procedures like this:
procedure Get_Next(State_Info : in out State_Info_Type;
Result : out Integer);
This explicitly shows the flow of information without needing to know the register size or the size of State_Info_Type.
As an aside Ada 2012 Will allow you to have functions that have in out parameters.
To quote #T.E.D,
Every Ada compiler I know of under the hood passes objects larger than fit in a machine register by reference. It is the compiler, not the details of your parameter passing mechanisim, that enforces not writing data back out of the routine.
Since this code hasn’t yet been tested, I think you are completely right to rework what looks like code written with a C mindset. But, you oughtn’t to mention pointers at all; you suggested
function Get_Next(stateInfoPtr : State_Info_Type) return integer;
but this would be better as
function Get_Next(stateInfo : State_Info_Type) return integer;
or even (IMO) as
function Get_Next(State_Info : State_Info_Type) return Integer;
to use more standard Ada styling! My editor (Emacs, but GPS can do this too) will change state_info into State_Info on the fly.
As an afterthought, you might be able to get rid of State_Info_Type_Pointer altogether. You mention .all, so I guess you’ve got
SITP : State_Info_Type_Pointer := new State_Info_Type;
... set up components
Next := Get_Next (SITP.all);
but what about
SIT : State_Info_Type;
... set up components
Next := Get_Next (SIT);
I wouldn't recommend this, but you can get pointers to variables in Ada 83 by using 'Address.
You can then use overlays (again this is all Ada83 stuff) to achieve access...
function something(int_address : Address) return integer
is
x : integer;
for x'address use int_address;
begin
-- play with x as you will.

Fundamentals of Ada's T'Class

Somewhat embarassed to ask this, but I know it's for the best. I've been programming in Ada for many years now, and understand nearly every part of the language fluently. However, I've never seemed able to wrap my head around T'Class. To borrow from others, can someone "explain it like I'm five?".
Edit: I bought it just to have, but contained within is a great description of, and example use of, T'Class; I refer to “Software Construction and Data Structures with Ada 95” by Michael B. Feldman.
If you start with
package P1 is
type T is tagged private;
procedure Method (Self : T);
end P1;
package P2 is
procedure Proc (Self : T); -- not a primitive
procedure Proc2 (Self : T'Class);
end P2;
In the case of Proc, you are telling the compiler that the parameter should always be considered precisely as of type T (remember that a tagged type is always passed by reference, so the actual type could be derived from T of course, you would not lose the extra data). In particular, that means that within the body of Proc, all calls to Method will be exactly calls to P1.Method, never a call to an overriding Method.
In the case of Proc2, you are telling the compiler that you do not know the exact type statically, so it will need to insert extra code to resolve things at run time. A call to Method, within the body of Proc2, could be call to P1.Method, or to another overriding Method.
Basically: with 'Class, things are resolved at runtime.
Well, if you were five, I would say that T'Class represents the whole family of T.
By family, we mean children and grand-children and grand-grand-children.
As you're not five, it means that this special type represents every tagged type which is in the inheritance tree of T. This way, if you use this type as a parameter, you can use every parameter which has T as ancestor directly or not.
For more information, you can read the wikibooks on this.

Invoking a primitive operation via dot operator fails

I have a problem understanding how the UFCS (Universal Function Call Syntax) works in Ada.
Let's say I have a type, like:
package People
type Person is tagged private;
-- This procedure is a primitive operation:
procedure Say_Name (Person_Object : in Person);
private
type Person is tagged record
Name : String;
end record;
end People;
then I can call the procedure as if it actually belonged to the Person type:
Some_Person_Instance.Say_Name;
Now that works, but in my particular instance it doesn't make sense to have a record, and a subtype would suffice.
subtype Person is String;
At this point (assuming I changed the procedure's workings), it fails to compile and I get the error:
invalid prefix in selected component "Person".
Why? It doesn't even help if I do:
type Person is new String;
Does UFCS only work for records?
I apologize if this is an inane question, but I've no study materials for Ada (apart for couple of e-books) and the textbook I ordered hasn't arrived yet.
UFCS is a full feature of the D language. For historical reasons, Ada has mixed approaches to calls in different parts of the language.
Ordinary subprogram calls are dealt with in ARM 6.4, and look like Subprogram_Name (Parameters) (or just Subprogram_Name if there are no parameters).
Protected subprogram calls (ARM 9.5.1) and entry calls (ARM 9.5.3) look like Object.Subprogram_Or_Entry_Name (Parameters).
Primitive subprograms of tagged types, however, can be called either way; either as an ordinary call, or, if the tagged parameter is the first parameter, using the prefix notation (ARM 4.1.3(9.1)).
There is discussion of this design in AI95-00252; apparently the designers did consider allowing both call forms for all types, but there were too many complications and too few benefits. A shame, I think we all agree, though perhaps it can be taken too far; the D example (from here)
values.multiply(10).divide(3).evens.writeln;
might be a case in point!
With regard to learning Ada and Web resources, have a look at the Ada Resource Association’s resource list.

Resources