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

[dashboard] Uncaught RuntimeError: memory access out of bounds #415

Open
dong-king opened this issue Jun 14, 2024 · 10 comments
Open

[dashboard] Uncaught RuntimeError: memory access out of bounds #415

dong-king opened this issue Jun 14, 2024 · 10 comments
Assignees
Labels

Comments

@dong-king
Copy link

Describe the bug
When I tested a certain action separately, it was normal. But when I put it into the project, it sometimes exposed a memory error:memory access out of bounds.
I don't know if it is because my dashboard project has many business functions, which caused the memory to exceed the bounds.

Is the memory size defined in the memory.h file of eez-framework:

extern uint8_t g_memory[];
static uint8_t * const MEMORY_BEGIN = g_memory;
static const uint32_t MEMORY_SIZE = 64 * 1024 * 1024;

Is there any way to dynamically adjust the memory size?

Screenshots
image

@mvladic
Copy link
Contributor

mvladic commented Jun 14, 2024

I don't think this is memory size issue. If you can post the eez-project file with which I can reproduce this problem it would be of great help.

@dong-king
Copy link
Author

Sometimes I want to execute a slightly complex js code directly in the action, so I customize an action based on your EvalJSExprActionComponent, as follows:

export class JsActionComponent extends ActionComponent {
    static classInfo = makeDerivedClassInfo(ActionComponent.classInfo, {
        componentPaletteGroupName: "Dashboard Specific",
        label: () => "JS",
        componentPaletteLabel: "JS",
        properties: [
            {
                name: "expression",
                type: PropertyType.MultilineText,
                propertyGridGroup: specificGroup,
                monospaceFont: true,
                disableSpellcheck: true,
                flowProperty: "template-literal"
            },
            makeExpressionProperty(
                {
                    // The escape character corresponding to '{'
                    name: "leftEscape",
                    type: PropertyType.MultilineText,
                    propertyGridGroup: specificGroup
                },
                "string"
            ),
            makeExpressionProperty(
                {
                    // // The escape character corresponding to '}'
                    name: "rightEscape",
                    type: PropertyType.MultilineText,
                    propertyGridGroup: specificGroup
                },
                "string"
            )
        ],
        beforeLoadHook: (
            component: JsActionComponent,
            jsComponent: Partial<JsActionComponent>
        ) => {
            if (
                !jsComponent.customOutputs ||
                jsComponent.customOutputs.length == 0
            ) {
                jsComponent.customOutputs = [
                    {
                        name: "result",
                        type: "any"
                    }
                ] as any;
            }
        },
        check: (component: EvalJSExprActionComponent, messages: IMessage[]) => {
            const { valueExpressions } = component.expandExpressionForBuild();

            valueExpressions.forEach(valueExpression => {
                try {
                    checkExpression(component, valueExpression);
                } catch (err) {
                    messages.push(
                        new Message(
                            MessageType.ERROR,
                            `Invalid expression "${valueExpression}": ${err}`,
                            getChildOfObject(component, "expression")
                        )
                    );
                }
            });
        },
        icon: (
            <svg viewBox="0 0 1664 1792">
                <path d="M384 1536q0-53-37.5-90.5T256 1408t-90.5 37.5T128 1536t37.5 90.5T256 1664t90.5-37.5T384 1536zm384 0q0-53-37.5-90.5T640 1408t-90.5 37.5T512 1536t37.5 90.5T640 1664t90.5-37.5T768 1536zm-384-384q0-53-37.5-90.5T256 1024t-90.5 37.5T128 1152t37.5 90.5T256 1280t90.5-37.5T384 1152zm768 384q0-53-37.5-90.5T1024 1408t-90.5 37.5T896 1536t37.5 90.5 90.5 37.5 90.5-37.5 37.5-90.5zm-384-384q0-53-37.5-90.5T640 1024t-90.5 37.5T512 1152t37.5 90.5T640 1280t90.5-37.5T768 1152zM384 768q0-53-37.5-90.5T256 640t-90.5 37.5T128 768t37.5 90.5T256 896t90.5-37.5T384 768zm768 384q0-53-37.5-90.5T1024 1024t-90.5 37.5T896 1152t37.5 90.5 90.5 37.5 90.5-37.5 37.5-90.5zM768 768q0-53-37.5-90.5T640 640t-90.5 37.5T512 768t37.5 90.5T640 896t90.5-37.5T768 768zm768 768v-384q0-52-38-90t-90-38-90 38-38 90v384q0 52 38 90t90 38 90-38 38-90zm-384-768q0-53-37.5-90.5T1024 640t-90.5 37.5T896 768t37.5 90.5T1024 896t90.5-37.5T1152 768zm384-320V192q0-26-19-45t-45-19H192q-26 0-45 19t-19 45v256q0 26 19 45t45 19h1280q26 0 45-19t19-45zm0 320q0-53-37.5-90.5T1408 640t-90.5 37.5T1280 768t37.5 90.5T1408 896t90.5-37.5T1536 768zm128-640v1536q0 52-38 90t-90 38H128q-52 0-90-38t-38-90V128q0-52 38-90t90-38h1408q52 0 90 38t38 90z" />
            </svg>
        ),
        componentHeaderColor: "#A6BBCF",
        defaultValue: {
            leftEscape: `"#l#"`,
            rightEscape: `"#r#"`,
            customOutputs: [
                {
                    name: "result",
                    type: "any"
                }
            ]
        },

        execute: async (context: IDashboardComponentContext) => {
            // const expression = context.evalProperty<string>("expression");
            // console.log(expression);
            let leftEscape = context.evalProperty("leftEscape");
            if (
                leftEscape == undefined ||
                typeof leftEscape != "string" ||
                leftEscape.trim() == ""
            ) {
                context.throwError(`Invalid leftEscape property`);
                return;
            }
            let rightEscape = context.evalProperty("rightEscape");
            if (
                rightEscape == undefined ||
                typeof rightEscape != "string" ||
                rightEscape.trim() == ""
            ) {
                context.throwError(`Invalid rightEscape property`);
                return;
            }
            let expression = context.getStringParam(0);
            const expressionValues = context.getExpressionListParam(4);
            const values: any = {};
            for (let i = 0; i < expressionValues.length; i++) {
                const name = `_val${i}`;
                values[name] = expressionValues[i];
            }

            let regex = new RegExp(`${leftEscape}`, "g");
            expression = expression.replace(regex, `{`);

            regex = new RegExp(`${rightEscape}`, "g");
            expression = expression.replace(regex, `}`);

            context = context.startAsyncExecution();

            let param = {
                context,
                values
            };
            (async function (
                code: string,
                globalModules: { [moduleName: string]: any}) {
                try {
                    const moduleNames = Object.keys(globalModules);
                    const args = moduleNames.join(", ");
                    const factoryFnCode = `return async (${args}) => {
                        ${code}
                    }`;
                    const factoryFn = new Function(factoryFnCode);
                    console.log(factoryFnCode);
                    const fn = factoryFn();
                    await fn(
                        ...moduleNames.map(moduleName => globalModules[moduleName])
                    );
                } catch (err) {
                    context.throwError(err.toString());
                } finally {
                    context.endAsyncExecution();
                }
            })(expression || "", param);
        }
    });

    expression: string;

    constructor() {
        super();

        makeObservable(this, {
            expression: observable
        });
    }

    static parse(expression: string) {
        const inputs = new Set<string>();
        if (expression) {
            EvalJSExprActionComponent.PARAMS_REGEXP.lastIndex = 0;
            let str = expression;
            while (true) {
                let matches = str.match(
                    EvalJSExprActionComponent.PARAMS_REGEXP
                );
                if (!matches) {
                    break;
                }
                const input = matches[1].trim();
                inputs.add(input);
                str = str.substring(matches.index! + matches[1].length);
            }
        }

        return Array.from(inputs.keys());
    }

    getInputs() {
        return [
            {
                name: "@seqin",
                type: "any" as ValueType,
                isSequenceInput: true,
                isOptionalInput: true
            },
            ...super.getInputs()
        ];
    }

    getOutputs(): ComponentOutput[] {
        return [
            {
                name: "@seqout",
                type: "null",
                isSequenceOutput: true,
                isOptionalOutput: true
            },
            ...super.getOutputs()
        ];
    }

    getBody(flowContext: IFlowContext): React.ReactNode {
        return (
            <div className="body">
                <pre>{this.expression}</pre>
            </div>
        );
    }

    expandExpressionForBuild() {
        let expression = this.expression;
        let valueExpressions: any[] = [];

        JsActionComponent.parse(expression).forEach(
            (valueExpression: any, i: number) => {
                const name = `_val${i}`;
                valueExpressions.push(valueExpression);
                let regex = new RegExp(`{${valueExpression}}`, "g");
                expression = expression.replace(regex, `values.${name}`);
            }
        );

        return { expression, valueExpressions };
    }

    buildFlowComponentSpecific(assets: Assets, dataBuffer: DataBuffer) {
        const { expression, valueExpressions } =
            this.expandExpressionForBuild();

        dataBuffer.writeObjectOffset(() => dataBuffer.writeString(expression));

        dataBuffer.writeArray(valueExpressions, valueExpression => {
            try {
                // as property
                buildExpression(assets, dataBuffer, this, valueExpression);
            } catch (err) {
                assets.projectStore.outputSectionsStore.write(
                    Section.OUTPUT,
                    MessageType.ERROR,
                    err,
                    getChildOfObject(this, "expression")
                );

                dataBuffer.writeUint16NonAligned(makeEndInstruction());
            }
        });
    }
}

registerClass("JsActionComponent", JsActionComponent);

Because { and } cannot be used, I provide leftEscape (default is #l#) and rightEscape (default is #r#) to escape { and }.

The following screenshot is a simple usage:
image
This custom action works fine in most cases. But sometimes it prompts: Uncaught RuntimeError: memory access out of bounds and cannot read properties of null (reading 'context').
image

I cant find the reason.

BTW If you think this action is useful, you can merge it into eez-studio.

@mvladic
Copy link
Contributor

mvladic commented Jun 14, 2024

BTW If you think this action is useful, you can merge it into eez-studio.

Yes, this is useful. I was aware of this issue with { and }, but I wasn't sure which way is the best to resolve it. I think maybe better option is to set left escape and right escape for the EEZ Flow expressions not for the JavaScript's { and }.

@mvladic
Copy link
Contributor

mvladic commented Jun 14, 2024

BTW There is JSON.parse action, so you don't need to use EvalJS for that.

@mvladic
Copy link
Contributor

mvladic commented Jun 14, 2024

I tried your Action with the same JavaScript code and I didn't see the problem so far. Can you tell me here:

image

What type you selected for the result output and in input?

@mvladic
Copy link
Contributor

mvladic commented Jun 14, 2024

This particular error I had before:

image

and it is fixed now in the latest version of Studio. You are probably on some older version.

@dong-king
Copy link
Author

dong-king commented Jun 14, 2024

What type you selected for the result output and in input?

result and in correspond to my custom structure.
The previous screenshot is just to illustrate how to use this action, not the action that will cause an error.

In fact, an action that cause errors will not fail every time.
I have a dashboard project with a lot of business logic. When I put the actions that cause errors in this project into a new dashboard project, they will not cause errors.

Maybe there are other reasons in this complex dashboard project that cause errors.

Basically, the errors occur when const factoryFn = new Function(factoryFnCode); or await fn( ...moduleNames.map(moduleName => globalModules[moduleName]) );is executed.

@mvladic
Copy link
Contributor

mvladic commented Jun 14, 2024

Can you log the value of inputSelectInfoStr, I want to know the value when error occurs. Also, how structure WAVE_TABLE_SOURCE_... is defined?

@dong-king
Copy link
Author

This particular error I had before:

image

and it is fixed now in the latest version of Studio. You are probably on some older version.
My colleague synchronized to version 0.13.0.

@mvladic
Copy link
Contributor

mvladic commented Jun 14, 2024

Never mind, this error in lz4.js isn't important.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants