1/*
2 * Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "JSClassRef.h"
28
29#include "APICast.h"
30#include "JSCallbackObject.h"
31#include "JSObjectRef.h"
32#include <runtime/InitializeThreading.h>
33#include <runtime/JSGlobalObject.h>
34#include <runtime/ObjectPrototype.h>
35#include <runtime/Identifier.h>
36#include <wtf/text/StringHash.h>
37#include <wtf/unicode/UTF8.h>
38
39using namespace std;
40using namespace JSC;
41using namespace WTF::Unicode;
42
43const JSClassDefinition kJSClassDefinitionEmpty = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
44
45static inline UString tryCreateStringFromUTF8(const char* string)
46{
47    if (!string)
48        return UString();
49
50    size_t length = strlen(string);
51    Vector<UChar, 1024> buffer(length);
52    UChar* p = buffer.data();
53    if (conversionOK != convertUTF8ToUTF16(&string, string + length, &p, p + length))
54        return UString();
55
56    return UString(buffer.data(), p - buffer.data());
57}
58
59OpaqueJSClass::OpaqueJSClass(const JSClassDefinition* definition, OpaqueJSClass* protoClass)
60    : parentClass(definition->parentClass)
61    , prototypeClass(0)
62    , initialize(definition->initialize)
63    , finalize(definition->finalize)
64    , hasProperty(definition->hasProperty)
65    , getProperty(definition->getProperty)
66    , setProperty(definition->setProperty)
67    , deleteProperty(definition->deleteProperty)
68    , getPropertyNames(definition->getPropertyNames)
69    , callAsFunction(definition->callAsFunction)
70    , callAsConstructor(definition->callAsConstructor)
71    , hasInstance(definition->hasInstance)
72    , convertToType(definition->convertToType)
73    , m_className(tryCreateStringFromUTF8(definition->className))
74    , m_staticValues(0)
75    , m_staticFunctions(0)
76{
77    initializeThreading();
78
79    if (const JSStaticValue* staticValue = definition->staticValues) {
80        m_staticValues = new OpaqueJSClassStaticValuesTable();
81        while (staticValue->name) {
82            UString valueName = tryCreateStringFromUTF8(staticValue->name);
83            if (!valueName.isNull()) {
84                // Use a local variable here to sidestep an RVCT compiler bug.
85                StaticValueEntry* entry = new StaticValueEntry(staticValue->getProperty, staticValue->setProperty, staticValue->attributes);
86                StringImpl* impl = valueName.impl();
87                StaticValueEntry* existingEntry = m_staticValues->get(impl);
88                m_staticValues->set(impl, entry);
89                delete existingEntry;
90            }
91            ++staticValue;
92        }
93    }
94
95    if (const JSStaticFunction* staticFunction = definition->staticFunctions) {
96        m_staticFunctions = new OpaqueJSClassStaticFunctionsTable();
97        while (staticFunction->name) {
98            UString functionName = tryCreateStringFromUTF8(staticFunction->name);
99            if (!functionName.isNull()) {
100                // Use a local variable here to sidestep an RVCT compiler bug.
101                StaticFunctionEntry* entry = new StaticFunctionEntry(staticFunction->callAsFunction, staticFunction->attributes);
102                StringImpl* impl = functionName.impl();
103                StaticFunctionEntry* existingEntry = m_staticFunctions->get(impl);
104                m_staticFunctions->set(impl, entry);
105                delete existingEntry;
106            }
107            ++staticFunction;
108        }
109    }
110
111    if (protoClass)
112        prototypeClass = JSClassRetain(protoClass);
113}
114
115OpaqueJSClass::~OpaqueJSClass()
116{
117    // The empty string is shared across threads & is an identifier, in all other cases we should have done a deep copy in className(), below.
118    ASSERT(!m_className.length() || !m_className.impl()->isIdentifier());
119
120    if (m_staticValues) {
121        OpaqueJSClassStaticValuesTable::const_iterator end = m_staticValues->end();
122        for (OpaqueJSClassStaticValuesTable::const_iterator it = m_staticValues->begin(); it != end; ++it) {
123            ASSERT(!it->first->isIdentifier());
124            delete it->second;
125        }
126        delete m_staticValues;
127    }
128
129    if (m_staticFunctions) {
130        OpaqueJSClassStaticFunctionsTable::const_iterator end = m_staticFunctions->end();
131        for (OpaqueJSClassStaticFunctionsTable::const_iterator it = m_staticFunctions->begin(); it != end; ++it) {
132            ASSERT(!it->first->isIdentifier());
133            delete it->second;
134        }
135        delete m_staticFunctions;
136    }
137
138    if (prototypeClass)
139        JSClassRelease(prototypeClass);
140}
141
142PassRefPtr<OpaqueJSClass> OpaqueJSClass::createNoAutomaticPrototype(const JSClassDefinition* definition)
143{
144    return adoptRef(new OpaqueJSClass(definition, 0));
145}
146
147PassRefPtr<OpaqueJSClass> OpaqueJSClass::create(const JSClassDefinition* clientDefinition)
148{
149    JSClassDefinition definition = *clientDefinition; // Avoid modifying client copy.
150
151    JSClassDefinition protoDefinition = kJSClassDefinitionEmpty;
152    protoDefinition.finalize = 0;
153    swap(definition.staticFunctions, protoDefinition.staticFunctions); // Move static functions to the prototype.
154
155    // We are supposed to use JSClassRetain/Release but since we know that we currently have
156    // the only reference to this class object we cheat and use a RefPtr instead.
157    RefPtr<OpaqueJSClass> protoClass = adoptRef(new OpaqueJSClass(&protoDefinition, 0));
158    return adoptRef(new OpaqueJSClass(&definition, protoClass.get()));
159}
160
161OpaqueJSClassContextData::OpaqueJSClassContextData(JSC::JSGlobalData&, OpaqueJSClass* jsClass)
162    : m_class(jsClass)
163{
164    if (jsClass->m_staticValues) {
165        staticValues = new OpaqueJSClassStaticValuesTable;
166        OpaqueJSClassStaticValuesTable::const_iterator end = jsClass->m_staticValues->end();
167        for (OpaqueJSClassStaticValuesTable::const_iterator it = jsClass->m_staticValues->begin(); it != end; ++it) {
168            ASSERT(!it->first->isIdentifier());
169            // Use a local variable here to sidestep an RVCT compiler bug.
170            StaticValueEntry* entry = new StaticValueEntry(it->second->getProperty, it->second->setProperty, it->second->attributes);
171            staticValues->add(StringImpl::create(it->first->characters(), it->first->length()), entry);
172        }
173    } else
174        staticValues = 0;
175
176    if (jsClass->m_staticFunctions) {
177        staticFunctions = new OpaqueJSClassStaticFunctionsTable;
178        OpaqueJSClassStaticFunctionsTable::const_iterator end = jsClass->m_staticFunctions->end();
179        for (OpaqueJSClassStaticFunctionsTable::const_iterator it = jsClass->m_staticFunctions->begin(); it != end; ++it) {
180            ASSERT(!it->first->isIdentifier());
181            // Use a local variable here to sidestep an RVCT compiler bug.
182            StaticFunctionEntry* entry = new StaticFunctionEntry(it->second->callAsFunction, it->second->attributes);
183            staticFunctions->add(StringImpl::create(it->first->characters(), it->first->length()), entry);
184        }
185
186    } else
187        staticFunctions = 0;
188}
189
190OpaqueJSClassContextData::~OpaqueJSClassContextData()
191{
192    if (staticValues) {
193        deleteAllValues(*staticValues);
194        delete staticValues;
195    }
196
197    if (staticFunctions) {
198        deleteAllValues(*staticFunctions);
199        delete staticFunctions;
200    }
201}
202
203OpaqueJSClassContextData& OpaqueJSClass::contextData(ExecState* exec)
204{
205    OpaqueJSClassContextData*& contextData = exec->globalData().opaqueJSClassData.add(this, 0).first->second;
206    if (!contextData)
207        contextData = new OpaqueJSClassContextData(exec->globalData(), this);
208    return *contextData;
209}
210
211UString OpaqueJSClass::className()
212{
213    // Make a deep copy, so that the caller has no chance to put the original into IdentifierTable.
214    return UString(m_className.characters(), m_className.length());
215}
216
217OpaqueJSClassStaticValuesTable* OpaqueJSClass::staticValues(JSC::ExecState* exec)
218{
219    OpaqueJSClassContextData& jsClassData = contextData(exec);
220    return jsClassData.staticValues;
221}
222
223OpaqueJSClassStaticFunctionsTable* OpaqueJSClass::staticFunctions(JSC::ExecState* exec)
224{
225    OpaqueJSClassContextData& jsClassData = contextData(exec);
226    return jsClassData.staticFunctions;
227}
228
229/*!
230// Doc here in case we make this public. (Hopefully we won't.)
231@function
232 @abstract Returns the prototype that will be used when constructing an object with a given class.
233 @param ctx The execution context to use.
234 @param jsClass A JSClass whose prototype you want to get.
235 @result The JSObject prototype that was automatically generated for jsClass, or NULL if no prototype was automatically generated. This is the prototype that will be used when constructing an object using jsClass.
236*/
237JSObject* OpaqueJSClass::prototype(ExecState* exec)
238{
239    /* Class (C++) and prototype (JS) inheritance are parallel, so:
240     *     (C++)      |        (JS)
241     *   ParentClass  |   ParentClassPrototype
242     *       ^        |          ^
243     *       |        |          |
244     *  DerivedClass  |  DerivedClassPrototype
245     */
246
247    if (!prototypeClass)
248        return 0;
249
250    OpaqueJSClassContextData& jsClassData = contextData(exec);
251
252    if (!jsClassData.cachedPrototype) {
253        // Recursive, but should be good enough for our purposes
254        jsClassData.cachedPrototype.set(exec->globalData(), new (exec) JSCallbackObject<JSObjectWithGlobalObject>(exec, exec->lexicalGlobalObject(), exec->lexicalGlobalObject()->callbackObjectStructure(), prototypeClass, &jsClassData), 0); // set jsClassData as the object's private data, so it can clear our reference on destruction
255        if (parentClass) {
256            if (JSObject* prototype = parentClass->prototype(exec))
257                jsClassData.cachedPrototype->setPrototype(exec->globalData(), prototype);
258        }
259    }
260    return jsClassData.cachedPrototype.get();
261}
262