1 // as_value.cpp: ActionScript values, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 // Free Software Foundation, Inc
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 #include <boost/lexical_cast.hpp>
26 #include <boost/format.hpp>
33 #include "as_object.h"
34 #include "as_function.h" // for as_function
35 #include "MovieClip.h" // for DISPLAYOBJECT values
36 #include "DisplayObject.h" // for DISPLAYOBJECT values
37 #include "as_environment.h" // for DISPLAYOBJECT values
38 #include "VM.h" // for DISPLAYOBJECT values
39 #include "movie_root.h" // for DISPLAYOBJECT values
40 #include "utility.h" // for typeName()
41 #include "GnashNumeric.h"
42 #include "namedStrings.h"
43 #include "GnashException.h"
45 #include "Date_as.h" // for Date type (readAMF0)
46 #include "SimpleBuffer.h"
47 #include "StringPredicates.h"
48 #include "Global_as.h"
49 #include "String_as.h"
50 #include "AMFConverter.h"
52 // Define the macro below to make abstract equality operator verbose
53 //#define GNASH_DEBUG_EQUALITY 1
55 // Define the macro below to make to_primitive verbose
56 //#define GNASH_DEBUG_CONVERSION_TO_PRIMITIVE 1
58 // Define this macro to make soft references activity verbose
59 #define GNASH_DEBUG_SOFT_REFERENCES
64 bool objectEqualsPrimitive(const as_value
& obj
, const as_value
& prim
,
66 bool stringEqualsNumber(const as_value
& str
, const as_value
& num
,
68 bool compareBoolean(const as_value
& boolean
, const as_value
& other
,
70 inline bool findMethod(as_object
& obj
, const ObjectURI
& m
, as_value
& ret
);
71 template<typename T
> as_object
* constructObject(VM
& vm
, const T
& arg
,
72 const ObjectURI
& className
);
84 /// Converts a string to a uint32_t cast to an int32_t.
86 /// @param whole When true, any string that isn't wholly valid is rejected.
87 /// @param base The base (8 or 16) to use.
88 /// @param s The string to parse.
89 /// @return The converted number.
91 parsePositiveInt(const std::string
& s
, Base base
, bool whole
= true)
94 std::istringstream
is(s
);
109 // If the conversion fails, or if the whole string must be convertible and
110 // some DisplayObjects are left, throw an exception.
111 if (!(is
>> target
) || (whole
&& is
.get(c
))) {
112 throw boost::bad_lexical_cast();
121 bool operator()(char c
) {
122 return (!std::isdigit(c
) && c
!= '.' && c
!= '-' && c
!= '+');
126 /// Omit an empty exponent that is valid in ActionScript but not in C++.
128 /// This function throws a boost::bad_lexical_cast if it finds an invalid
129 /// exponent to avoid attempting an extraction when it will definitely fail.
131 /// A successful return from this function does not mean the exponent is
132 /// valid, only that the result of stringstream's conversion will mirror
135 /// @param si An iterator pointing to the position after an exponent sign.
136 /// @param last The end of the string to extract. If we have an exponent
137 /// with no following digit, this iterator is moved to
138 /// a position before the exponent sign.
140 validateExponent(std::string::const_iterator si
,
141 std::string::const_iterator
& last
)
144 // Check for exponent with no following character. Depending on the
145 // version of gcc, extraction may be rejected (probably more correct) or
146 // accepted as a valid exponent (what ActionScript wants).
147 // In this case we remove the exponent to get the correct behaviour
154 // Exponents with a following '-' or '+' are also valid if they end the
155 // string. It's unlikely that any version of gcc allowed this.
156 if (*si
== '-' || *si
== '+') {
164 // An exponent ("e", "e-", or "e+") followed by a non digit is invalid.
165 if (!std::isdigit(*si
)) {
166 throw boost::bad_lexical_cast();
171 /// Convert a string to a double if the complete string can be converted.
173 /// This follows the conditions of the standard C locale for numbers except
174 /// that an exponent signifier with no following digit (e.g. "2e") is
175 /// considered valid. Moreover, "2e-" is also considered valid.
177 /// This function scans the string twice (once for verification, once for
178 /// extraction) and copies it once (for extraction).
180 parseDecimalNumber(std::string::const_iterator start
,
181 std::string::const_iterator last
)
183 assert(start
!= last
);
185 // Find the first position that is not a numeric character ('e' or 'E' not
186 // included). Even if no invalid character is found, it does not mean
187 // that the number is valid ("++++---" would pass the test).
188 std::string::const_iterator si
=
189 std::find_if(start
, last
, NonNumericChar());
192 // If this character is not an exponent sign, the number is malformed.
193 if (*si
!= 'e' && *si
!= 'E') throw boost::bad_lexical_cast();
194 /// Move the last iterator to point before empty exponents.
195 else validateExponent(si
+ 1, last
);
198 return boost::lexical_cast
<double>(std::string(start
, last
));
201 } // anonymous namespace
203 // Conversion to const std::string&.
205 as_value::to_string(int version
) const
213 const CharacterProxy
& sp
= getCharacterProxy();
214 if (!sp
.get()) return "";
215 return sp
.getTarget();
218 return doubleToString(getNum());
220 if (version
<= 6) return "";
225 return getBool() ? "true" : "false";
228 as_object
* obj
= getObj();
230 if (isNativeType(obj
, s
)) return s
->value();
233 as_value ret
= to_primitive(STRING
);
234 // This additional is_string test is NOT compliant with ECMA-262
235 // specification, but seems required for compatibility with the
237 if (ret
.is_string()) return ret
.getStr();
239 catch (const ActionTypeError
& e
) {}
241 return is_function() ? "[type Function]" : "[type Object]";
246 return "[exception]";
252 as_value::defaultPrimitive(int version
) const
254 if (_type
== OBJECT
&& version
> 5) {
256 if (isNativeType(getObj(), d
)) return STRING
;
261 // Conversion to primitive value.
263 as_value::to_primitive(AsType hint
) const
265 if (_type
!= OBJECT
) return *this;
267 #if GNASH_DEBUG_CONVERSION_TO_PRIMITIVE
268 log_debug("to_primitive(%s)", hint
==NUMBER
? "NUMBER" : "STRING");
271 // TODO: implement as_object::DefaultValue (ECMA-262 - 8.6.2.6)
274 as_object
* obj(nullptr);
276 if (hint
== NUMBER
) {
277 assert(_type
== OBJECT
);
280 if (!findMethod(*obj
, NSV::PROP_VALUE_OF
, method
)) {
281 // Returning undefined here instead of throwing
282 // a TypeError passes tests in actionscript.all/Object.as
283 // and many swfdec tests, with no new failures (though
284 // perhaps we aren't testing enough).
289 assert(hint
== STRING
);
290 assert(_type
== OBJECT
);
293 // @@ Moock says, "the value that results from
294 // calling toString() on the object".
295 if (!findMethod(*obj
, NSV::PROP_TO_STRING
, method
) &&
296 !findMethod(*obj
, NSV::PROP_VALUE_OF
, method
)) {
297 throw ActionTypeError();
303 as_environment
env(getVM(*obj
));
305 as_value ret
= invoke(method
, env
, obj
, args
);
307 #if GNASH_DEBUG_CONVERSION_TO_PRIMITIVE
308 log_debug("to_primitive: method call returned %s", ret
);
311 if (ret
._type
== OBJECT
) {
312 throw ActionTypeError();
318 as_value::to_number(const int version
) const
324 const std::string
& s
= getStr();
326 return version
>= 5 ? NaN
: 0.0;
331 // For SWF4, any valid number before non-numerical
332 // DisplayObjects is returned, including exponent, positive
333 // and negative signs and whitespace before.
335 std::istringstream
is(s
);
344 // Will throw if invalid.
345 if (parseNonDecimalInt(s
, d
)) return d
;
348 // @@ Moock says the rule here is: if the
349 // string is a valid float literal, then it
350 // gets converted; otherwise it is set to NaN.
351 // Valid for SWF5 and above.
352 const std::string::size_type pos
=
353 s
.find_first_not_of(" \r\n\t");
355 if (pos
== std::string::npos
) return NaN
;
357 // Will throw a boost::bad_lexical_cast if it fails.
358 return parseDecimalNumber(s
.begin() + pos
, s
.end());
361 catch (const boost::bad_lexical_cast
&) {
362 // There is no standard textual representation of infinity
363 // in the C++ standard, so our conversion function an
364 // exception for 'inf', just like for any other
365 // non-numerical text. This is correct behaviour.
373 // Evan: from my tests
374 // Martin: FlashPlayer6 gives 0; FP9 gives NaN.
375 return (version
>= 7 ? NaN
: 0);
379 return getBool() ? 1 : 0;
386 // @@ Moock says the result here should be
387 // "the return value of the object's valueOf()
390 // Arrays and Movieclips should return NaN.
392 as_value ret
= to_primitive(NUMBER
);
393 return ret
.to_number(version
);
395 catch (const ActionTypeError
& e
) {
396 #if GNASH_DEBUG_CONVERSION_TO_PRIMITIVE
397 log_debug("to_primitive(%s, NUMBER) threw an "
398 "ActionTypeError %s", *this, e
.what());
400 if (is_function() && version
< 6) {
410 // This is tested, no valueOf is going
411 // to be invoked for movieclips.
416 // Other object types should return NaN.
421 // Conversion to boolean
423 as_value::to_bool(const int version
) const
429 if (version
>= 7) return !getStr().empty();
430 const double num
= to_number(version
);
431 return num
&& !isNaN(num
);
435 const double d
= getNum();
436 // see testsuite/swfdec/if-6.swf
437 return d
&& ! isNaN(d
);
446 assert(_type
== UNDEFINED
|| _type
== NULLTYPE
|| is_exception());
451 // Return value as an object.
453 as_value::to_object(VM
& vm
) const
462 return getObject(toDisplayObject());
465 return constructObject(vm
, getStr(), NSV::CLASS_STRING
);
468 return constructObject(vm
, getNum(), NSV::CLASS_NUMBER
);
471 return constructObject(vm
, getBool(), NSV::CLASS_BOOLEAN
);
474 // Invalid to convert exceptions.
480 as_value::toMovieClip(bool allowUnloaded
) const
482 if (_type
!= DISPLAYOBJECT
) return nullptr;
484 DisplayObject
*ch
= getCharacter(allowUnloaded
);
485 if (!ch
) return nullptr;
486 return ch
->to_movie();
490 as_value::toDisplayObject(bool allowUnloaded
) const
492 if (_type
!= DISPLAYOBJECT
) return nullptr;
493 return getCharacter(allowUnloaded
);
496 // Return value as an ActionScript function. Returns NULL if value is
497 // not an ActionScript function.
499 as_value::to_function() const
501 if (_type
== OBJECT
) {
502 return getObj()->to_function();
509 as_value::get_object() const
511 if (_type
== OBJECT
) {
519 as_value::set_undefined()
522 _value
= boost::blank();
529 _value
= boost::blank();
533 as_value::set_as_object(as_object
* obj
)
540 if (obj
->displayObject()) {
541 // The static cast is fine as long as the as_object is genuinely
543 _type
= DISPLAYOBJECT
;
544 _value
= CharacterProxy(obj
->displayObject(), getRoot(*obj
));
548 if (_type
!= OBJECT
|| getObj() != obj
) {
555 as_value::equals(const as_value
& v
, int version
) const
558 // First compare values of the same type.
559 if (_type
== v
._type
) return equalsSameType(v
);
561 // Then compare booleans.
562 if (is_bool()) return compareBoolean(*this, v
, version
);
563 if (v
.is_bool()) return compareBoolean(v
, *this, version
);
565 // Then compare any other primitive, including null and undefined, with
567 if (!is_object() && v
.is_object()) {
568 return objectEqualsPrimitive(v
, *this, version
);
571 if (is_object() && !v
.is_object()) {
572 return objectEqualsPrimitive(*this, v
, version
);
575 // Remaining null or undefined values only equate to other null or
577 const bool null
= (is_undefined() || is_null());
578 const bool v_null
= (v
.is_undefined() || v
.is_null());
579 if (null
|| v_null
) return null
== v_null
;
581 // Now compare a number with a string.
582 if (is_number() && v
.is_string()) {
583 return stringEqualsNumber(v
, *this, version
);
585 if (is_string() && v
.is_number()) {
586 return stringEqualsNumber(*this, v
, version
);
589 // Finally compare non-identical objects.
594 p
= to_primitive(NUMBER
);
596 catch (const ActionTypeError
& e
) {}
599 vp
= v
.to_primitive(NUMBER
);
601 catch (const ActionTypeError
& e
) {}
603 // No conversion took place; the result is false
604 if (strictly_equals(p
) && v
.strictly_equals(vp
)) {
608 return p
.equals(vp
, version
);
612 as_value::typeOf() const
629 return is_function() ? "function" : "object";
633 DisplayObject
* ch
= getCharacter();
634 if ( ! ch
) return "movieclip"; // dangling
635 if ( ch
->to_movie() ) return "movieclip"; // bound to movieclip
636 return "object"; // bound to some other DisplayObject
643 if (is_exception()) return "exception";
650 as_value::equalsSameType(const as_value
& v
) const
652 assert(_type
== v
._type
);
663 return _value
== v
._value
;
666 return toDisplayObject() == v
.toDisplayObject();
670 const double a
= getNum();
671 const double b
= v
.getNum();
672 if (isNaN(a
) && isNaN(b
)) return true;
676 if (is_exception()) return false;
684 as_value::strictly_equals(const as_value
& v
) const
686 if ( _type
!= v
._type
) return false;
687 return equalsSameType(v
);
691 as_value::setReachable() const
697 as_object
* op
= getObj();
698 if (op
) op
->setReachable();
703 CharacterProxy sp
= getCharacterProxy();
712 as_value::getObj() const
714 assert(_type
== OBJECT
);
715 return boost::get
<as_object
*>(_value
);
719 as_value::getCharacterProxy() const
721 assert(_type
== DISPLAYOBJECT
);
722 return boost::get
<CharacterProxy
>(_value
);
726 as_value::getCharacter(bool allowUnloaded
) const
728 return getCharacterProxy().get(allowUnloaded
);
732 as_value::set_string(const std::string
& str
)
739 as_value::set_double(double val
)
746 as_value::set_bool(bool val
)
753 as_value::is_function() const
755 return _type
== OBJECT
&& getObj()->to_function();
759 as_value::writeAMF0(amf::Writer
& w
) const
762 assert (!is_exception());
767 log_unimpl(_("serialization of as_value of type %d"), _type
);
771 if (is_function()) return false;
772 return w
.writeObject(getObj());
775 return w
.writeString(getStr());
778 return w
.writeNumber(getNum());
782 return w
.writeUndefined();
785 return w
.writeNull();
788 return w
.writeBoolean(getBool());
793 parseNonDecimalInt(const std::string
& s
, double& d
, bool whole
)
795 const std::string::size_type slen
= s
.length();
797 // "0#" would still be octal, but has the same value as a decimal.
798 if (slen
< 3) return false;
800 bool negative
= false;
802 if (s
[0] == '0' && (s
[1] == 'x' || s
[1] == 'X')) {
803 // The only legitimate place for a '-' is after 0x. If it's a
804 // '+' we don't care, as it won't disturb the conversion.
805 std::string::size_type start
= 2;
810 d
= parsePositiveInt(s
.substr(start
), BASE_HEX
, whole
);
811 if (negative
) d
= -d
;
814 else if ((s
[0] == '0' || ((s
[0] == '-' || s
[0] == '+') && s
[1] == '0')) &&
815 s
.find_first_not_of("01234567", 1) == std::string::npos
) {
817 std::string::size_type start
= 0;
822 d
= parsePositiveInt(s
.substr(start
), BASE_OCT
, whole
);
823 if (negative
) d
= -d
;
832 doubleToString(double val
, int radix
)
836 // e.g. for 9*.1234567890123456789:
849 // For 1*.111111111111111111111111111111111111:
853 // 1.11111111111111e+15
854 // 1.11111111111111e+16
856 // For 1.234567890123456789 * 10^-i:
859 // 0.0123456789012346
860 // 0.00123456789012346
861 // 0.000123456789012346
862 // 0.0000123456789012346
863 // 0.00000123456789012346
864 // 1.23456789012346e-6
865 // 1.23456789012346e-7
867 // Handle non-numeric values.
868 if (isNaN(val
)) return "NaN";
870 if (isInf(val
)) return val
< 0 ? "-Infinity" : "Infinity";
872 if (val
== 0.0 || val
== -0.0) return "0";
874 std::ostringstream ostr
;
878 // ActionScript always expects dot as decimal point.
879 ostr
.imbue(std::locale::classic());
881 // force to decimal notation for this range (because the
882 // reference player does)
883 if (std::abs(val
) < 0.0001 && std::abs(val
) >= 0.00001) {
885 // All nineteen digits (4 zeros + up to 15 significant digits)
886 ostr
<< std::fixed
<< std::setprecision(19) << val
;
888 std::string str
= ostr
.str();
890 // Because 'fixed' also adds trailing zeros, remove them.
891 std::string::size_type pos
= str
.find_last_not_of('0');
892 if (pos
!= std::string::npos
) {
898 ostr
<< std::setprecision(15) << val
;
900 std::string str
= ostr
.str();
902 // Remove a leading zero from 2-digit exponent if any
903 std::string::size_type pos
= str
.find("e", 0);
905 if (pos
!= std::string::npos
&& str
.at(pos
+ 2) == '0') {
906 str
.erase(pos
+ 2, 1);
913 bool negative
= (val
< 0);
914 if (negative
) val
= -val
;
916 double left
= std::floor(val
);
917 if (left
< 1) return "0";
920 const std::string digits
= "0123456789abcdefghijklmnopqrstuvwxyz";
922 // Construct the string backwards for speed, then reverse.
925 left
= std::floor(left
/ radix
);
927 str
.push_back(digits
[static_cast<int>(n
)]);
929 if (negative
) str
.push_back('-');
931 std::reverse(str
.begin(), str
.end());
938 /// Checks for equality between an object value and a primitive value
940 /// @param obj An as_value of object type. Callers must ensure this
941 /// condition is met.
942 /// @param prim An as_value of primitive type. Callers must ensure this
943 /// condition is met.
945 /// This is a function try-block.
947 objectEqualsPrimitive(const as_value
& obj
, const as_value
& prim
, int version
)
950 assert(obj
.is_object());
951 assert(!prim
.is_object());
953 as_value tmp
= obj
.to_primitive(as_value::NUMBER
);
954 if (obj
.strictly_equals(tmp
)) return false;
955 return tmp
.equals(prim
, version
);
957 catch (const ActionTypeError
&) {
961 /// @param boolean A boolean as_value
962 /// @param other An as_value of any type.
964 compareBoolean(const as_value
& boolean
, const as_value
& other
, int version
)
966 assert(boolean
.is_bool());
967 return as_value(boolean
.to_number(version
)).equals(other
, version
);
971 stringEqualsNumber(const as_value
& str
, const as_value
& num
, int version
)
973 assert(num
.is_number());
974 assert(str
.is_string());
975 const double n
= str
.to_number(version
);
976 if (!isFinite(n
)) return false;
977 return num
.strictly_equals(n
);
981 /// Returns a member only if it is an object.
983 findMethod(as_object
& obj
, const ObjectURI
& m
, as_value
& ret
)
985 return obj
.get_member(m
, &ret
) && ret
.is_object();
988 /// Construct an instance of the specified global class.
990 /// If the class is not present or is not a constructor function, this
991 /// function throws an ActionTypeError.
993 /// TODO: consider whether ActionTypeError is an appropriate exception.
994 /// TODO: test the other failure cases.
997 constructObject(VM
& vm
, const T
& arg
, const ObjectURI
& className
)
1000 as_object
& gl
= *vm
.getGlobal();
1004 // This is tested in actionscript.all/Object.as to return an
1005 // undefined value. We throw the exception back to the VM, which pushes
1006 // an undefined value onto the stack.
1007 if (!gl
.get_member(className
, &clval
) ) {
1008 throw ActionTypeError();
1011 // This is not properly tested.
1012 if (!clval
.is_function()) {
1013 throw ActionTypeError();
1016 as_function
* ctor
= clval
.to_function();
1018 // This is also not properly tested.
1019 if (!ctor
) throw ActionTypeError();
1024 as_environment
env(vm
);
1025 as_object
* ret
= constructInstance(*ctor
, env
, args
);
1031 } // unnamed namespace
1034 operator<<(std::ostream
& o
, const as_value
& v
)
1039 case as_value::UNDEFINED
:
1040 return o
<< "[undefined]";
1041 case as_value::NULLTYPE
:
1042 return o
<< "[null]";
1043 case as_value::BOOLEAN
:
1045 std::ios::fmtflags
f(o
.flags());
1046 o
<< "[bool:" << std::boolalpha
<< v
.getBool() << "]";
1050 case as_value::OBJECT
:
1052 as_object
* obj
= v
.getObj();
1054 const std::string desc
= obj
->array() ? "array" :
1055 obj
->relay() ? typeName(*obj
->relay()) : typeName(*obj
);
1056 return o
<< "[object(" << desc
<< "):" << static_cast<void*>(obj
)
1059 case as_value::STRING
:
1060 return o
<< "[string:" + v
.getStr() + "]";
1061 case as_value::NUMBER
:
1062 return o
<< "[number:" << v
.getNum() << "]";
1063 case as_value::DISPLAYOBJECT
:
1066 const CharacterProxy
& sp
= v
.getCharacterProxy();
1067 if (sp
.isDangling()) {
1068 DisplayObject
* rebound
= sp
.get();
1070 ret
= boost::format("[rebound %s(%s):%p]") %
1071 typeName(*rebound
) % sp
.getTarget() %
1072 static_cast<void*>(rebound
);
1075 ret
= boost::format("[dangling DisplayObject:%s]") %
1080 DisplayObject
* ch
= sp
.get();
1081 ret
= boost::format("[%s(%s):%p]") % typeName(*ch
) %
1082 sp
.getTarget() % static_cast<void*>(ch
);
1084 return o
<< ret
.str();
1087 assert(v
.is_exception());
1088 return o
<< "[exception]";
1093 } // namespace gnash
1098 // indent-tabs-mode: nil