Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem with creating new class type from C code and displaying list of props/functions from it. #315

Closed
reinholdPL opened this issue Jun 5, 2024 · 16 comments

Comments

@reinholdPL
Copy link

When i'm creating my own class in C code (like in examples/point.c) and using it in QuickJS why can't i see props and functions from it when i'm running java script code? I mean I can access and use them, but when I'm trying to JSON.stringify that object, or assign it to another object data from class data is not visible (or it's lost in case of assign). For e.g. when I run:

import {Point} from "./examples/point.so";
let point = new Point(5,5);
console.log("point.x = "+point.x);
point.x = 10;
console.log("point.x =" + point.x);
console.log("point stringified = " + JSON.stringify(point, null,2));
let point2 = Object.assign({}, point);
console.log("point2.x = " + point2.x);

result is:

point.x = 5
point.x = 10
point stringified = {}
point2.x = undefined

TooTallNate pushed a commit to TooTallNate/quickjs that referenced this issue Jul 3, 2024
GitHub recently upgraded the ubuntu-latest images and I suspect that is
the cause of the linux-asan and linux-msan failures. Pin to the old LTS
for now.

Fixes: quickjs-ng/quickjs#314
@leoplo
Copy link

leoplo commented Oct 10, 2024

The problem looks to come from js_get_length64(ctx, &len, val) in function js_json_to_str which returns 0 for classes defined in C code.

@leoplo
Copy link

leoplo commented Oct 10, 2024

Looks like (*(*(JSObject *)val.u.ptr)->shape)->prop_count is 0 for classes defined in C code instead of the expected value 2 in your example which makes JS_GetOwnPropertyNamesInternal return a empty array.

@reinholdPL
Copy link
Author

reinholdPL commented Oct 10, 2024 via email

@leoplo
Copy link

leoplo commented Oct 10, 2024

I think it is dangerous to set prop_count manually as it seems used for various allocations. I am currently looking at JS_NewObjectProtoClass as I guess there is something wrong with shape creation of classes defined in C. By the way I noticed Object.keys(point) is empty and I guess it is also because wrongly defined prop_count.

@leoplo
Copy link

leoplo commented Oct 10, 2024

Well I do not get where prop_count is supposed to be correctly defined but I guess this issue could be renamed "null prop_count for C defined classes" where the consequence is empty results when using JSON.stringify or Object.keys .

@saghul
Copy link
Contributor

saghul commented Oct 10, 2024

Are the properties defined as enunerable?

@leoplo
Copy link

leoplo commented Oct 11, 2024

@saghul nice catch, indeed the following code:

import {Point} from "./examples/point.so";

class Point2 {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

let point = new Point(5,5);
console.log(point.propertyIsEnumerable("x"));
let point2 = new Point2(5, 5);
console.log(point2.propertyIsEnumerable("x"));

produces the output:

false
true

Therefore it might be by design that properties are not enumerable, a curious choice from my point of view as it is enumerable by default in Javascript.

@reinholdPL
Copy link
Author

how can I define props as enumerable? adding JS_PROP_ENUMERABLE alongside other object properties would be enough?

@saghul
Copy link
Contributor

saghul commented Oct 11, 2024

This is because of how those properties are defined in that particular sample.

quickjs/quickjs.h

Line 1050 in 6e2e68f

#define JS_CGETSET_MAGIC_DEF(name, fgetter, fsetter, magic) { name, JS_PROP_CONFIGURABLE, JS_DEF_CGETSET_MAGIC, magic, .u = { .getset = { .get = { .getter_magic = fgetter }, .set = { .setter_magic = fsetter } } } }

When defining it like that it will only be configurable, not enumerable.

@reinholdPL
Copy link
Author

so changing it to JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE would solve my problem?

@saghul
Copy link
Contributor

saghul commented Oct 11, 2024

Yes. In NG we have this:

#define JS_CGETSET_DEF2(name, fgetter, fsetter, prop_flags) { name, prop_flags, JS_DEF_CGETSET, 0, { .getset = { .get = { .getter = fgetter }, .set = { .setter = fsetter } } } }

Which you can copy and paste in your project, which defines a getter / setter and allows you to decide which specific flags you want.

@leoplo
Copy link

leoplo commented Oct 11, 2024

Why not use JS_PROP_DOUBLE_DEF with proper prop_flags rather than JS_CGETSET_MAGIC_DEF?
#define JS_PROP_DOUBLE_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_DOUBLE, 0, .u = { .f64 = val } }

@saghul
Copy link
Contributor

saghul commented Oct 11, 2024

Because the example is trying to showcase those APIs, in your app you do you :-)

@leoplo
Copy link

leoplo commented Oct 11, 2024

I get the point of the example, I was just wondering why using JS_CGETSET_DEF2 ? I guess if one wants to define enumerable properties then the solution is to use JS_PROP_... functions in js_..._proto_funcs.

@reinholdPL
Copy link
Author

thank you all, for me this issue can be closed. I think it's sad that there does not exist some wiki for users with "problems" like this :)

@leoplo
Copy link

leoplo commented Oct 11, 2024

For someone searching for classes with enumerable properties, I think the point example class could with written as:

#include "../quickjs.h"
#include <math.h>

#define countof(x) (sizeof(x) / sizeof((x)[0]))

/* Point Class */

static JSClassID js_point_class_id;

static void js_point_finalizer(JSRuntime *rt, JSValue val)
{
    return;
}

static JSValue js_point_ctor(JSContext *ctx,
                             JSValueConst new_target,
                             int argc, JSValueConst *argv)
{
    JSValue obj = JS_UNDEFINED;
    JSValue proto;

    /* using new_target to get the prototype is necessary when the
       class is extended. */
    proto = JS_GetPropertyStr(ctx, new_target, "prototype");
    if (JS_IsException(proto))
        goto fail;
    obj = JS_NewObjectProtoClass(ctx, proto, js_point_class_id);
    JS_FreeValue(ctx, proto);
    JS_DefinePropertyValueStr(ctx, obj, "x", argv[0], JS_PROP_C_W_E);
    JS_DefinePropertyValueStr(ctx, obj, "y", argv[1], JS_PROP_C_W_E);
    if (JS_IsException(obj))
        goto fail;
    return obj;
 fail:
    JS_FreeValue(ctx, obj);
    return JS_EXCEPTION;
}

static JSValue js_point_norm(JSContext *ctx, JSValueConst this_val,
                             int argc, JSValueConst *argv)
{
    JSValue val;
    int ret, x, y;

    val = JS_GetPropertyStr(ctx, this_val, "x");
    if (JS_IsException(val))
      return JS_EXCEPTION;
    ret = JS_ToInt32(ctx, &x, val);
    JS_FreeValue(ctx, val);
    if (ret)
      return JS_EXCEPTION;

    val = JS_GetPropertyStr(ctx, this_val, "y");
    if (JS_IsException(val))
      return JS_EXCEPTION;
    ret = JS_ToInt32(ctx, &y, val);
    JS_FreeValue(ctx, val);
    if (ret)
      return JS_EXCEPTION;

    return JS_NewFloat64(ctx, sqrt((double)x * x + (double)y * y));
}

static JSClassDef js_point_class = {
    "Point",
    .finalizer = js_point_finalizer,
};

static const JSCFunctionListEntry js_point_proto_funcs[] = {
    JS_CFUNC_DEF("norm", 0, js_point_norm),
};

static int js_point_init(JSContext *ctx, JSModuleDef *m)
{
    JSValue point_proto, point_class;

    /* create the Point class */
    JS_NewClassID(&js_point_class_id);
    JS_NewClass(JS_GetRuntime(ctx), js_point_class_id, &js_point_class);

    point_proto = JS_NewObject(ctx);
    JS_SetPropertyFunctionList(ctx, point_proto, js_point_proto_funcs, countof(js_point_proto_funcs));

    point_class = JS_NewCFunction2(ctx, js_point_ctor, "Point", 2, JS_CFUNC_constructor, 0);
    /* set proto.constructor and ctor.prototype */
    JS_SetConstructor(ctx, point_class, point_proto);
    JS_SetClassProto(ctx, js_point_class_id, point_proto);

    JS_SetModuleExport(ctx, m, "Point", point_class);
    return 0;
}

JSModuleDef *js_init_module(JSContext *ctx, const char *module_name)
{
    JSModuleDef *m;
    m = JS_NewCModule(ctx, module_name, js_point_init);
    if (!m)
        return NULL;
    JS_AddModuleExport(ctx, m, "Point");
    return m;
}

leoplo added a commit to leoplo/quickjs that referenced this issue Oct 11, 2024
It is probably not trivial to guess that classes created like in the point
example do not have enumerable properties (at least it was not for bellard#315).
Therefore PointEnumerable class describes how to implement such class.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants