Search code examples
c++v8

V8 memory leaks and conditional jumps


I have embedded V8 in my C application. I am able to execute javascript in my C application. However I see bunch of memory leaks, conditional jumps when I tried to run my C application. I have total 4 files.

1. mjse_int.h

typedef struct
{
    std::unique_ptr<v8::Platform> platform;
    v8::Isolate::CreateParams create_params;
    v8::Isolate *isolate;
}MJSEInstance;

2. mjse.h

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Create MJSE instance
*/
void *mjse_create_instance(char **argv);

/**
* @brief Destroy MJSE instance
* @param instance -> MJSE instance
*/
void mjse_destroy_instance(void *minstance);

/**
* @brief Executes a script
* @param minstance - MJSE instance
* @param script - the javascript
* @param scriptlen - Length of script
*/
void mjse_execute_script(void *minstance, char *script, int scriptlen);

#ifdef __cplusplus
}
#endif

#endif

3. mjse.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include/libplatform/libplatform.h"
#include "include/v8.h"
#include "mjse.h"
#include "mjse_int.h"

extern "C"  {

using namespace v8;

void *mjse_create_instance(char **argv)
{
    MJSEInstance *instance = (MJSEInstance *)calloc(1, sizeof(MJSEInstance));

    v8::V8::InitializeICUDefaultLocation(argv[0]);
    v8::V8::InitializeExternalStartupData(argv[0]);
    instance->platform = v8::platform::NewDefaultPlatform();

    v8::V8::InitializePlatform(instance->platform.get());
    v8::V8::Initialize();

    instance->create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
    instance->isolate = v8::Isolate::New(instance->create_params);

    printf("Version: %s\n", v8::V8::GetVersion());

    return instance;
}

void mjse_destroy_instance(void *minstance)
{
    MJSEInstance *instance = (MJSEInstance *)minstance;

    if(instance == NULL)
        return;

    if(instance->isolate)
    {
        instance->isolate->Dispose();
    }

    v8::V8::Dispose();
    v8::V8::ShutdownPlatform();

    if(instance->create_params.array_buffer_allocator)
    {
        delete instance->create_params.array_buffer_allocator;
    }

    free(instance);
}

void mjse_execute_script(void *minstance, char *script, int scriptlen)
{
    MJSEInstance *instance = (MJSEInstance *)minstance;

    v8::Isolate::Scope isolate_scope(instance->isolate);

    // Create a stack-allocated handle scope.
    v8::HandleScope handle_scope(instance->isolate);

    // Create a new context.
    v8::Local<v8::Context> context = v8::Context::New(instance->isolate);

    // Enter the context for compiling and running the hello world script.
    v8::Context::Scope context_scope(context);

    {
        // Create a string containing the JavaScript source code.
        v8::Local<v8::String> source =
          v8::String::NewFromUtf8(instance->isolate, script,
          v8::NewStringType::kNormal).ToLocalChecked();

        // Compile the source code.
        v8::Local<v8::Script> script =
          v8::Script::Compile(context, source).ToLocalChecked();

        // Run the script to get the result.
        v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();

        // Convert the result to an UTF8 string and print it.
        v8::String::Utf8Value utf8(instance->isolate, result);
        printf("%s\n", *utf8);
    }
}
}

Using these 3 files, I have built a .so file. Now my testing code looks as below.

4. main.c

int main(int argc, char **argv)
{
    int len;
    char *script;
    void *instance;

    instance = mjse_create_instance(argv);

    script =    "function js_add_elements1(var1, var2) \
                { \
                    var var3 = parseInt(var1) + parseInt(var2); \
                    var result = 'Addition of ' + var1 + ' and ' + var2 + ' results ' + var3; \
                    return result; \
                };\
                js_add_elements1(2, 3);";

    len = strlen(script);
    mjse_execute_script(instance, script, len);

    script =    "function js_add_elements2(var1, var2) \
                { \
                    var var3 = parseInt(var1) + parseInt(var2); \
                    var result = 'Addition of ' + var1 + ' and ' + var2 + ' results ' + var3; \
                    return result; \
                };\
                js_add_elements2(2, 3);";

    len = strlen(script);
    mjse_execute_script(instance, script, len);

    script =    "function js_add_elements3(var1, var2) \
                { \
                    var var3 = parseInt(var1) + parseInt(var2); \
                    var result = 'Addition of ' + var1 + ' and ' + var2 + ' results ' + var3; \
                    return result; \
                };\
                js_add_elements3(2, 3);";

    len = strlen(script);
    mjse_execute_script(instance, script, len);

    mjse_destroy_instance(instance);

    return 0;
}

With this, I built main.c and tested. The output looks as below.

Version: 7.7.0 (candidate)
Addition of 2 and 3 results 5
Addition of 2 and 3 results 5
Addition of 2 and 3 results 5

From functionality perspective it looks fine. However there quite a bunch of Conditional jumps and memory leaks as show below (only few to keep size less).

==31273== Conditional jump or move depends on uninitialised value(s)
==31273==    at 0x5DB55E8: v8::internal::DeclarationScope::CheckConflictingVarDeclarations() (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x599DCE8: v8::internal::PreParser::PreParseFunction(v8::internal::AstRawString const*, v8::internal::FunctionKind, v8::internal::FunctionLiteral::FunctionType, v8::internal::DeclarationScope*, int*, v8::internal::ProducedPreparseData**, int) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x597C6B5: v8::internal::Parser::SkipFunction(v8::internal::AstRawString const*, v8::internal::FunctionKind, v8::internal::FunctionLiteral::FunctionType, v8::internal::DeclarationScope*, int*, int*, v8::internal::ProducedPreparseData**) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x5970495: v8::internal::Parser::ParseFunctionLiteral(v8::internal::AstRawString const*, v8::internal::Scanner::Location, v8::internal::FunctionNameValidity, v8::internal::FunctionKind, int, v8::internal::FunctionLiteral::FunctionType, v8::internal::LanguageMode, v8::internal::ZoneList<v8::internal::AstRawString const*>*) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x5996C7A: v8::internal::ParserBase<v8::internal::Parser>::ParseHoistableDeclaration(int, v8::base::Flags<v8::internal::ParseFunctionFlag, int>, v8::internal::ZoneList<v8::internal::AstRawString const*>*, bool) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x596EFE6: v8::internal::Parser::DoParseProgram(v8::internal::Isolate*, v8::internal::ParseInfo*) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x596E6D8: v8::internal::Parser::ParseProgram(v8::internal::Isolate*, v8::internal::ParseInfo*) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x599A4D9: v8::internal::parsing::ParseProgram(v8::internal::ParseInfo*, v8::internal::Isolate*) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x56EF5B5: v8::internal::(anonymous namespace)::CompileToplevel(v8::internal::ParseInfo*, v8::internal::Isolate*, v8::internal::IsCompiledScope*) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x56F0EA8: v8::internal::Compiler::GetSharedFunctionInfoForScript(v8::internal::Isolate*, v8::internal::Handle<v8::internal::String>, v8::internal::Compiler::ScriptDetails const&, v8::ScriptOriginOptions, v8::Extension*, v8::internal::ScriptData*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason, v8::internal::NativesFlag) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x56B5C64: v8::ScriptCompiler::CompileUnboundInternal(v8::Isolate*, v8::ScriptCompiler::Source*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason) (in /usr/local/mysys/system/libs/libmjse.so)
==31273==    by 0x56B6356: v8::ScriptCompiler::Compile(v8::Local<v8::Context>, v8::ScriptCompiler::Source*, v8::ScriptCompiler::CompileOptions, v8::ScriptCompiler::NoCacheReason) (in /usr/local/mysys/system/libs/libmjse.so)
==31273== 

==31273==    LEAK SUMMARY:<br>
==31273==    definitely lost: 144 bytes in 1 blocks<br>
==31273==    indirectly lost: 1,744 bytes in 11 blocks<br>
==31273==    possibly lost: 960 bytes in 3 blocks<br>
==31273==    still reachable: 1,474 bytes in 18 blocks<br>
==31273==    suppressed: 0 bytes in 0 blocks<br>

Not sure whats wrong here. Any help in this regard, highly appreciated.

EDIT

After modifying code as suggested by Radosław Cybulski, memory leaks are gone. Only still reachable memory 168 bytes present.

However still conditional jump for uninitialised values are present (with valgrind run). One thing I observe if I remove function arguments in javascript, these conditional jump for uninitialised values are not showing. Say I will have javascript as below.

char *jscript =  "function js_add_elements1() \
    { \
        return 10; \
    };\

js_add_elements1();";

With javascript as above, I do not see conditional memory jumps for uninitialised values. What could be the reason?


Solution

  • free(instance);
    

    releases memory, but prevents unique_ptr (platform member) destruction. Try:

    instance->~MJSEInstance();
    free(instance):
    

    Or even better use new and delete instead of malloc and free.