Clojure

Compiler sometimes gives constant collections types which mismatch with their runtime values

Details

  • Type: Defect Defect
  • Status: Closed Closed
  • Priority: Major Major
  • Resolution: Completed
  • Affects Version/s: Release 1.3, Release 1.4, Release 1.5
  • Fix Version/s: Release 1.6
  • Component/s: None
  • Labels:
  • Environment:
    Clojure 1.3 om Mac OS 10.7, Clojure 1.5.0 alpha1 on Linux x86_64 (OpenJDK 1.7.0 b147)
  • Patch:
    Code and Test
  • Approval:
    Ok

Description

(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true

The problem is not present in Clojure 1.2.1.

Cause: In the first example, the Compiler will parse the MapExpr (which reports a Java class of IPersistentMap) into a ConstantExpr whose value is a PersistentHashMap (and reports type as same). The .containsKey() call is a HostExpr which parses to an InstanceMethodExpr.

When emitted to bytecode, the ConstantExpr becomes a call to RT.map() to generate a map. RT.map() produces either a PersistentArrayMap or PersistentHashMap depending on size. The InstanceMethodExpr becomes a call to PersistentHashMap.containsKey() (based on the reported type of it's expression). In the case where RT.map() will produce a PersistentArrayMap at runtime (small maps), this causes a mismatch.

Note also that this same scenario occurs for constant sets, vectors, etc - all of those cases produce ConstantExpr's with the type of the concrete data structure, instead of the more general IPersistentMap, IPersistentSet, etc. All of those scenarios should be addressed in the same way. But, those interfaces do not capture the full capabilities of the returned object. The compiler and the marshaller need to agree on the types to produce.

Approach: One approach (A) explored previously was to have MapExpr always produce a ConstantExpr containing a value of the same type that will be produced at runtime by RT.map(). While this works, it is also fragile, and sidesteps the real issue that MapExpr creates a ConstantExpr with a type that is too specific. Rich said this is the wrong approach.

(B) Another approach is to create an anonymous subclass of ConstantExpr in MapExpr that overrides getJavaClass() to report a more general type. This isn't done anywhere else and it may have negative ramifications (but I have not found any).

(C) Another approach is to follow the path of example #2 above. That example wraps the map in a MetaExpr which reports getJavaClass() returning the hinted type. In the case above, it hints the concrete type that happens to match the return of RT.map(). Instead, we could wrap the ConstantExpr in a MetaExpr hinted on the abstract type IPersistentMap (the return type of RT.map()).

RH - a more general (interface) type will not give full capabilities. Perhaps APersistent*

Patch:

  1. 0001-Fix-for-CLJ-944.patch
    30/Oct/12 5:02 PM
    0.9 kB
    Nicola Mometto
  2. 0002-Fix-for-CLJ-944.patch
    01/Nov/12 3:48 PM
    1 kB
    Nicola Mometto
  3. clj944-plus-tests.patch
    12/Apr/13 3:57 PM
    5 kB
    Stuart Halloway

Activity

Michel Alexandre Salim made changes -
Field Original Value New Value
Environment Clojure 1.3 om Mac OS 10.7 Clojure 1.3 om Mac OS 10.7, Clojure 1.5.0 alpha1 on Linux x86_64 (OpenJDK 1.7.0 b147)
Affects Version/s Release 1.5 [ 10150 ]
Affects Version/s Release 1.4 [ 10040 ]
Affects Version/s Release 1.3 [ 10038 ]
Nicola Mometto made changes -
Attachment 0001-Fix-for-CLJ-944.patch [ 11644 ]
Nicola Mometto made changes -
Attachment 0002-Fix-for-CLJ-944.patch [ 11655 ]
Andy Fingerhut made changes -
Patch Code [ 10001 ]
Stuart Halloway made changes -
Approval Triaged [ 10120 ]
Rich Hickey made changes -
Approval Triaged [ 10120 ] Vetted [ 10003 ]
Priority Minor [ 4 ] Major [ 3 ]
Rich Hickey made changes -
Fix Version/s Release 1.6 [ 10157 ]
Fix Version/s Release 1.5 [ 10150 ]
Stuart Halloway made changes -
Approval Vetted [ 10003 ] Screened [ 10004 ]
Description Hi guys, I am getting the following exception:

{code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The mailing list suggest that the compiler injects an incorrect cast to clojure.lang.PersistentHashMap. In this case it should probably be cast to a clojure.lang.Associative, the highest common interface having the .containsKey method.

The problem is not present in Clojure 1.2.1.
Patch clj944-plus-tests does three things:

* includes the previous "0002" patch which has the compiler emit map types consistent with the reader
* adds tests
* removes tests that were broken all along and now symptomatic

Hi guys, I am getting the following exception:

{code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The mailing list suggest that the compiler injects an incorrect cast to clojure.lang.PersistentHashMap. In this case it should probably be cast to a clojure.lang.Associative, the highest common interface having the .containsKey method.

The problem is not present in Clojure 1.2.1.
Attachment clj944-plus-tests.patch [ 11955 ]
Rich Hickey made changes -
Approval Screened [ 10004 ] Vetted [ 10003 ]
Stuart Halloway made changes -
Patch Code [ 10001 ] Code and Test [ 10002 ]
Approval Vetted [ 10003 ] Screened [ 10004 ]
Description Patch clj944-plus-tests does three things:

* includes the previous "0002" patch which has the compiler emit map types consistent with the reader
* adds tests
* removes tests that were broken all along and now symptomatic

Hi guys, I am getting the following exception:

{code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The mailing list suggest that the compiler injects an incorrect cast to clojure.lang.PersistentHashMap. In this case it should probably be cast to a clojure.lang.Associative, the highest common interface having the .containsKey method.

The problem is not present in Clojure 1.2.1.
I (Stu) agree with Nicola's assessment in the comments: the single real problem here is that the compiler's MapExpr parser emits maps differently from other map-makers, e.g. RT's factory functions.

Patch clj944-plus-tests does three things:

* has the compiler emit map types consistent with the reader (like the previous "0002" patch)
* adds tests
* removes broken tests

Original report follows:

Hi guys, I am getting the following exception:

{code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The mailing list suggest that the compiler injects an incorrect cast to clojure.lang.PersistentHashMap. In this case it should probably be cast to a clojure.lang.Associative, the highest common interface having the .containsKey method.

The problem is not present in Clojure 1.2.1.
Summary (.containsKey {:one 1} :one) throws Exception compiler makes different concrete maps then the reader
Alex Miller made changes -
Description I (Stu) agree with Nicola's assessment in the comments: the single real problem here is that the compiler's MapExpr parser emits maps differently from other map-makers, e.g. RT's factory functions.

Patch clj944-plus-tests does three things:

* has the compiler emit map types consistent with the reader (like the previous "0002" patch)
* adds tests
* removes broken tests

Original report follows:

Hi guys, I am getting the following exception:

{code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The mailing list suggest that the compiler injects an incorrect cast to clojure.lang.PersistentHashMap. In this case it should probably be cast to a clojure.lang.Associative, the highest common interface having the .containsKey method.

The problem is not present in Clojure 1.2.1.
{code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The problem is not present in Clojure 1.2.1.

*Cause:* I (Stu) agree with Nicola's assessment in the comments: the single real problem here is that the compiler's MapExpr parser emits maps differently from other map-makers, e.g. RT's factory functions.

*Patch:* clj944-plus-tests

*Solution:* Make MapExpr in compiler emit the same kinds of maps (based on the PersistentArrayMap.HASHTABLE_THRESHOLD) that are emitted by other map-makers.

The patch does three things:
* has the compiler emit map types consistent with the reader (like the previous "0002" patch)
* adds tests
* removes broken tests
Summary compiler makes different concrete maps then the reader Compiler makes different concrete maps then the reader
Rich Hickey made changes -
Approval Screened [ 10004 ] Incomplete [ 10006 ]
Alex Miller made changes -
Assignee Alex Miller [ alexmiller ]
Alex Miller made changes -
Labels compiler
Gary Fredericks made changes -
Summary Compiler makes different concrete maps then the reader Compiler makes different concrete maps than the reader
Alex Miller made changes -
Description {code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The problem is not present in Clojure 1.2.1.

*Cause:* I (Stu) agree with Nicola's assessment in the comments: the single real problem here is that the compiler's MapExpr parser emits maps differently from other map-makers, e.g. RT's factory functions.

*Patch:* clj944-plus-tests

*Solution:* Make MapExpr in compiler emit the same kinds of maps (based on the PersistentArrayMap.HASHTABLE_THRESHOLD) that are emitted by other map-makers.

The patch does three things:
* has the compiler emit map types consistent with the reader (like the previous "0002" patch)
* adds tests
* removes broken tests
{code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The problem is not present in Clojure 1.2.1.

*Cause:* In the first example, the Compiler will parse the MapExpr (which reports a Java class of IPersistentMap) into a ConstantExpr whose value is a PersistentHashMap (and reports type as same). The .containsKey() call is a HostExpr which parses to an InstanceMethodExpr.

When emitted to bytecode, the ConstantExpr becomes a call to RT.map() to generate a map. RT.map() produces either a PersistentArrayMap or PersistentHashMap depending on size. The InstanceMethodExpr becomes a call to PersistentHashMap.containsKey() (based on the reported type of it's expression). In the case where RT.map() will produce a PersistentArrayMap at runtime (small maps), this causes a mismatch.

Note also that this same scenario occurs for constant sets, vectors, etc - all of those cases produce ConstantExpr's with the type of the concrete data structure, instead of the more general IPersistentMap, IPersistentSet, etc. All of those scenarios should be addressed in the same way.

*Approach:* One approach (A) explored previously was to have MapExpr always produce a ConstantExpr containing a value of the same type that will be produced at runtime by RT.map(). While this works, it is also fragile, and sidesteps the real issue that MapExpr creates a ConstantExpr with a type that is too specific. Rich said this is the wrong approach.

(B) Another approach is to create an anonymous subclass of ConstantExpr in MapExpr that overrides getJavaClass() to report a more general type. This isn't done anywhere else and it may have negative ramifications (but I have not found any).

(C) Another approach is to follow the path of example #2 above. That example wraps the map in a MetaExpr which reports getJavaClass() returning the hinted type. In the case above, it hints the concrete type that happens to match the return of RT.map(). Instead, we could wrap the ConstantExpr in a MetaExpr hinted on the abstract type IPersistentMap (the return type of RT.map()).

*Patch:*

Rich Hickey made changes -
Summary Compiler makes different concrete maps than the reader Compiler sometimes gives constant collections types which mismatch with their runtime values
Rich Hickey made changes -
Description {code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The problem is not present in Clojure 1.2.1.

*Cause:* In the first example, the Compiler will parse the MapExpr (which reports a Java class of IPersistentMap) into a ConstantExpr whose value is a PersistentHashMap (and reports type as same). The .containsKey() call is a HostExpr which parses to an InstanceMethodExpr.

When emitted to bytecode, the ConstantExpr becomes a call to RT.map() to generate a map. RT.map() produces either a PersistentArrayMap or PersistentHashMap depending on size. The InstanceMethodExpr becomes a call to PersistentHashMap.containsKey() (based on the reported type of it's expression). In the case where RT.map() will produce a PersistentArrayMap at runtime (small maps), this causes a mismatch.

Note also that this same scenario occurs for constant sets, vectors, etc - all of those cases produce ConstantExpr's with the type of the concrete data structure, instead of the more general IPersistentMap, IPersistentSet, etc. All of those scenarios should be addressed in the same way.

*Approach:* One approach (A) explored previously was to have MapExpr always produce a ConstantExpr containing a value of the same type that will be produced at runtime by RT.map(). While this works, it is also fragile, and sidesteps the real issue that MapExpr creates a ConstantExpr with a type that is too specific. Rich said this is the wrong approach.

(B) Another approach is to create an anonymous subclass of ConstantExpr in MapExpr that overrides getJavaClass() to report a more general type. This isn't done anywhere else and it may have negative ramifications (but I have not found any).

(C) Another approach is to follow the path of example #2 above. That example wraps the map in a MetaExpr which reports getJavaClass() returning the hinted type. In the case above, it hints the concrete type that happens to match the return of RT.map(). Instead, we could wrap the ConstantExpr in a MetaExpr hinted on the abstract type IPersistentMap (the return type of RT.map()).

*Patch:*

{code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The problem is not present in Clojure 1.2.1.

*Cause:* In the first example, the Compiler will parse the MapExpr (which reports a Java class of IPersistentMap) into a ConstantExpr whose value is a PersistentHashMap (and reports type as same). The .containsKey() call is a HostExpr which parses to an InstanceMethodExpr.

When emitted to bytecode, the ConstantExpr becomes a call to RT.map() to generate a map. RT.map() produces either a PersistentArrayMap or PersistentHashMap depending on size. The InstanceMethodExpr becomes a call to PersistentHashMap.containsKey() (based on the reported type of it's expression). In the case where RT.map() will produce a PersistentArrayMap at runtime (small maps), this causes a mismatch.

Note also that this same scenario occurs for constant sets, vectors, etc - all of those cases produce ConstantExpr's with the type of the concrete data structure, instead of the more general IPersistentMap, IPersistentSet, etc. All of those scenarios should be addressed in the same way. But, those interfaces do not capture the full capabilities of the returned object. The compiler and the marshaller need to agree on the types to produce.

*Approach:* One approach (A) explored previously was to have MapExpr always produce a ConstantExpr containing a value of the same type that will be produced at runtime by RT.map(). While this works, it is also fragile, and sidesteps the real issue that MapExpr creates a ConstantExpr with a type that is too specific. Rich said this is the wrong approach.

(B) Another approach is to create an anonymous subclass of ConstantExpr in MapExpr that overrides getJavaClass() to report a more general type. This isn't done anywhere else and it may have negative ramifications (but I have not found any).

(C) Another approach is to follow the path of example #2 above. That example wraps the map in a MetaExpr which reports getJavaClass() returning the hinted type. In the case above, it hints the concrete type that happens to match the return of RT.map(). Instead, we could wrap the ConstantExpr in a MetaExpr hinted on the abstract type IPersistentMap (the return type of RT.map()).

RH - a more general type will not give full capabilities.

*Patch:*

Rich Hickey made changes -
Description {code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The problem is not present in Clojure 1.2.1.

*Cause:* In the first example, the Compiler will parse the MapExpr (which reports a Java class of IPersistentMap) into a ConstantExpr whose value is a PersistentHashMap (and reports type as same). The .containsKey() call is a HostExpr which parses to an InstanceMethodExpr.

When emitted to bytecode, the ConstantExpr becomes a call to RT.map() to generate a map. RT.map() produces either a PersistentArrayMap or PersistentHashMap depending on size. The InstanceMethodExpr becomes a call to PersistentHashMap.containsKey() (based on the reported type of it's expression). In the case where RT.map() will produce a PersistentArrayMap at runtime (small maps), this causes a mismatch.

Note also that this same scenario occurs for constant sets, vectors, etc - all of those cases produce ConstantExpr's with the type of the concrete data structure, instead of the more general IPersistentMap, IPersistentSet, etc. All of those scenarios should be addressed in the same way. But, those interfaces do not capture the full capabilities of the returned object. The compiler and the marshaller need to agree on the types to produce.

*Approach:* One approach (A) explored previously was to have MapExpr always produce a ConstantExpr containing a value of the same type that will be produced at runtime by RT.map(). While this works, it is also fragile, and sidesteps the real issue that MapExpr creates a ConstantExpr with a type that is too specific. Rich said this is the wrong approach.

(B) Another approach is to create an anonymous subclass of ConstantExpr in MapExpr that overrides getJavaClass() to report a more general type. This isn't done anywhere else and it may have negative ramifications (but I have not found any).

(C) Another approach is to follow the path of example #2 above. That example wraps the map in a MetaExpr which reports getJavaClass() returning the hinted type. In the case above, it hints the concrete type that happens to match the return of RT.map(). Instead, we could wrap the ConstantExpr in a MetaExpr hinted on the abstract type IPersistentMap (the return type of RT.map()).

RH - a more general type will not give full capabilities.

*Patch:*

{code}
(.containsKey {:one 1} :one)
;=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to clojure.lang.PersistentHashMap
{code}

The map is a clojure.lang.PersistentArrayMap, which obviously has a containsKey method (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentArrayMap.java#L95).

Casting it works fine though:

{code}
(.containsKey ^clojure.lang.PersistentArrayMap {:one 1} :one)
;=> true
{code}

The problem is not present in Clojure 1.2.1.

*Cause:* In the first example, the Compiler will parse the MapExpr (which reports a Java class of IPersistentMap) into a ConstantExpr whose value is a PersistentHashMap (and reports type as same). The .containsKey() call is a HostExpr which parses to an InstanceMethodExpr.

When emitted to bytecode, the ConstantExpr becomes a call to RT.map() to generate a map. RT.map() produces either a PersistentArrayMap or PersistentHashMap depending on size. The InstanceMethodExpr becomes a call to PersistentHashMap.containsKey() (based on the reported type of it's expression). In the case where RT.map() will produce a PersistentArrayMap at runtime (small maps), this causes a mismatch.

Note also that this same scenario occurs for constant sets, vectors, etc - all of those cases produce ConstantExpr's with the type of the concrete data structure, instead of the more general IPersistentMap, IPersistentSet, etc. All of those scenarios should be addressed in the same way. But, those interfaces do not capture the full capabilities of the returned object. The compiler and the marshaller need to agree on the types to produce.

*Approach:* One approach (A) explored previously was to have MapExpr always produce a ConstantExpr containing a value of the same type that will be produced at runtime by RT.map(). While this works, it is also fragile, and sidesteps the real issue that MapExpr creates a ConstantExpr with a type that is too specific. Rich said this is the wrong approach.

(B) Another approach is to create an anonymous subclass of ConstantExpr in MapExpr that overrides getJavaClass() to report a more general type. This isn't done anywhere else and it may have negative ramifications (but I have not found any).

(C) Another approach is to follow the path of example #2 above. That example wraps the map in a MetaExpr which reports getJavaClass() returning the hinted type. In the case above, it hints the concrete type that happens to match the return of RT.map(). Instead, we could wrap the ConstantExpr in a MetaExpr hinted on the abstract type IPersistentMap (the return type of RT.map()).

RH - a more general (interface) type will not give full capabilities. Perhaps APersistent*

*Patch:*

Alex Miller made changes -
Approval Incomplete [ 10006 ] Vetted [ 10003 ]
Fix Version/s Release 1.6 [ 10157 ]
Fix Version/s Release 1.5 [ 10150 ]
Rich Hickey made changes -
Resolution Completed [ 1 ]
Fix Version/s Release 1.6 [ 10157 ]
Status Open [ 1 ] Closed [ 6 ]
Alex Miller made changes -
Approval Vetted [ 10003 ] Ok [ 10007 ]
Assignee Alex Miller [ alexmiller ]
Status Closed [ 6 ] Reopened [ 4 ]
Resolution Completed [ 1 ]
Alex Miller made changes -
Resolution Completed [ 1 ]
Status Reopened [ 4 ] Closed [ 6 ]

People

Vote (2)
Watch (4)

Dates

  • Created:
    Updated:
    Resolved: