A tricky way to determine Lua version (updated)

classic Classic list List threaded Threaded
28 messages Options
12
Reply | Threaded
Open this post in threaded view
|

A tricky way to determine Lua version (updated)

Dmitry Meyer
Hello everyone,

I was wandering around Stack Overflow when came across the message by
Egor Skriptunoff titled "A tricky way to determine Lua version":

http://lua-users.org/lists/lua-l/2016-05/msg00297.html

It's about a "simple program that determines Lua version".

 > The curious fact about this program is that it doesn't depend on anything
 > that can be changed.
 > It does not use any standard library function or any global variable
 > (as they may be withdrawn from sandboxed environment).
 > It does not rely on the name of "_ENV" upvalue
 > (as it can be renamed when Lua is being built).

It was written before the Lua 5.4 release, so I decided to update it.

I ended up with the following function:

     local luaversion = function()
         if ({false, [1] = true})[1] then
             return 'LuaJIT'
         elseif 1 / 0 == 1 / '-0' then
             return 0 + '0' .. '' == '0' and 'Lua 5.4' or 'Lua 5.3'
         end
         local f = function() return function() end end
         return f() == f() and 'Lua 5.2' or 'Lua 5.1'
     end

I rely there on the following incompatibility between Lua 5.3 and 5.4
(quoted from the reference manual):

 > [...] unlike in previous versions, the new implementation preserves
 > the implicit type of the numeral in the string. For instance, the
 > result of "1" + "2" now is an integer, not a float.

I also wrote a shell script to demonstrate how the function works with
different Lua versions:

https://gist.github.com/un-def/914b1a93181e43e8a2adc35ad5c9b03a

It uses hererocks[1] to install Lua interpreters.


[1]: https://github.com/luarocks/hererocks
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Egor Skriptunoff-2
On Mon, Jul 20, 2020 at 1:53 PM Dmitry Meyer wrote:
"A tricky way to determine Lua version":
http://lua-users.org/lists/lua-l/2016-05/msg00297.html

local f, t = function()return function()end end, {nil,
   [false] = {[-1/0] = 'Lua 5.1', [1/0] = 'Lua 5.4'},
   [true]  = {[-1/0] = 'Lua 5.2', [1/0] = 'Lua 5.3'},
   [1]     = 'LuaJIT'}
local version = t[1] or t[f()==f()][1/'-0']
 
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Egor Skriptunoff-2
Sorry, this code doesn't work if Lua 5.4 is built with LUA_NOCVTS2N  :-(

Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Andrew Gierth
In reply to this post by Egor Skriptunoff-2
>>>>> "Egor" == Egor Skriptunoff <[hidden email]> writes:

 Egor> local f, t = function()return function()end end, {nil,
 Egor>    [false] = {[-1/0] = 'Lua 5.1', [1/0] = 'Lua 5.4'},
 Egor>    [true]  = {[-1/0] = 'Lua 5.2', [1/0] = 'Lua 5.3'},
 Egor>    [1]     = 'LuaJIT'}
 Egor> local version = t[1] or t[f()==f()][1/'-0']

How about,

local f, t = function()return function()end end, {nil, -1,
  [false] = {[-1/0] = 'Lua 5.1', [1/0] = 'Lua 5.4'},
  [true]  = {[-1/0] = 'Lua 5.2', [1/0] = 'Lua 5.3'},
  [1]     = 'LuaJIT'}
print(t[1] or t[f()==f()][1/(t[2]*0)])

Not sure how reliably the t[2]*0 evaluates to -0 on 5.1/5.2, though. It
works on the few platforms I tried. (Using a constant -1 there doesn't
work.)

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Egor Skriptunoff-2
On Tue, Jul 21, 2020 at 1:38 AM Andrew Gierth wrote:
How about,

local f, t = function()return function()end end, {nil, -1,
  [false] = {[-1/0] = 'Lua 5.1', [1/0] = 'Lua 5.4'},
  [true]  = {[-1/0] = 'Lua 5.2', [1/0] = 'Lua 5.3'},
  [1]     = 'LuaJIT'}
print(t[1] or t[f()==f()][1/(t[2]*0)])

Not sure how reliably the t[2]*0 evaluates to -0 on 5.1/5.2, though. It
works on the few platforms I tried. (Using a constant -1 there doesn't
work.)

Your idea is correct: we have to calculate negative zero in runtime.
This is because of "zero-bug" in Lua 5.1:
both +0 and -0 can't coexist in the constant's table of Lua 5.1 bytecode.
Example:
z=-0;print(1/0)
The "zero-bug" is not listed in the official Lua bugs page, but it was fixed in Lua 5.2.0.

I've rewritten your code to make it a few bytes shorter:

local f, t = function()return function()end end, {0, [1] = -1,
   [false] = {[-1/0] = 'Lua 5.1', [1/0] = 'Lua 5.4', 'LuaJIT'},
   [true]  = {[-1/0] = 'Lua 5.2', [1/0] = 'Lua 5.3'}}
local version = t[f()==f()][1/-t[1]]


Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Philippe Verdy-2
       if ({false, [1] = true})[1] then
             return 'LuaJIT' 

I'm not sure that the evaluation of the table constructor is documented correctly. Here you assign two different boolean values to the same index [1], this creates a collision and the evaluation order matters.

And there should be no difference with
         if ({[1] = false, [1] = true})[1] then
             return 'LuaJIT' 
even if the first index is given explicitly.

In my opinion the constructor should always be storing values in the table in the order indicated, but this is unspecified (note that the values assign to each index should all be evaluated at run time, even if they are colliding when storing them at a given index position, as well all specified index should be evaluated) as if the constructor was in fact using a pair of sequences of equal length:
     __constructor(__keys, __values)
with __keys= {1, 1} and __values = {false, true)

So make this test:
   local function ident(x) print("ident: "..tostring(x)); return x; end
   k = {ident(1), ident(1), ident(2)}
   v = {ident(false), ident(true), ident('other'}
   t = {k[1] = v[1], k[2] = v[2], k[3] = v[3]}
and you should see six calls:
   ident:1
   ident:1
   ident:2
   ident: false
   ident: true
   ident: other
and t should still contain a two keys at [1] and [2] (not 3), and the value t[1] should be v[2] == true, not v[1] = false which was first assigned but then reassigned/overwritten in t[k[2]] == t[1] == t[k[1]]

How can LuaJIT behave differently ??? This does not seem coherent. May be it was specified now, but being unspecified in the past means that the test is not really conclusive about what was the "correct" behavior in past versions.

Is the order of evaluation of table constructors really specified now (i.e. the order of evaluation of keys expressions, and values expressions, and then the order of assignment) ?

And are all these evaluations interleaved (i.e. evaluate the first key, then the first value, then assign, then restart with the next key/value pair) or separated (all keys first, then all values, then all assignments)?

Or mixed: i.e. keys and values evaluation interleaved, and stored in a single temporary sequence (then used to create the constructor)? As if the code for
   t = {ident(1) = ident(false), ident(2) = ident(true), ident(3) = ident('other')}
 was:
   __kv= {ident(1), ident(false), ident(2), ident(true), ident(3), ident('other')}
   t = __convertkeyvalues(__kv)
which should also print:
   ident:1
   ident:1
   ident:2
   ident: false
   ident: true
   ident: other
in that order, for the evaluation of each key and value before the actual storage in the final array, and that would also assign values in t in the ascending sequence in _kv (the last keys overwriting the value used in any prior key).

The Lua Documentation is not very specific (unlike in expressions for the evaluation of binary and unary operators and their associativity). If this is unspecified, the result in Lua is also unpredictable.

Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Philippe Verdy-2
Fix below:
Le mer. 22 juil. 2020 à 05:19, Philippe Verdy <[hidden email]> a écrit :
       if ({false, [1] = true})[1] then
             return 'LuaJIT' 
Is the order of evaluation of table constructors really specified now (i.e. the order of evaluation of keys expressions, and values expressions, and then the order of assignment) ?

And are all these evaluations interleaved (i.e. evaluate the first key, then the first value, then assign, then restart with the next key/value pair) or separated (all keys first, then all values, then all assignments)?

Or mixed: i.e. keys and values evaluation interleaved, and stored in a single temporary sequence (then used to create the constructor)? As if the code for
   t = {ident(1) = ident(false), ident(2) = ident(true), ident(3) = ident('other')}
 was:
   __kv= {ident(1), ident(false), ident(2), ident(true), ident(3), ident('other')}
   t = __convertkeyvalues(__kv)
which should also print:
(Fixed): 
   ident:1
   ident: false
   ident:1
   ident: true
   ident:2
   ident: other

in that order, for the evaluation of each key and value before the actual storage in the final array, and that would also assign values in t in the ascending sequence in _kv (the last keys overwriting the value used in any prior key).
Note that I cannot print the order of assignation, as there's no way to assign a metatable to a table that is still not constructed to inspect the behavior of "__newindex", all we can do is to check the result to see which value was assigned to a given key. Lua still has no syntaxic way to create a metatable with a constructor using metamethods for its assignments. But the result is still unpredictable.

Even if Lua had a syntax for defining metamethod (for __newindex at least), but we could also pass a table of metamethods to the constructor, and that table would also have its own constructor and its evaluation order would also matter)!

   t = {
      __newindex = function(t, k, v) print('new t['..tostring(k)..']='..tostring(v)); t|k] = v; end
   } :: {ident(1) = ident(false), ident(2) = ident(true), ident(3) = ident('other')}

Change the syntax by writing the metatable construction after the main table construction should not be different:

   t = {ident(1) = ident(false), ident(2) = ident(true), ident(3) = ident('other')} ::  {
      __newindex = function(t, k, v) print('new t['..tostring(k)..']='..tostring(v)); t|k] = v; end
   }

Here I would expect the metatable to be fully built before building the actual table assigned to t.

Then the table constructor can also create the initial table because it knows the (even) size of the ordered sequence containing all pairs/values, so it can predict a default size for the final table, even if the ordered sequence contains some duplicate keys (but note that the sequence may also contain nil values, so this is still not a pure sequence even if it's ordered).

Anyway I'm not even sure that the table constructor uses __newindex for its table constructors, it may just use rawset(t, k, v) which is untrackable (except by using a debugging core library and loading it before the Lua engine is loaded, meaning that the result is unpredicatable in that case).

Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Philippe Verdy-2
Also note that this candidate syntax uses "::" between the metatable constructor and the main table constructor. The metatable constructor may be built in a separate statement and stored in a variable so:

   t = {
      __newindex = function(t, k, v) print('new t['..tostring(k)..']='..tostring(v)); t|k] = v; end
   } :: {ident(1) = ident(false), ident(2) = ident(true), ident(3) = ident('other')} 
   -- or alternatively:
   t = {ident(1) = ident(false), ident(2) = ident(true), ident(3) = ident('other')}  :: {
      __newindex = function(t, k, v) print('new t['..tostring(k)..']='..tostring(v)); t|k] = v; end
   }

could be written as:

   meta = {
      __newindex = function(t, k, v)
         print('new t['..tostring(k)..']='..tostring(v))
         t|k] = v
      end,
   }
   t = meta::{ident(1) = ident(false), ident(2) = ident(true), ident(3) = ident('other'),}
  -- or alternatively:
   t = {ident(1) = ident(false), ident(2) = ident(true), ident(3) = ident('other'),} ::meta

I tend to prefer the first syntax (not the alternative), which does not conflict with the syntax of label instructions "::labelname::" as the result of the table constructors cannot start any instruction, unless it is isolated between parentheses in order to use it as a function to call, possibly with currification of its single parameter, where the parameter(s) are inside these parentheses. The alternative (with "::meta" at end) could be ambiguous with a following "::label:" instruction...



Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Philippe Verdy-2
And sorry for the remaining typos 
* "|' instead of "[" instead the "meta:__newindex" function body, and
* missing "[]" around expressions of keys in the main table constructor.
This does not change the meaning of the message. We still need to have a defined behavior with an expected evaluation order for keys and values, and for assignments of keys in the new table.

A JIT compiler may still use either a single array for storing all keys and values or could preallocate separate arrays with known length for its keys and values, in order to build the final table with a predefined initial size (the final size of the returned table could still be smaller if there are duplicate keys or nil values assigned to them; and in my opinion the table constructor should use the indicated metatable, it may use the core "rawset(t,k,v)" function of the library directly only for simple table constructors not specifying any metatable, but it should respect the same order for assignments as if there was a metatable specified and containing a __newindex method), so that no reallocation will occur during the construction (so this would minimize the GC overhead).

Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Philippe Verdy-2
In reply to this post by Philippe Verdy-2
So continuing on:
Le mer. 22 juil. 2020 à 06:28, Philippe Verdy <[hidden email]> a écrit :
   meta = {
      __newindex = function(t, k, v)
         print('new t['..tostring(k)..']='..tostring(v))
         t|k] = v
      end,
   }
   t = meta::{[ident(1)] = ident(false), [ident(2)] = ident(true), [ident(3)] = ident('other'),}

there's an "equivalent" code for the second statement:
    t = table.new(3); setmetatable(t, meta)
    t[ident(1)] = ident(false); t[ident(2)] = ident(true); t[ident(3)] = ident('other');

It is using table.new(int) to specify an hint for the initial maximum size and avoid the resizing overhead: this size is predetermined from the total number of key values indicated syntaxically, even if these keys may be duplicate, or if some values may be nil.

The metatable is then set first, then for each key/value pair, the key is evaluated, then the value, then the assignment using __newindex(t,k,v) instead of rawset(t,k,v) (because we specified a metatable and that table defines a "__newindex").

So all is interleaved, and in a welldefined order: it is warrantied that the last value indicated in the table constructor will be in the last key indicated in the constructor (or that it will be nil), but there's no such warranty for previous keys that may be overwritten, or deleted by further assignment.

Some of the values evaluated and assigned to some key may have be replaced and will be garbage collected, but the table will still maintain its size, not necessarily the same as #t or table.maxn(t), as the constructed table is still not limited to pure sequences.

This order is fully consistant with the use of table for implementing inheritance. But a JIT compiler trying to optimize this may attempt to create a fixed size array for keys and values. However the assignment of keys and values in the temporary tables should not affect at all the construction of the table: these two arrays will not be prefilled (except if their values are static constants not requiring any further evaluation, so the two arrays may be a simple "virtual" copy of static unmutable arrays

Then the JIT compiler should still evaluate the array values of members in order: either from the constant unmutable array prebuilt by the compiler, or by compiling the code to evaluate the key and the value to pass to __newindex (or rawset).
The compiler will not need to evaluate the table reference (t) multiple times, it managed this reference internally before returning the fully built table whose metatable was set early just after initializing it with its default size.

This way, the GC impact is minimal (some GC operations may occur after the construction (rarely in the middle), only after using __newindex(t,k,v) with a duplicate key in k (overwriting an existing non-nil value set by a previous key/pair assignment); such GC may occur in the middle because the overwritten value is no longer referenced and memory may be requested during the evaluation of further keys or values specified later inside the syntax of the constructor.

My proposal is then consistant and should define the expected behavior while allowing a JIT to perform fast and generate an efficient code for the constructor.

It will be compatible as well with existing runtime engines (the syntax I propose just makes things a bit simpler: no need to declare a local variable for the reference to the new table, no need to use setmetatable(), no need to make so many assignments repeating the reference to the table in construction, so it works as well when a table constructor is used in the middle of and expression (where it is not possible syntaxically to insert multiple statements, even if this is not a problem at all for the runtime VM: this is only a current syntaxic limitation).

My proposed syntax "meta::{{ [key]=value, ... }" is simple to read as well. Its runtime behavior is well defined (fully specified order of execution, fully predictable result for the resulting table, including values of keys, and table maximum size), single use of the allocator (if keys form a sequence without large "holes"), and minimal GC (no GC impact at all if all keys are distinct). For initializing tables that are pure sequences of distinct integer keys (even if some values are nil), there will be only ONE allocation.

The JIT may optimize the initial table allocation by checking the value of the first evaluated key, then using it to determine the size to allocate as this key value plus the number of additional keys still not evaluated: it may allocate the table only after the evaluation of this first key and the first value: if that value is not nil, it may assume this will be the minimal key value, if that first value is nil, the allocation of the new table is still suspended until it finds a non-nil value to assign: the first non-nil value returned determined the estimated lowest key that will be used. If the table contains a static key [1] (at any position), no suspension is needed, the initial table size is predetermined by the total number of key/value pairs.

The same is true if the table contain any static key [n] where n is an integer lower than the total number of keys in the constructor.
Large static keys like [1000] may not be used at all for the initial size to use for calling table.new(sz), if the constructor syntaxically contains less than 1000 keys/values pairs, but for this case, the constructor may use the total number of keys multiplied by some reasonnable factor (e.g. 1.25) then rounded as long as it remains lower than this static [1000] key, so for a table constructor like:
  t = {[1000] = 1000, [100]=100},
it would allocate a new table of size round(2* 1.25) = round(2.5) = 3...

And no longer need to make any reference to the core library table.new(sz) and setmetatable(t,k,v) which may have been obscured in the environment.



Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Dmitry Meyer
In reply to this post by Philippe Verdy-2
>         if ({false, [1] = true})[1] then
>               return 'LuaJIT'
>
> I'm not sure that the evaluation of the table constructor is documented
> correctly. Here you assign two different boolean values to the same
> index [1], this creates a collision and the evaluation order matters.
>

As I understand, there is no collision.

{item1, ...} stores items in the array part
{[idx1] = item1, ...} _always_ stores items in the hash part (even if
you use integer keys without holes)

 From "Lua performance tips":

> If you write something like{[1] = true, [2] = true, [3] = true}, however, Lua is not smart enough to detect that the given expressions (literal numbers, in this case) describe array indices, so it creates a table with four slots in its hash part, wasting memory and CPU time


$ luac -p -l - <<< 'local t = ({false, [1] = true})'

main <stdin:0,0> (7 instructions at 0x565552f68ae0)
0+ params, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions
        1 [1] PREPARATORY 0
        2 [1] NEWTABLE 0 1 1 ; 1
        3 [1] EXTRAARG 0
        4 [1] LOADFALSE 1
        5 [1] SETI     0 1 0k ; true
        6 [1] SETLIST   0 1 0
        7 [1] RETURN   1 1 1 ; 0 out
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Philippe Verdy-2
This is not a "collision" in the hashed part but still a collision as only one item will be accessed. The hashed part should not contain any key whose value is an integer in the sequential range of the array part, otherwise Lua would not know where to lookup the value. May be in the first stage if you write  {[1] = true, [2] = true, [3] = true} it creates an empty array part but a 4-slots hash part (including one free slot) for the 3 hashed integers.
If you use  {true, [2] = true, [3] = true}, there's one key in the array part (for the sequential range 1 to 1) and a 2-slots hash part (full) or larger 4-slots hash part (if ever the integer indexes [2], and [3] are mapped to the same hash value and there's a tradeoff to avoid using hashed parts that are 100% full or near 100%, where collision chains are very likely. In that case there's still no collision between the sequential range part and the hashed part that automatically excludes all keys that are in the sequential part.
The exact tuning however for the hashed part (the maximum fill level) is left to be implementation dependant.
As well, which part to the table to use (sequential or hashed) is to be left implementation dependant: a compiler may still decide to allocate more keys in the sequential range in order to reduce the fill level of the hashed part and reduce the level of collision chains in that last part. This also saves memory because the transfered integer keys that can grow the seize of the sequential array part will no longer have to be stored separately in hash nodes, and the hashed part could be compressed better or the unused nodes may be already free for other keys: this compression could occur for example at end of evaluation of the table constructor, when all keys are known.

I was however not concerned much about that tuning, but about the evaluation order: I spoke about collision of keys meaining that the same key value [1] (independantyly of its location in the array part of the hashed part) is reassigned with distinct values and the evaluation order (of key expressions and value expressions, and assignment of keys in the table should be in a well defined order, so that we can know which value will be returned. This is still unspecified, and independant of the internal tuning of the table (between what should be in the sequential range or what should be in the hashed part).

Apparently the sequential range part (array) is only used by table constructors for keys that have no explicit expressions but incremented automatically. And still what is the result of t={'a', [1]='b'} if we query t[1]? 'a' or 'b'? Lua can only store one of these two values, never both simultaneously, otherwise the result would be desastrous (imagine what would happens it you set t[1] = nil, and query again t[1] and sees that the result is not nil but the second value that was left in the other part when only one part had its value cleared...). Clearly the Lua engine has to decide each time which part to use, and it will normally try to use the sequential part first (because it is faster) before hashing the index and using the hashed part.

Then if the hashed part is full (or its collision chains have exceeded a maximum length or the fill level exceeds some threshold like 85%), Lua has to make a choice: it can either
- grow the sequential range of the array and transfer some integer keys already present in the hashed part into a larger range (this does not require any rehashing, just a single realloc for the larger sequential range where there can be a resonnable number of keys with non nil values transfered as long as this part remains at least about 50% used with non-nil values),
- or grow the hash part (which is costlier in memory and requires full rehashing).

As well when many keys are cleared in the table (either in the array part or the hashed part, so that this part falls below some fill level (e.g. below 25%) it can decide to reduce this part, or redistribute some keys from one part to the other to maintain the minimum fill level, by transfering few key/values pairs: one part may grow in size while the other part may be reduced, but growing the size of the sequential array is much less costly than the reverse. A tuning decision has to be made in the implementation, but this should not change the behavior and the resulting values left in the table after initializing it with a table constructors or changing it later with assignments of invidual keys.

So I don't see why t={a', [1]='b'} would return different value 'a' or 'b' for t[1], independantly of the implementation and why using t={[1]='a', [1]='b'} would make any difference: this should never have any "visible" effect on queried values (beside the effect on performance and memory cost), because the key values are still exactly the same: the order of evaluation for keys or values or the order on internal assignments (possibly colliding on one or the other part) should be the only thing that matters.





Le mer. 22 juil. 2020 à 14:05, Dmitry Meyer <[hidden email]> a écrit :
>         if ({false, [1] = true})[1] then
>               return 'LuaJIT'
>
> I'm not sure that the evaluation of the table constructor is documented
> correctly. Here you assign two different boolean values to the same
> index [1], this creates a collision and the evaluation order matters.
>

As I understand, there is no collision.

{item1, ...} stores items in the array part
{[idx1] = item1, ...} _always_ stores items in the hash part (even if
you use integer keys without holes)

 From "Lua performance tips":

> If you write something like{[1] = true, [2] = true, [3] = true}, however, Lua is not smart enough to detect that the given expressions (literal numbers, in this case) describe array indices, so it creates a table with four slots in its hash part, wasting memory and CPU time


$ luac -p -l - <<< 'local t = ({false, [1] = true})'

main <stdin:0,0> (7 instructions at 0x565552f68ae0)
0+ params, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions
        1       [1]     PREPARATORY     0
        2       [1]     NEWTABLE        0 1 1   ; 1
        3       [1]     EXTRAARG        0
        4       [1]     LOADFALSE       1
        5       [1]     SETI            0 1 0k  ; true
        6       [1]     SETLIST         0 1 0
        7       [1]     RETURN          1 1 1   ; 0 out
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Andrew Gierth
In reply to this post by Philippe Verdy-2
>>>>> "Philippe" == Philippe Verdy <[hidden email]> writes:

 Philippe>        if ({false, [1] = true})[1] then
 Philippe>              return 'LuaJIT'

 Philippe> I'm not sure that the evaluation of the table constructor is
 Philippe> documented correctly. Here you assign two different boolean
 Philippe> values to the same index [1], this creates a collision and
 Philippe> the evaluation order matters.

The evaluation order matters, and LuaJIT happens to do it the opposite
way to (all) the reference implementations; the manual explicitly states
that the order of evaluation is undefined:

  The order of the assignments in a constructor is undefined. (This
  order would be relevant only when there are repeated keys.)

Hence using it to distinguish LuaJIT from other implementations.

Note that almost everything in this version detection logic is based on
language features that are left undefined. For example the [f()==f()]
test is seeing whether a closure with no upvalues allocates a new object
or not each time, of which the manual says:

  Functions created at different times but with no detectable
  differences may be classified as equal or not (depending on internal
  caching details).

Even the 1/0 parts rely on the implementation using IEEE floats, or at
least some float implementation that has both +/- infinity and +/- 0,
and that for integers in 5.3/5.4 there is no separate -0; if you build
Lua with only integer support, the code will not work.

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Philippe Verdy-2
If the documentation does not define the  behavior, then the detection is also undefined as you may just test one case for which you would get one result or another case where you would get the opposite answer.
The detection will ever be fullproof. And it is not reliable to depend on this detection, except by not writing ANY code that depends on an undefined behavior.

This means that ANY code that uses table constructors mixing implicit (autoincremented) integer keys and other explicit integer keys IS UNRELIALE. and it will remain unreliable and will cause unexpected bugs on all versions of LUA that still don't specify the behavior and checks/enforces the rules.


It's very strange then that Lua accepts a simple syntax like {1, [1]=2} without being able to say what will be the value stored at index [1]. Your test may give an answer correct only for some sets of keys, where the order of assignments will be in one way, but for other sets of keys, you could as well get the inverse result, and you would NOT detect LuaJIT, and your code would make false decisions.

In summary, table constructors should ONLY be pure sequences with implicit keys, OR all members should be explicitly keyed.

I see it as a major defect of the language that should be solved by enforcing the rules to define the expected behavior. This should not break existing applications more than when they already use today the undefined behavior assuming that it is one way when it could still be the other way even on exactly the same platform instance.

Given that, this means that Lua is unable to say that it is portable, and every developer is forced to test their apps on many implementations, and some will be never be tested, notably newer LUA engines that could appear at any time or could be upgraded.

This means that for critical applications, YOU MUST NOT upgrade Lua, but this application Must provide its own copy of the engine. And reusability is also compromised notably for external Lua libraries used that would depend also on a behavior which is defined only for some versions of implementations. 

And this can cause serious security bugs that could be exploited later. 

Undefined behavior has already caused lot of damages in many languages and platforms. Inclicluding hardware platforms like CPUs. LUA devopers should make efforts to define the behavior and enforce them by conformance and coverage tests so that  platforms can fix them and then be safely upgradable and serviceable.

Until then, do not upgrade your existing Lua engine, and do not use any third party library that was not explicitly tested fie exactly the same version of the implementation.

Anlther way to solve this problem would be to develop a minter that will detect the code using undefined behavior, so that this code can be rewritten.

The assumptions that Lua tables are capable of mixing sequences and associative keyed arrays is then wrong, it has never been specified correctly. 

Le mer. 22 juil. 2020 à 21:05, Andrew Gierth <[hidden email]> a écrit :
>>>>> "Philippe" == Philippe Verdy <[hidden email]> writes:

 Philippe>        if ({false, [1] = true})[1] then
 Philippe>              return 'LuaJIT'

 Philippe> I'm not sure that the evaluation of the table constructor is
 Philippe> documented correctly. Here you assign two different boolean
 Philippe> values to the same index [1], this creates a collision and
 Philippe> the evaluation order matters.

The evaluation order matters, and LuaJIT happens to do it the opposite
way to (all) the reference implementations; the manual explicitly states
that the order of evaluation is undefined:

  The order of the assignments in a constructor is undefined. (This
  order would be relevant only when there are repeated keys.)

Hence using it to distinguish LuaJIT from other implementations.

Note that almost everything in this version detection logic is based on
language features that are left undefined. For example the [f()==f()]
test is seeing whether a closure with no upvalues allocates a new object
or not each time, of which the manual says:

  Functions created at different times but with no detectable
  differences may be classified as equal or not (depending on internal
  caching details).

Even the 1/0 parts rely on the implementation using IEEE floats, or at
least some float implementation that has both +/- infinity and +/- 0,
and that for integers in 5.3/5.4 there is no separate -0; if you build
Lua with only integer support, the code will not work.

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Coda Highland
The documentation doesn't define the behavior, but the source code does. This isn't some underlying UB issue where you could get different results based on the underlying hardware. Given the official releases of the Lua interpreters and knowing the code they were built with, this trickery is entirely well-behaved and reliable.

That said, arguing about this just indicates that people are talking on different levels. Nobody here is WRONG. But one side is arguing about what you SHOULD do, while the other is arguing about what you CAN do.

Duh, of course it's a bad idea. You would never want to write code that relies on this behavior. There would be no promise that it would continue to operate as intended in the future. Even for version detection this isn't actually a GOOD idea because what if you're running on an implementation of Lua that isn't from PUC-Rio or LuaJIT? It certainly wouldn't identify Fengari or Ravi, and there's not even any guarantee that the version this function returns would match the version of the Lua language that those versions are intended to follow.

But as has been repeatedly asserted: if you're using one of the versions this function supports... it does WORK.

/s/ Adam


On Thu, Jul 23, 2020 at 8:53 AM Philippe Verdy <[hidden email]> wrote:
If the documentation does not define the  behavior, then the detection is also undefined as you may just test one case for which you would get one result or another case where you would get the opposite answer.
The detection will ever be fullproof. And it is not reliable to depend on this detection, except by not writing ANY code that depends on an undefined behavior.

This means that ANY code that uses table constructors mixing implicit (autoincremented) integer keys and other explicit integer keys IS UNRELIALE. and it will remain unreliable and will cause unexpected bugs on all versions of LUA that still don't specify the behavior and checks/enforces the rules.


It's very strange then that Lua accepts a simple syntax like {1, [1]=2} without being able to say what will be the value stored at index [1]. Your test may give an answer correct only for some sets of keys, where the order of assignments will be in one way, but for other sets of keys, you could as well get the inverse result, and you would NOT detect LuaJIT, and your code would make false decisions.

In summary, table constructors should ONLY be pure sequences with implicit keys, OR all members should be explicitly keyed.

I see it as a major defect of the language that should be solved by enforcing the rules to define the expected behavior. This should not break existing applications more than when they already use today the undefined behavior assuming that it is one way when it could still be the other way even on exactly the same platform instance.

Given that, this means that Lua is unable to say that it is portable, and every developer is forced to test their apps on many implementations, and some will be never be tested, notably newer LUA engines that could appear at any time or could be upgraded.

This means that for critical applications, YOU MUST NOT upgrade Lua, but this application Must provide its own copy of the engine. And reusability is also compromised notably for external Lua libraries used that would depend also on a behavior which is defined only for some versions of implementations. 

And this can cause serious security bugs that could be exploited later. 

Undefined behavior has already caused lot of damages in many languages and platforms. Inclicluding hardware platforms like CPUs. LUA devopers should make efforts to define the behavior and enforce them by conformance and coverage tests so that  platforms can fix them and then be safely upgradable and serviceable.

Until then, do not upgrade your existing Lua engine, and do not use any third party library that was not explicitly tested fie exactly the same version of the implementation.

Anlther way to solve this problem would be to develop a minter that will detect the code using undefined behavior, so that this code can be rewritten.

The assumptions that Lua tables are capable of mixing sequences and associative keyed arrays is then wrong, it has never been specified correctly. 

Le mer. 22 juil. 2020 à 21:05, Andrew Gierth <[hidden email]> a écrit :
>>>>> "Philippe" == Philippe Verdy <[hidden email]> writes:

 Philippe>        if ({false, [1] = true})[1] then
 Philippe>              return 'LuaJIT'

 Philippe> I'm not sure that the evaluation of the table constructor is
 Philippe> documented correctly. Here you assign two different boolean
 Philippe> values to the same index [1], this creates a collision and
 Philippe> the evaluation order matters.

The evaluation order matters, and LuaJIT happens to do it the opposite
way to (all) the reference implementations; the manual explicitly states
that the order of evaluation is undefined:

  The order of the assignments in a constructor is undefined. (This
  order would be relevant only when there are repeated keys.)

Hence using it to distinguish LuaJIT from other implementations.

Note that almost everything in this version detection logic is based on
language features that are left undefined. For example the [f()==f()]
test is seeing whether a closure with no upvalues allocates a new object
or not each time, of which the manual says:

  Functions created at different times but with no detectable
  differences may be classified as equal or not (depending on internal
  caching details).

Even the 1/0 parts rely on the implementation using IEEE floats, or at
least some float implementation that has both +/- infinity and +/- 0,
and that for integers in 5.3/5.4 there is no separate -0; if you build
Lua with only integer support, the code will not work.

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Philippe Verdy-2
Yes it may work today... Until the. Ext version that will have more complex behavior including those from existing tuning parameters or compilation options that could enable or disable some part of the code, or other conditions changing dynamically at runtime, such as the effect of GC or with some some specific subtypes including those created in the source for supporting lower ranges of processors, or the effect of memory alignment constraints, or whever we run in debugging mode where some optimizations maybe disabled.

Any way this test is intended to be experimental and is interesting to develop a good linter that will help developers to locate Lua code whose result is not warranted, in order to use alternatives which may be slower but safe and stable even on the same platform, and will cause less nightmare in the future when they'll want to upgrade their engine.

As you say, such code should not be used at all in production code. It should just be for experimental alpha demos and trials. 

Le jeu. 23 juil. 2020 à 16:20, Coda Highland <[hidden email]> a écrit :
The documentation doesn't define the behavior, but the source code does. This isn't some underlying UB issue where you could get different results based on the underlying hardware. Given the official releases of the Lua interpreters and knowing the code they were built with, this trickery is entirely well-behaved and reliable.

That said, arguing about this just indicates that people are talking on different levels. Nobody here is WRONG. But one side is arguing about what you SHOULD do, while the other is arguing about what you CAN do.

Duh, of course it's a bad idea. You would never want to write code that relies on this behavior. There would be no promise that it would continue to operate as intended in the future. Even for version detection this isn't actually a GOOD idea because what if you're running on an implementation of Lua that isn't from PUC-Rio or LuaJIT? It certainly wouldn't identify Fengari or Ravi, and there's not even any guarantee that the version this function returns would match the version of the Lua language that those versions are intended to follow.

But as has been repeatedly asserted: if you're using one of the versions this function supports... it does WORK.

/s/ Adam


On Thu, Jul 23, 2020 at 8:53 AM Philippe Verdy <[hidden email]> wrote:
If the documentation does not define the  behavior, then the detection is also undefined as you may just test one case for which you would get one result or another case where you would get the opposite answer.
The detection will ever be fullproof. And it is not reliable to depend on this detection, except by not writing ANY code that depends on an undefined behavior.

This means that ANY code that uses table constructors mixing implicit (autoincremented) integer keys and other explicit integer keys IS UNRELIALE. and it will remain unreliable and will cause unexpected bugs on all versions of LUA that still don't specify the behavior and checks/enforces the rules.


It's very strange then that Lua accepts a simple syntax like {1, [1]=2} without being able to say what will be the value stored at index [1]. Your test may give an answer correct only for some sets of keys, where the order of assignments will be in one way, but for other sets of keys, you could as well get the inverse result, and you would NOT detect LuaJIT, and your code would make false decisions.

In summary, table constructors should ONLY be pure sequences with implicit keys, OR all members should be explicitly keyed.

I see it as a major defect of the language that should be solved by enforcing the rules to define the expected behavior. This should not break existing applications more than when they already use today the undefined behavior assuming that it is one way when it could still be the other way even on exactly the same platform instance.

Given that, this means that Lua is unable to say that it is portable, and every developer is forced to test their apps on many implementations, and some will be never be tested, notably newer LUA engines that could appear at any time or could be upgraded.

This means that for critical applications, YOU MUST NOT upgrade Lua, but this application Must provide its own copy of the engine. And reusability is also compromised notably for external Lua libraries used that would depend also on a behavior which is defined only for some versions of implementations. 

And this can cause serious security bugs that could be exploited later. 

Undefined behavior has already caused lot of damages in many languages and platforms. Inclicluding hardware platforms like CPUs. LUA devopers should make efforts to define the behavior and enforce them by conformance and coverage tests so that  platforms can fix them and then be safely upgradable and serviceable.

Until then, do not upgrade your existing Lua engine, and do not use any third party library that was not explicitly tested fie exactly the same version of the implementation.

Anlther way to solve this problem would be to develop a minter that will detect the code using undefined behavior, so that this code can be rewritten.

The assumptions that Lua tables are capable of mixing sequences and associative keyed arrays is then wrong, it has never been specified correctly. 

Le mer. 22 juil. 2020 à 21:05, Andrew Gierth <[hidden email]> a écrit :
>>>>> "Philippe" == Philippe Verdy <[hidden email]> writes:

 Philippe>        if ({false, [1] = true})[1] then
 Philippe>              return 'LuaJIT'

 Philippe> I'm not sure that the evaluation of the table constructor is
 Philippe> documented correctly. Here you assign two different boolean
 Philippe> values to the same index [1], this creates a collision and
 Philippe> the evaluation order matters.

The evaluation order matters, and LuaJIT happens to do it the opposite
way to (all) the reference implementations; the manual explicitly states
that the order of evaluation is undefined:

  The order of the assignments in a constructor is undefined. (This
  order would be relevant only when there are repeated keys.)

Hence using it to distinguish LuaJIT from other implementations.

Note that almost everything in this version detection logic is based on
language features that are left undefined. For example the [f()==f()]
test is seeing whether a closure with no upvalues allocates a new object
or not each time, of which the manual says:

  Functions created at different times but with no detectable
  differences may be classified as equal or not (depending on internal
  caching details).

Even the 1/0 parts rely on the implementation using IEEE floats, or at
least some float implementation that has both +/- infinity and +/- 0,
and that for integers in 5.3/5.4 there is no separate -0; if you build
Lua with only integer support, the code will not work.

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Philippe Verdy-2
In reply to this post by Coda Highland
As well I consider that the documented syntax of table constructors, where explicit keys can be arbitrary expressions whose value is unpredictable, such as a function call, is fundamentally flawed if the behavior and notably the order of assignments, is not defined at all. Thus syntax serves no serious purpose

I would say the same with the unary operator # which can return anything, as well as the corel ibrary function table.maxn()....

Lack of specification there cause nasty bugs that are later hard to locate! And the undefined behavior cannot even  be detected reliably at runtime because the sequence order is not easy to reproduce and depends on many unknown and undetectable factors.... 

Le jeu. 23 juil. 2020 à 16:20, Coda Highland <[hidden email]> a écrit :
The documentation doesn't define the behavior, but the source code does. This isn't some underlying UB issue where you could get different results based on the underlying hardware. Given the official releases of the Lua interpreters and knowing the code they were built with, this trickery is entirely well-behaved and reliable.

That said, arguing about this just indicates that people are talking on different levels. Nobody here is WRONG. But one side is arguing about what you SHOULD do, while the other is arguing about what you CAN do.

Duh, of course it's a bad idea. You would never want to write code that relies on this behavior. There would be no promise that it would continue to operate as intended in the future. Even for version detection this isn't actually a GOOD idea because what if you're running on an implementation of Lua that isn't from PUC-Rio or LuaJIT? It certainly wouldn't identify Fengari or Ravi, and there's not even any guarantee that the version this function returns would match the version of the Lua language that those versions are intended to follow.

But as has been repeatedly asserted: if you're using one of the versions this function supports... it does WORK.

/s/ Adam


On Thu, Jul 23, 2020 at 8:53 AM Philippe Verdy <[hidden email]> wrote:
If the documentation does not define the  behavior, then the detection is also undefined as you may just test one case for which you would get one result or another case where you would get the opposite answer.
The detection will ever be fullproof. And it is not reliable to depend on this detection, except by not writing ANY code that depends on an undefined behavior.

This means that ANY code that uses table constructors mixing implicit (autoincremented) integer keys and other explicit integer keys IS UNRELIALE. and it will remain unreliable and will cause unexpected bugs on all versions of LUA that still don't specify the behavior and checks/enforces the rules.


It's very strange then that Lua accepts a simple syntax like {1, [1]=2} without being able to say what will be the value stored at index [1]. Your test may give an answer correct only for some sets of keys, where the order of assignments will be in one way, but for other sets of keys, you could as well get the inverse result, and you would NOT detect LuaJIT, and your code would make false decisions.

In summary, table constructors should ONLY be pure sequences with implicit keys, OR all members should be explicitly keyed.

I see it as a major defect of the language that should be solved by enforcing the rules to define the expected behavior. This should not break existing applications more than when they already use today the undefined behavior assuming that it is one way when it could still be the other way even on exactly the same platform instance.

Given that, this means that Lua is unable to say that it is portable, and every developer is forced to test their apps on many implementations, and some will be never be tested, notably newer LUA engines that could appear at any time or could be upgraded.

This means that for critical applications, YOU MUST NOT upgrade Lua, but this application Must provide its own copy of the engine. And reusability is also compromised notably for external Lua libraries used that would depend also on a behavior which is defined only for some versions of implementations. 

And this can cause serious security bugs that could be exploited later. 

Undefined behavior has already caused lot of damages in many languages and platforms. Inclicluding hardware platforms like CPUs. LUA devopers should make efforts to define the behavior and enforce them by conformance and coverage tests so that  platforms can fix them and then be safely upgradable and serviceable.

Until then, do not upgrade your existing Lua engine, and do not use any third party library that was not explicitly tested fie exactly the same version of the implementation.

Anlther way to solve this problem would be to develop a minter that will detect the code using undefined behavior, so that this code can be rewritten.

The assumptions that Lua tables are capable of mixing sequences and associative keyed arrays is then wrong, it has never been specified correctly. 

Le mer. 22 juil. 2020 à 21:05, Andrew Gierth <[hidden email]> a écrit :
>>>>> "Philippe" == Philippe Verdy <[hidden email]> writes:

 Philippe>        if ({false, [1] = true})[1] then
 Philippe>              return 'LuaJIT'

 Philippe> I'm not sure that the evaluation of the table constructor is
 Philippe> documented correctly. Here you assign two different boolean
 Philippe> values to the same index [1], this creates a collision and
 Philippe> the evaluation order matters.

The evaluation order matters, and LuaJIT happens to do it the opposite
way to (all) the reference implementations; the manual explicitly states
that the order of evaluation is undefined:

  The order of the assignments in a constructor is undefined. (This
  order would be relevant only when there are repeated keys.)

Hence using it to distinguish LuaJIT from other implementations.

Note that almost everything in this version detection logic is based on
language features that are left undefined. For example the [f()==f()]
test is seeing whether a closure with no upvalues allocates a new object
or not each time, of which the manual says:

  Functions created at different times but with no detectable
  differences may be classified as equal or not (depending on internal
  caching details).

Even the 1/0 parts rely on the implementation using IEEE floats, or at
least some float implementation that has both +/- infinity and +/- 0,
and that for integers in 5.3/5.4 there is no separate -0; if you build
Lua with only integer support, the code will not work.

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

Coda Highland
In reply to this post by Philippe Verdy-2
It shouldn't even be used for that. It's recreational hacking. It's doing stuff that behaves differently based on unspecified implementation details, for the pure joy and fun of doing something clever. And when you're doing that, being a terrible idea just makes it more fun. It shouldn't be taken too seriously.

/s/ Adam

On Thu, Jul 23, 2020 at 11:49 AM Philippe Verdy <[hidden email]> wrote:
Yes it may work today... Until the. Ext version that will have more complex behavior including those from existing tuning parameters or compilation options that could enable or disable some part of the code, or other conditions changing dynamically at runtime, such as the effect of GC or with some some specific subtypes including those created in the source for supporting lower ranges of processors, or the effect of memory alignment constraints, or whever we run in debugging mode where some optimizations maybe disabled.

Any way this test is intended to be experimental and is interesting to develop a good linter that will help developers to locate Lua code whose result is not warranted, in order to use alternatives which may be slower but safe and stable even on the same platform, and will cause less nightmare in the future when they'll want to upgrade their engine.

As you say, such code should not be used at all in production code. It should just be for experimental alpha demos and trials. 

Le jeu. 23 juil. 2020 à 16:20, Coda Highland <[hidden email]> a écrit :
The documentation doesn't define the behavior, but the source code does. This isn't some underlying UB issue where you could get different results based on the underlying hardware. Given the official releases of the Lua interpreters and knowing the code they were built with, this trickery is entirely well-behaved and reliable.

That said, arguing about this just indicates that people are talking on different levels. Nobody here is WRONG. But one side is arguing about what you SHOULD do, while the other is arguing about what you CAN do.

Duh, of course it's a bad idea. You would never want to write code that relies on this behavior. There would be no promise that it would continue to operate as intended in the future. Even for version detection this isn't actually a GOOD idea because what if you're running on an implementation of Lua that isn't from PUC-Rio or LuaJIT? It certainly wouldn't identify Fengari or Ravi, and there's not even any guarantee that the version this function returns would match the version of the Lua language that those versions are intended to follow.

But as has been repeatedly asserted: if you're using one of the versions this function supports... it does WORK.

/s/ Adam


On Thu, Jul 23, 2020 at 8:53 AM Philippe Verdy <[hidden email]> wrote:
If the documentation does not define the  behavior, then the detection is also undefined as you may just test one case for which you would get one result or another case where you would get the opposite answer.
The detection will ever be fullproof. And it is not reliable to depend on this detection, except by not writing ANY code that depends on an undefined behavior.

This means that ANY code that uses table constructors mixing implicit (autoincremented) integer keys and other explicit integer keys IS UNRELIALE. and it will remain unreliable and will cause unexpected bugs on all versions of LUA that still don't specify the behavior and checks/enforces the rules.


It's very strange then that Lua accepts a simple syntax like {1, [1]=2} without being able to say what will be the value stored at index [1]. Your test may give an answer correct only for some sets of keys, where the order of assignments will be in one way, but for other sets of keys, you could as well get the inverse result, and you would NOT detect LuaJIT, and your code would make false decisions.

In summary, table constructors should ONLY be pure sequences with implicit keys, OR all members should be explicitly keyed.

I see it as a major defect of the language that should be solved by enforcing the rules to define the expected behavior. This should not break existing applications more than when they already use today the undefined behavior assuming that it is one way when it could still be the other way even on exactly the same platform instance.

Given that, this means that Lua is unable to say that it is portable, and every developer is forced to test their apps on many implementations, and some will be never be tested, notably newer LUA engines that could appear at any time or could be upgraded.

This means that for critical applications, YOU MUST NOT upgrade Lua, but this application Must provide its own copy of the engine. And reusability is also compromised notably for external Lua libraries used that would depend also on a behavior which is defined only for some versions of implementations. 

And this can cause serious security bugs that could be exploited later. 

Undefined behavior has already caused lot of damages in many languages and platforms. Inclicluding hardware platforms like CPUs. LUA devopers should make efforts to define the behavior and enforce them by conformance and coverage tests so that  platforms can fix them and then be safely upgradable and serviceable.

Until then, do not upgrade your existing Lua engine, and do not use any third party library that was not explicitly tested fie exactly the same version of the implementation.

Anlther way to solve this problem would be to develop a minter that will detect the code using undefined behavior, so that this code can be rewritten.

The assumptions that Lua tables are capable of mixing sequences and associative keyed arrays is then wrong, it has never been specified correctly. 

Le mer. 22 juil. 2020 à 21:05, Andrew Gierth <[hidden email]> a écrit :
>>>>> "Philippe" == Philippe Verdy <[hidden email]> writes:

 Philippe>        if ({false, [1] = true})[1] then
 Philippe>              return 'LuaJIT'

 Philippe> I'm not sure that the evaluation of the table constructor is
 Philippe> documented correctly. Here you assign two different boolean
 Philippe> values to the same index [1], this creates a collision and
 Philippe> the evaluation order matters.

The evaluation order matters, and LuaJIT happens to do it the opposite
way to (all) the reference implementations; the manual explicitly states
that the order of evaluation is undefined:

  The order of the assignments in a constructor is undefined. (This
  order would be relevant only when there are repeated keys.)

Hence using it to distinguish LuaJIT from other implementations.

Note that almost everything in this version detection logic is based on
language features that are left undefined. For example the [f()==f()]
test is seeing whether a closure with no upvalues allocates a new object
or not each time, of which the manual says:

  Functions created at different times but with no detectable
  differences may be classified as equal or not (depending on internal
  caching details).

Even the 1/0 parts rely on the implementation using IEEE floats, or at
least some float implementation that has both +/- infinity and +/- 0,
and that for integers in 5.3/5.4 there is no separate -0; if you build
Lua with only integer support, the code will not work.

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

dyngeccetor8
In reply to this post by Coda Highland
On 2020-07-23 4:19 p.m., Coda Highland wrote:
> That said, arguing about this just indicates that people are talking on
> different levels. Nobody here is WRONG. But one side is arguing about
> what you SHOULD do, while the other is arguing about what you CAN do.

I think here lies distinction between hacker and programmer. Hacker wish
to exploit custom details to create tool for job. Programmer wish to
create product with easy maintainability and agnostic to hardware.

So they have different targets, different design considerations, but
their final product is software.

So exploiting special cases as "-0.0" or "{false, [1] = true}" is evil
thing for production code but neat hack to determine possible Lua
version in some game sandbox.

-- Martin
Reply | Threaded
Open this post in threaded view
|

Re: A tricky way to determine Lua version (updated)

dyngeccetor8
In reply to this post by Philippe Verdy-2
On 2020-07-23 7:01 p.m., Philippe Verdy wrote:
> As well I consider that the documented syntax of table constructors,
> where explicit keys can be arbitrary expressions whose value is
> unpredictable, such as a function call, is fundamentally flawed if the
> behavior and notably the order of assignments, is not defined at all.
> Thus syntax serves no serious purpose
>
> I would say the same with the unary operator # which can return
> anything, as well as the corel ibrary function table.maxn()....

It's off-topic but I think array-table dualism can be fixed by adding
syntax for indexing array part. Parenthesis for example. So for "t =
{false, [1] = true}", "t[1]" is true and "t(1)" is false.

-- Martin
12