Exemplo n.º 1
0
static inline HRESULT enumvar_get_next_item(EnumeratorInstance *This)
{
    HRESULT hres;
    VARIANT nextitem;

    if (This->atend)
        return S_OK;

    /* dont leak pervious value */
    jsval_release(This->item);

    /* not at end ... get next item */
    VariantInit(&nextitem);
    hres = IEnumVARIANT_Next(This->enumvar, 1, &nextitem, NULL);
    if (hres == S_OK)
    {
        hres = variant_to_jsval(&nextitem, &This->item);
        VariantClear(&nextitem);
        if (FAILED(hres))
        {
            WARN("failed to convert jsval to variant!");
            This->item = jsval_undefined();
            return hres;
        }
    }
    else
    {
        This->item = jsval_undefined();
        This->atend = TRUE;
    }

    return S_OK;
}
Exemplo n.º 2
0
/* ECMA-262 3rd Edition    15.4.4.9 */
static HRESULT Array_shift(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, unsigned argc, jsval_t *argv,
        jsval_t *r)
{
    jsdisp_t *jsthis;
    DWORD length = 0, i;
    jsval_t v, ret;
    HRESULT hres;

    TRACE("\n");

    hres = get_length(ctx, vthis, &jsthis, &length);
    if(FAILED(hres))
        return hres;

    if(!length) {
        hres = set_length(jsthis, 0);
        if(FAILED(hres))
            return hres;
    }

    if(!length) {
        if(r)
            *r = jsval_undefined();
        return S_OK;
    }

    hres = jsdisp_get_idx(jsthis, 0, &ret);
    if(hres == DISP_E_UNKNOWNNAME) {
        ret = jsval_undefined();
        hres = S_OK;
    }

    for(i=1; SUCCEEDED(hres) && i<length; i++) {
        hres = jsdisp_get_idx(jsthis, i, &v);
        if(hres == DISP_E_UNKNOWNNAME)
            hres = jsdisp_delete_idx(jsthis, i-1);
        else if(SUCCEEDED(hres))
            hres = jsdisp_propput_idx(jsthis, i-1, v);
    }

    if(SUCCEEDED(hres)) {
        hres = jsdisp_delete_idx(jsthis, length-1);
        if(SUCCEEDED(hres))
            hres = set_length(jsthis, length-1);
    }

    if(FAILED(hres))
        return hres;

    if(r)
        *r = ret;
    else
        jsval_release(ret);
    return hres;
}
Exemplo n.º 3
0
static HRESULT Array_pop(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, unsigned argc, jsval_t *argv,
        jsval_t *r)
{
    jsdisp_t *jsthis;
    jsval_t val;
    DWORD length;
    HRESULT hres;

    TRACE("\n");

    hres = get_length(ctx, vthis, &jsthis, &length);
    if(FAILED(hres))
        return hres;

    if(!length) {
        hres = set_length(jsthis, 0);
        if(FAILED(hres))
            return hres;

        if(r)
            *r = jsval_undefined();
        return S_OK;
    }

    length--;
    hres = jsdisp_get_idx(jsthis, length, &val);
    if(SUCCEEDED(hres))
        hres = jsdisp_delete_idx(jsthis, length);
    else if(hres == DISP_E_UNKNOWNNAME) {
        val = jsval_undefined();
        hres = S_OK;
    }else
        return hres;

    if(SUCCEEDED(hres))
        hres = set_length(jsthis, length);

    if(FAILED(hres)) {
        jsval_release(val);
        return hres;
    }

    if(r)
        *r = val;
    else
        jsval_release(val);
    return hres;
}
Exemplo n.º 4
0
Arquivo: json.c Projeto: AmesianX/wine
/* ECMA-262 5.1 Edition    15.12.3 */
static HRESULT JSON_stringify(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r)
{
    stringify_ctx_t stringify_ctx = {ctx, NULL,0,0, NULL,0,0, {0}};
    HRESULT hres;

    TRACE("\n");

    if(argc >= 2 && is_object_instance(argv[1])) {
        FIXME("Replacer %s not yet supported\n", debugstr_jsval(argv[1]));
        return E_NOTIMPL;
    }

    if(argc >= 3) {
        jsval_t space_val;

        hres = maybe_to_primitive(ctx, argv[2], &space_val);
        if(FAILED(hres))
            return hres;

        if(is_number(space_val)) {
            double n = get_number(space_val);
            if(n >= 1) {
                int i, len;
                if(n > 10)
                    n = 10;
                len = floor(n);
                for(i=0; i < len; i++)
                    stringify_ctx.gap[i] = ' ';
                stringify_ctx.gap[len] = 0;
            }
        }else if(is_string(space_val)) {
            jsstr_t *space_str = get_string(space_val);
            size_t len = jsstr_length(space_str);
            if(len > 10)
                len = 10;
            jsstr_extract(space_str, 0, len, stringify_ctx.gap);
        }

        jsval_release(space_val);
    }

    hres = stringify(&stringify_ctx, argv[0]);
    if(SUCCEEDED(hres) && r) {
        assert(!stringify_ctx.stack_top);

        if(hres == S_OK) {
            jsstr_t *ret = jsstr_alloc_len(stringify_ctx.buf, stringify_ctx.buf_len);
            if(ret)
                *r = jsval_string(ret);
            else
                hres = E_OUTOFMEMORY;
        }else {
            *r = jsval_undefined();
        }
    }

    heap_free(stringify_ctx.buf);
    heap_free(stringify_ctx.stack);
    return hres;
}
Exemplo n.º 5
0
static HRESULT invoke_source(script_ctx_t *ctx, FunctionInstance *function, IDispatch *this_obj, unsigned argc, jsval_t *argv,
        jsval_t *r)
{
    jsdisp_t *var_disp, *arg_disp;
    exec_ctx_t *exec_ctx;
    scope_chain_t *scope;
    HRESULT hres;

    if(ctx->state == SCRIPTSTATE_UNINITIALIZED || ctx->state == SCRIPTSTATE_CLOSED) {
        WARN("Script engine state does not allow running code.\n");
        return E_UNEXPECTED;
    }

    if(!function->func_code) {
        FIXME("no source\n");
        return E_FAIL;
    }

    hres = create_var_disp(ctx, function, argc, argv, &var_disp);
    if(FAILED(hres))
        return hres;

    hres = create_arguments(ctx, function, var_disp, argc, argv, &arg_disp);
    if(FAILED(hres)) {
        jsdisp_release(var_disp);
        return hres;
    }

    hres = jsdisp_propput(var_disp, argumentsW, PROPF_DONTDELETE, jsval_obj(arg_disp));
    if(FAILED(hres)) {
        jsdisp_release(arg_disp);
        jsdisp_release(var_disp);
        return hres;
    }

    hres = scope_push(function->scope_chain, var_disp, to_disp(var_disp), &scope);
    if(SUCCEEDED(hres)) {
        hres = create_exec_ctx(ctx, this_obj, var_disp, scope, FALSE, &exec_ctx);
        scope_release(scope);

        if(SUCCEEDED(hres)) {
            jsdisp_t *prev_args;

            prev_args = function->arguments;
            function->arguments = arg_disp;
            hres = exec_source(exec_ctx, function->code, function->func_code, FALSE, r);
            function->arguments = prev_args;

            exec_release(exec_ctx);
        }
    }

    /* Reset arguments value to cut the reference cycle. Note that since all activation contexts have
     * their own arguments property, it's impossible to use prototype's one during name lookup */
    jsdisp_propput_name(var_disp, argumentsW, jsval_undefined());

    jsdisp_release(arg_disp);
    jsdisp_release(var_disp);
    return hres;
}
Exemplo n.º 6
0
static HRESULT array_to_args(script_ctx_t *ctx, jsdisp_t *arg_array, unsigned *argc, jsval_t **ret)
{
    jsval_t *argv, val;
    DWORD length, i;
    HRESULT hres;

    hres = jsdisp_propget_name(arg_array, lengthW, &val);
    if(FAILED(hres))
        return hres;

    hres = to_uint32(ctx, val, &length);
    jsval_release(val);
    if(FAILED(hres))
        return hres;

    argv = heap_alloc(length * sizeof(*argv));
    if(!argv)
        return E_OUTOFMEMORY;

    for(i=0; i<length; i++) {
        hres = jsdisp_get_idx(arg_array, i, argv+i);
        if(hres == DISP_E_UNKNOWNNAME) {
            argv[i] = jsval_undefined();
        }else if(FAILED(hres)) {
            while(i--)
                jsval_release(argv[i]);
            heap_free(argv);
            return hres;
        }
    }

    *argc = length;
    *ret = argv;
    return S_OK;
}
Exemplo n.º 7
0
static HRESULT Enumerator_moveFirst(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
        jsval_t *r)
{
    EnumeratorInstance *This;
    HRESULT hres = S_OK;

    TRACE("\n");

    if (!(This = enumerator_this(jsthis)))
        return throw_type_error(ctx, JS_E_ENUMERATOR_EXPECTED, NULL);

    if (This->enumvar)
    {
        hres = IEnumVARIANT_Reset(This->enumvar);
        if (FAILED(hres))
            return hres;

        This->atend = FALSE;
        hres = enumvar_get_next_item(This);
        if(FAILED(hres))
            return hres;
    }

    if (r)
        *r = jsval_undefined();
    return S_OK;
}
HRESULT variant_to_jsval(VARIANT *var, jsval_t *r)
{
    switch(V_VT(var)) {
    case VT_EMPTY:
        *r = jsval_undefined();
        return S_OK;
    case VT_NULL:
        *r = jsval_null();
        return S_OK;
    case VT_BOOL:
        *r = jsval_bool(V_BOOL(var));
        return S_OK;
    case VT_I4:
        *r = jsval_number(V_I4(var));
        return S_OK;
    case VT_R8:
        *r = jsval_number(V_R8(var));
        return S_OK;
    case VT_BSTR: {
        jsstr_t *str;

        str = jsstr_alloc_len(V_BSTR(var), SysStringLen(V_BSTR(var)));
        if(!str)
            return E_OUTOFMEMORY;
        if(!V_BSTR(var))
            str->length_flags |= JSSTR_FLAG_NULLBSTR;

        *r = jsval_string(str);
        return S_OK;
    }
    case VT_DISPATCH: {
        if(V_DISPATCH(var))
            IDispatch_AddRef(V_DISPATCH(var));
        *r = jsval_disp(V_DISPATCH(var));
        return S_OK;
    }
    case VT_I2:
        *r = jsval_number(V_I2(var));
        return S_OK;
    case VT_INT:
        *r = jsval_number(V_INT(var));
        return S_OK;
    case VT_UNKNOWN:
        if(V_UNKNOWN(var)) {
            IDispatch *disp;
            HRESULT hres;

            hres = IUnknown_QueryInterface(V_UNKNOWN(var), &IID_IDispatch, (void**)&disp);
            if(SUCCEEDED(hres)) {
                *r = jsval_disp(disp);
                return S_OK;
            }
        }
        /* fall through */
    default:
        return jsval_variant(r, var);
    }
}
Exemplo n.º 9
0
/* ECMA-262 3rd Edition    15.4.4.13 */
static HRESULT Array_unshift(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, unsigned argc, jsval_t *argv,
        jsval_t *r)
{
    jsdisp_t *jsthis;
    WCHAR buf[14], *buf_end, *str;
    DWORD i, length;
    jsval_t val;
    DISPID id;
    HRESULT hres;

    TRACE("\n");

    hres = get_length(ctx, vthis, &jsthis, &length);
    if(FAILED(hres))
        return hres;

    if(argc) {
        buf_end = buf + sizeof(buf)/sizeof(WCHAR)-1;
        *buf_end-- = 0;
        i = length;

        while(i--) {
            str = idx_to_str(i, buf_end);

            hres = jsdisp_get_id(jsthis, str, 0, &id);
            if(SUCCEEDED(hres)) {
                hres = jsdisp_propget(jsthis, id, &val);
                if(FAILED(hres))
                    return hres;

                hres = jsdisp_propput_idx(jsthis, i+argc, val);
                jsval_release(val);
            }else if(hres == DISP_E_UNKNOWNNAME) {
                hres = IDispatchEx_DeleteMemberByDispID(vthis->u.dispex, id);
            }
        }

        if(FAILED(hres))
            return hres;
    }

    for(i=0; i<argc; i++) {
        hres = jsdisp_propput_idx(jsthis, i, argv[i]);
        if(FAILED(hres))
            return hres;
    }

    if(argc) {
        length += argc;
        hres = set_length(jsthis, length);
        if(FAILED(hres))
            return hres;
    }

    if(r)
        *r = ctx->version < 2 ? jsval_undefined() : jsval_number(length);
    return S_OK;
}
Exemplo n.º 10
0
static HRESULT Array_indexOf(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, unsigned argc, jsval_t *argv,
        jsval_t *r)
{
    jsdisp_t *jsthis;
    unsigned length, i, from = 0;
    jsval_t search, value;
    BOOL eq;
    HRESULT hres;

    TRACE("\n");

    hres = get_length(ctx, vthis, &jsthis, &length);
    if(FAILED(hres))
        return hres;
    if(!length) {
        if(r) *r = jsval_number(-1);
        return S_OK;
    }

    search = argc ? argv[0] : jsval_undefined();

    if(argc > 1) {
        double from_arg;

        hres = to_integer(ctx, argv[1], &from_arg);
        if(FAILED(hres))
            return hres;

        if(from_arg >= 0)
            from = min(from_arg, length);
        else
            from = max(from_arg + length, 0);
    }

    for(i = from; i < length; i++) {
        hres = jsdisp_get_idx(jsthis, i, &value);
        if(hres == DISP_E_UNKNOWNNAME)
            continue;
        if(FAILED(hres))
            return hres;

        hres = jsval_strict_equal(value, search, &eq);
        jsval_release(value);
        if(FAILED(hres))
            return hres;
        if(eq) {
            if(r) *r = jsval_number(i);
            return S_OK;
        }
    }

    if(r) *r = jsval_number(-1);
    return S_OK;
}
Exemplo n.º 11
0
static HRESULT jsval_variant(jsval_t *val, VARIANT *var)
{
    VARIANT *v;
    HRESULT hres;

    __JSVAL_TYPE(*val) = JSV_VARIANT;
    __JSVAL_VAR(*val) = v = heap_alloc(sizeof(VARIANT));
    if(!v) {
        *val = jsval_undefined();
        return E_OUTOFMEMORY;
    }

    V_VT(v) = VT_EMPTY;
    hres = VariantCopy(v, var);
    if(FAILED(hres)) {
        *val = jsval_undefined();
        heap_free(v);
    }
    return hres;
}
Exemplo n.º 12
0
static HRESULT WINAPI JScriptParse_InitNew(IActiveScriptParse *iface)
{
    JScript *This = impl_from_IActiveScriptParse(iface);
    script_ctx_t *ctx;
    HRESULT hres;

    TRACE("(%p)\n", This);

    if(This->ctx)
        return E_UNEXPECTED;

    ctx = heap_alloc_zero(sizeof(script_ctx_t));
    if(!ctx)
        return E_OUTOFMEMORY;

    ctx->ref = 1;
    ctx->state = SCRIPTSTATE_UNINITIALIZED;
    ctx->active_script = &This->IActiveScript_iface;
    ctx->safeopt = This->safeopt;
    ctx->version = This->version;
    ctx->html_mode = This->html_mode;
    ctx->ei.val = jsval_undefined();
    ctx->acc = jsval_undefined();
    heap_pool_init(&ctx->tmp_heap);

    hres = create_jscaller(ctx);
    if(FAILED(hres)) {
        heap_free(ctx);
        return hres;
    }

    ctx->last_match = jsstr_empty();

    ctx = InterlockedCompareExchangePointer((void**)&This->ctx, ctx, NULL);
    if(ctx) {
        script_release(ctx);
        return E_UNEXPECTED;
    }

    return This->site ? set_ctx_site(This) : S_OK;
}
Exemplo n.º 13
0
static HRESULT init_parameters(jsdisp_t *var_disp, FunctionInstance *function, unsigned argc, jsval_t *argv)
{
    DWORD i=0;
    HRESULT hres;

    for(i=0; i < function->func_code->param_cnt; i++) {
        hres = jsdisp_propput_name(var_disp, function->func_code->params[i],
                i < argc ? argv[i] : jsval_undefined());
        if(FAILED(hres))
            return hres;
    }

    return S_OK;
}
Exemplo n.º 14
0
static HRESULT Array_forEach(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, unsigned argc, jsval_t *argv,
        jsval_t *r)
{
    jsval_t value, args[3], res;
    jsdisp_t *jsthis;
    unsigned length, i;
    HRESULT hres;

    TRACE("\n");

    /* FIXME: Check IsCallable */
    if(argc != 1 || !is_object_instance(argv[0])) {
        FIXME("Unsupported arguments\n");
        return E_NOTIMPL;
    }

    hres = get_length(ctx, vthis, &jsthis, &length);
    if(FAILED(hres))
        return hres;

    for(i = 0; i < length; i++) {
        hres = jsdisp_get_idx(jsthis, i, &value);
        if(hres == DISP_E_UNKNOWNNAME)
            continue;
        if(FAILED(hres))
            return hres;

        args[0] = value;
        args[1] = jsval_number(i);
        args[2] = jsval_obj(jsthis);
        hres = disp_call_value(ctx, get_object(argv[0]), NULL, DISPATCH_METHOD, ARRAY_SIZE(args), args, &res);
        jsval_release(value);
        if(FAILED(hres))
            return hres;
        jsval_release(res);
    }

    if(r) *r = jsval_undefined();
    return S_OK;
}
Exemplo n.º 15
0
/* ECMA-262 3rd Edition    15.4.4.11 */
static HRESULT Array_sort(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, unsigned argc, jsval_t *argv,
        jsval_t *r)
{
    jsdisp_t *jsthis, *cmp_func = NULL;
    jsval_t *vtab, **sorttab = NULL;
    DWORD length;
    DWORD i;
    HRESULT hres = S_OK;

    TRACE("\n");

    hres = get_length(ctx, vthis, &jsthis, &length);
    if(FAILED(hres))
        return hres;

    if(argc > 1) {
        WARN("invalid arg_cnt %d\n", argc);
        return E_FAIL;
    }

    if(argc == 1) {
        if(!is_object_instance(argv[0])) {
            WARN("arg is not dispatch\n");
            return E_FAIL;
        }

        cmp_func = iface_to_jsdisp((IUnknown*)get_object(argv[0]));
        if(!cmp_func || !is_class(cmp_func, JSCLASS_FUNCTION)) {
            WARN("cmp_func is not a function\n");
            if(cmp_func)
                jsdisp_release(cmp_func);
            return E_FAIL;
        }
    }

    if(!length) {
        if(cmp_func)
            jsdisp_release(cmp_func);
        if(r)
            *r = jsval_obj(jsdisp_addref(jsthis));
        return S_OK;
    }

    vtab = heap_alloc_zero(length * sizeof(*vtab));
    if(vtab) {
        for(i=0; i<length; i++) {
            hres = jsdisp_get_idx(jsthis, i, vtab+i);
            if(hres == DISP_E_UNKNOWNNAME) {
                vtab[i] = jsval_undefined();
                hres = S_OK;
            } else if(FAILED(hres)) {
                WARN("Could not get elem %d: %08x\n", i, hres);
                break;
            }
        }
    }else {
        hres = E_OUTOFMEMORY;
    }

    if(SUCCEEDED(hres)) {
        sorttab = heap_alloc(length*2*sizeof(*sorttab));
        if(!sorttab)
            hres = E_OUTOFMEMORY;
    }

    /* merge-sort */
    if(SUCCEEDED(hres)) {
        jsval_t *tmpv, **tmpbuf;
        INT cmp;

        tmpbuf = sorttab + length;
        for(i=0; i < length; i++)
            sorttab[i] = vtab+i;

        for(i=0; i < length/2; i++) {
            hres = sort_cmp(ctx, cmp_func, *sorttab[2*i+1], *sorttab[2*i], &cmp);
            if(FAILED(hres))
                break;

            if(cmp < 0) {
                tmpv = sorttab[2*i];
                sorttab[2*i] = sorttab[2*i+1];
                sorttab[2*i+1] = tmpv;
            }
        }

        if(SUCCEEDED(hres)) {
            DWORD k, a, b, bend;

            for(k=2; k < length; k *= 2) {
                for(i=0; i+k < length; i += 2*k) {
                    a = b = 0;
                    if(i+2*k <= length)
                        bend = k;
                    else
                        bend = length - (i+k);

                    memcpy(tmpbuf, sorttab+i, k*sizeof(jsval_t*));

                    while(a < k && b < bend) {
                        hres = sort_cmp(ctx, cmp_func, *tmpbuf[a], *sorttab[i+k+b], &cmp);
                        if(FAILED(hres))
                            break;

                        if(cmp < 0) {
                            sorttab[i+a+b] = tmpbuf[a];
                            a++;
                        }else {
                            sorttab[i+a+b] = sorttab[i+k+b];
                            b++;
                        }
                    }

                    if(FAILED(hres))
                        break;

                    if(a < k)
                        memcpy(sorttab+i+a+b, tmpbuf+a, (k-a)*sizeof(jsval_t*));
                }

                if(FAILED(hres))
                    break;
            }
        }

        for(i=0; SUCCEEDED(hres) && i < length; i++)
            hres = jsdisp_propput_idx(jsthis, i, *sorttab[i]);
    }

    if(vtab) {
        for(i=0; i < length; i++)
            jsval_release(vtab[i]);
        heap_free(vtab);
    }
    heap_free(sorttab);
    if(cmp_func)
        jsdisp_release(cmp_func);

    if(FAILED(hres))
        return hres;

    if(r)
        *r = jsval_obj(jsdisp_addref(jsthis));
    return S_OK;
}
Exemplo n.º 16
0
HRESULT variant_to_jsval(VARIANT *var, jsval_t *r)
{
    if(V_VT(var) == (VT_VARIANT|VT_BYREF))
        var = V_VARIANTREF(var);

    switch(V_VT(var)) {
    case VT_EMPTY:
        *r = jsval_undefined();
        return S_OK;
    case VT_NULL:
        *r = jsval_null();
        return S_OK;
    case VT_BOOL:
        *r = jsval_bool(V_BOOL(var));
        return S_OK;
    case VT_I4:
        *r = jsval_number(V_I4(var));
        return S_OK;
    case VT_R8:
        *r = jsval_number(V_R8(var));
        return S_OK;
    case VT_BSTR: {
        jsstr_t *str;

        if(V_BSTR(var)) {
            str = jsstr_alloc_len(V_BSTR(var), SysStringLen(V_BSTR(var)));
            if(!str)
                return E_OUTOFMEMORY;
        }else {
            str = jsstr_null_bstr();
        }

        *r = jsval_string(str);
        return S_OK;
    }
    case VT_DISPATCH: {
        if(V_DISPATCH(var))
            IDispatch_AddRef(V_DISPATCH(var));
        *r = jsval_disp(V_DISPATCH(var));
        return S_OK;
    }
    case VT_I2:
        *r = jsval_number(V_I2(var));
        return S_OK;
    case VT_UI2:
        *r = jsval_number(V_UI2(var));
        return S_OK;
    case VT_INT:
        *r = jsval_number(V_INT(var));
        return S_OK;
    case VT_UI4:
        *r = jsval_number(V_UI4(var));
        return S_OK;
    case VT_UI8:
        /*
         * Native doesn't support VT_UI8 here, but it's needed for IE9+ APIs
         * (native IE9 doesn't use jscript.dll for JavaScript).
         */
        *r = jsval_number(V_UI8(var));
        return S_OK;
    case VT_R4:
        *r = jsval_number(V_R4(var));
        return S_OK;
    case VT_UNKNOWN:
        if(V_UNKNOWN(var)) {
            IDispatch *disp;
            HRESULT hres;

            hres = IUnknown_QueryInterface(V_UNKNOWN(var), &IID_IDispatch, (void**)&disp);
            if(SUCCEEDED(hres)) {
                *r = jsval_disp(disp);
                return S_OK;
            }
        }else {
            *r = jsval_disp(NULL);
            return S_OK;
        }
        /* fall through */
    default:
        return jsval_variant(r, var);
    }
}
Exemplo n.º 17
0
Arquivo: json.c Projeto: AmesianX/wine
/* ECMA-262 5.1 Edition    15.12.3 (abstract operation JO) */
static HRESULT stringify_object(stringify_ctx_t *ctx, jsdisp_t *obj)
{
    DISPID dispid = DISPID_STARTENUM;
    jsval_t val = jsval_undefined();
    unsigned prop_cnt = 0, i;
    size_t stepback;
    BSTR prop_name;
    HRESULT hres;

    if(is_on_stack(ctx, obj)) {
        FIXME("Found a cycle\n");
        return E_FAIL;
    }

    if(!stringify_push_obj(ctx, obj))
        return E_OUTOFMEMORY;

    if(!append_char(ctx, '{'))
        return E_OUTOFMEMORY;

    while((hres = IDispatchEx_GetNextDispID(&obj->IDispatchEx_iface, fdexEnumDefault, dispid, &dispid)) == S_OK) {
        jsval_release(val);
        hres = jsdisp_propget(obj, dispid, &val);
        if(FAILED(hres))
            return hres;

        if(is_undefined(val))
            continue;

        stepback = ctx->buf_len;

        if(prop_cnt && !append_char(ctx, ',')) {
            hres = E_OUTOFMEMORY;
            break;
        }

        if(*ctx->gap) {
            if(!append_char(ctx, '\n')) {
                hres = E_OUTOFMEMORY;
                break;
            }

            for(i=0; i < ctx->stack_top; i++) {
                if(!append_string(ctx, ctx->gap)) {
                    hres = E_OUTOFMEMORY;
                    break;
                }
            }
        }

        hres = IDispatchEx_GetMemberName(&obj->IDispatchEx_iface, dispid, &prop_name);
        if(FAILED(hres))
            break;

        hres = json_quote(ctx, prop_name, SysStringLen(prop_name));
        SysFreeString(prop_name);
        if(FAILED(hres))
            break;

        if(!append_char(ctx, ':') || (*ctx->gap && !append_char(ctx, ' '))) {
            hres = E_OUTOFMEMORY;
            break;
        }

        hres = stringify(ctx, val);
        if(FAILED(hres))
            break;

        if(hres == S_FALSE) {
            ctx->buf_len = stepback;
            continue;
        }

        prop_cnt++;
    }
    jsval_release(val);
    if(FAILED(hres))
        return hres;

    if(prop_cnt && *ctx->gap) {
        if(!append_char(ctx, '\n'))
            return E_OUTOFMEMORY;

        for(i=1; i < ctx->stack_top; i++) {
            if(!append_string(ctx, ctx->gap)) {
                hres = E_OUTOFMEMORY;
                break;
            }
        }
    }

    if(!append_char(ctx, '}'))
        return E_OUTOFMEMORY;

    stringify_pop_obj(ctx);
    return S_OK;
}