Checking Blender

PVS-Studio Command Center: “How quickly time flies… But this year, on January 2, Blender turned 30 years old! It’s as if just yesterday we published an article analyzing errors… Like 8 years ago? We urgently need to correct the situation !”.

A few words about the project

Blender is undoubtedly a cult project, and has earned universal interest and recognition over the years. This happened due to the fact that since 2000, existing as an Open Source solution, it allows any artist, at least a little familiar with the tool, to realize their wildest fantasies in XYZ-axis reality.

It also has a very nice and user-friendly interface that makes you want to work with it. There are renderers such as Eevee and Cycles that can make something beautiful. And if you want even more and more variety, you can google and install a lot of things. In general, there are absolutely many different addons for Blender that can completely cover all your needs.

However! There is also a fly in the ointment. All those, including myself, who use Blender to take courses for 200 thousand rubles, are certainly aware that the tool can be capricious, especially if you don’t know what you’re doing.

Yes, a long time ago Blender crashed for me a couple of times, fortunately there is autosave. It was then that I realized: to avoid surprises, I had to learn how to use this tool properly. Fortunately, YouTube contains a bunch of free courses and individual videos on how to make this or that, uh… thing. As already mentioned, the project is a cult one.

So, what am I talking about? Bugs! It's not like everything falls apart all the time. No! Everything works stably, and you can work 12-14 hours a day, and everything is fine! But from time to time your animation, distorted in all directions, seems to hint that something is wrong. I think anyone who has ever used or is using Blender will agree with me. Especially if you haven’t kept track of the Transform parameters, in particular Scale – this is the base, you need to know this!

The commit where I cloned the project: 76092ac.

Disclaimer

The writing and publication of this article is not intended to devalue the work of programmers involved in the development of the product in question. I really like him! The goal is to popularize the static analyzer, find errors and transmit the report to developers to improve the quality of the project code.

Test results

Before analyzing the code and warnings, I would like to note that when analyzing this or that case of triggering a diagnostic rule in this project, it was often not always clear to me whether it was a bug or a feature. Often the code is very confusing, difficult to understand and takes time. However, a list of errors has been compiled, and the alarms have been analyzed. It’s safe to say that you won’t mind wasting your time, because it will be interesting.

So further, in my usual manner, I present to you the most interesting sections of the Blender code, in which, according to the PVS-Studio static analyzer, there are suspicious places.

Buckle up and let the showdown begin!

Transparent bugs

Inspired by the idea of ​​transparent code, I sorted out some problematic areas of the Blender code and discovered the concept of transparent bugs: “The idea behind transparent bugs is simple. This kind of system is transparent when you can look at its code and understand where the bugs are, what they do and how“.

Fragment N1

void Cryptomatte::begin_sync()
{
  const eViewLayerEEVEEPassType enabled_passes = 
                                               
    static_cast<eViewLayerEEVEEPassType>(
      inst.film.enabled_passes_get() &
      (  EEVEE_RENDER_PASS_CRYPTOMATTE_OBJECT | 
         EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET  |
         EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET ) );
  ....
  
}

Analyzer warning: V501 There are identical sub-expressions 'EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET' to the left and to the right of the '|' operator. eevee_cryptomatte.cc 18

Obvious bug: the same constant is used twice EEVEE_RENDER_PASS_CRYPTOMATTE_ASSET when receiving a bitmask. Most likely another constant should be used – EEVEE_RENDER_PASS_CRYPTOMATTE_MATERIAL = (1 << 18).

Fragment N2

void MeshFromGeometry::create_edges(Mesh *mesh)
{
  ....
  for (int i = 0; i < tot_edges; ++i) 
  {
    ....
    dst_edge[0] = mesh_geometry_.global_to_local_vertices_
                                .lookup_default(src_edge[0], 0);
    dst_edge[1] = mesh_geometry_.global_to_local_vertices_
                                .lookup_default(src_edge[1], 0);
    BLI_assert(   dst_edge[0] < total_verts
               && dst_edge[0] < total_verts); 
  }
  ....
}

Analyzer warning: V501 There are identical sub-expressions 'dst_edge[0] < total_verts' to the left and to the right of the '&&' operator. obj_import_mesh.cc 266

Left and right subexpressions of logical “AND” BLI_assert absolutely the same. Most likely, the second subexpression should have checked dst_edge[1] instead of dst_edge[0].

Fragment N3

static void get_nearest_fcurve_verts_list (bAnimContext *ac, 
                                           const int mval[2],
                                           ListBase *matches)
{
  ....
  filter = (ANIMFILTER_DATA_VISIBLE  |
            ANIMFILTER_CURVE_VISIBLE |         
            ANIMFILTER_FCURVESONLY   |     
            ANIMFILTER_NODUPLIS      | 
            ANIMFILTER_FCURVESONLY);       
  ....
}

Analyzer warning: V501 There are identical sub-expressions 'ANIMFILTER_FCURVESONLY' to the left and to the right of the '|' operator. graph_select.cc 178

Formatting code with a table is like a form of art. In this example everything is obvious!

The concept of bug transparency works.

There is no need to explain anything.

This is wonderful!

By the way, there are not many places like this in the project code. I can only point out that formatting the code often solves the problem, and transparent bugs are fixed immediately.

Problems with dereferencing

“An interesting example of dereferencing null pointers when a diagnostic rule is triggered V522 “is the fact that dereferencing is only a consequence of an error, and not the cause of its occurrence.”

Null pointer dereference is a fairly common problem. Especially due to logical errors in the program code conditions. How do these bugs work? How to find them? The analyzer and I have answers to these questions, as well as a couple of interesting examples for our analysis.

Fragment N4

static bool gpencil_stroke_eraser_is_occluded (tGPsdata *p, bGPDlayer *gpl,
                                               bGPDspoint *pt, const int x,
                                               const int y)
{
  Object *obact = (Object *)p->ownerPtr.data;
  Brush *brush = p->brush;
  Brush *eraser = p->eraser;
  BrushGpencilSettings *gp_settings = nullptr;

  if (brush->gpencil_tool == GPAINT_TOOL_ERASE) 
  {
    gp_settings = brush->gpencil_settings;
  }
  else 
  if ((eraser != nullptr) &                               // <=
           (eraser->gpencil_tool == GPAINT_TOOL_ERASE)) 
  {
    gp_settings = eraser->gpencil_settings;
  }

  if ((gp_settings != nullptr) && 
      (gp_settings->flag & GP_BRUSH_OCCLUDE_ERASER) ) {
    RegionView3D *rv3d = static_cast<RegionView3D *>(p->region->regiondata);

    ....
  return false;
}

Analyzer warning: V522 Dereferencing of the null pointer 'eraser' might take place. Check the bitwise operation. gpencil_paint.cc 1429

As we can see, there are no problems with pointer checking and dereferencing. Unless, of course, you pay attention to the symbol &, proudly separating two subexpressions in parentheses. Of course, a double ampersand must be used here && as in the condition below. However, the code is exactly what it is: the bitwise AND operator will also execute the right-hand subexpression, which will dereference a potential null pointer.

By the way, in the same file there is another similar trigger:

Fragment N5

void ui_draw_popover_back (ARegion *region, uiStyle * /*style*/, 
                           uiBlock *block,  rcti *rect          )
{
  ....
  if (block) 
  {
    float mval_origin[2] = {float(block->bounds_offset[0]), 
                            float(block->bounds_offset[1])};
    ui_window_to_block_fl (region, block, &mval_origin[0], &mval_origin[1]);
    ui_draw_popover_back_impl (wt->wcol_theme, rect, block->direction,
                               U.widget_unit / block->aspect, mval_origin);
  }
  else 
  {
    const float zoom = 1.0f / block->aspect;               // <=
    wt->state (wt, &STATE_INFO_NULL, UI_EMBOSS_UNDEFINED);
    wt->draw_block (&wt->wcol, rect, 0, 0, zoom);
  }
  ....
}

Analyzer warning: V522 Dereferencing of the null pointer 'block' might take place. interface_widgets.cc 5294

There is a certain problem in logic in this piece of code. If block == nullptrthen the branch will be executed elsein which a guaranteed null pointer dereference will occur block.

And there is more than one such trigger:

  • V522 Dereferencing of the null pointer 'em' might take place. transform.cc 2117

  • V522 Dereferencing of the null pointer 'mesh' might take place. MOD_cloth.cc 108

  • V522 Dereferencing of the null pointer 'data.mval_fl' might take place. editmesh_select.cc 801

Condition… the result never changes…

“Madness is the exact repetition of the same action. Over and over again, in the hope of change. This is madness”— Vaas, FarCry 3.

It’s quite symbolic: we have a bug, we’re trying to call a function, but it’s not called, the code is correct, what’s wrong? We've been running through the same code for half an hour now and don't understand what's going on. Miracles, and that’s all! And then, unexpectedly for ourselves, we find a problem in a place where we never thought to find it. Errors in condition logic. Because often the logic is overcomplicated, but it doesn’t even occur to us that we did something wrong.

Fragment N6

void BLI_threadpool_init(ListBase *threadbase,
                         void *(*do_thread)(void *),
                         int tot)
{
  ....
  if (threadbase != nullptr && tot > 0) 
  {
    ....
    if (tot > RE_MAX_THREAD) 
    {
      tot = RE_MAX_THREAD;
    }
    else if (tot < 1)    // <=
    {
      tot = 1;
    }
    ....
  }
  ....
}

Analyzer warning: V547 Expression 'tot < 1' is always false. threads.cc 131

Everything is simple here: if we look at the first condition, we will find the check *tot > 0. *At the same time tot has type int, and if it is greater than 0, then it cannot be less than 1. Accordingly, the condition tot < 1 always will be false. As a result, the code can be simplified by completely removing this check.

Fragment N7

enum
{
  ALIGN_WORLD = 0,
  ALIGN_VIEW,
  ALIGN_CURSOR,
};

bool ED_object_add_generic_get_opts(bool *r_is_view_aligned, ....)
{
  ....
    if (RNA_struct_property_is_set(op->ptr, "rotation")) 
    {
      ....
    }
    else 
    {
      int alignment = ALIGN_WORLD;
      PropertyRNA *prop = RNA_struct_find_property(op->ptr, "align");

      if (RNA_property_is_set(op->ptr, prop)) 
      {
        *r_is_view_aligned = alignment == ALIGN_VIEW; 
        alignment = RNA_property_enum_get(op->ptr, prop);
      }
    }
  ....
}

I'll leave it to you to figure out what the problem is here.

Analyzer warning: V547 Expression 'alignment == ALIGN_VIEW' is always false. object_add.cc 544

So, what am I talking about? The reason may have been a typo, and the killer was the butler. Variable r_is_view_aligned has type bool *and the test is indeed always equal false. Moreover, most likely, the expression to the right of the comparison operator == should be different, or is it intended to be that way? An absolutely incomprehensible situation, but the main thing is that the problem was found, and then it’s not far from fixing it. It's a small thing, but it's nice!

Additional triggers:

  • V547 Expression 'changed == false' is always true. editmesh_tools.cc 1456

  • V547 Expression 'ob->type == OB_GPENCIL_LEGACY' is always true. gpencil_edit.cc 130

  • V547 Expression 'closure_count > i' is always true. eevee_raytrace.cc 371

  • V547 Expression 'retval == OPERATOR_FINISHED' is always false. wm_gizmo_group.cc 490

I want to play a game with you… Be careful

Fragment N8

void BKE_gpencil_stroke_copy_settings(const bGPDstroke *gps_src,
                                      bGPDstroke *gps_dst)
{
  gps_dst->thickness = gps_src->thickness;
  gps_dst->flag = gps_src->flag;
  gps_dst->inittime = gps_src->inittime;
  gps_dst->mat_nr = gps_src->mat_nr;
  copy_v2_v2_short(gps_dst->caps, gps_src->caps);
  gps_dst->hardness = gps_src->hardness;
  copy_v2_v2(gps_dst->aspect_ratio, gps_src->aspect_ratio);
  gps_dst->fill_opacity_fac = gps_dst->fill_opacity_fac; // <=
  copy_v3_v3(gps_dst->boundbox_min, gps_src->boundbox_min);
  copy_v3_v3(gps_dst->boundbox_max, gps_src->boundbox_max);
  gps_dst->uv_rotation = gps_src->uv_rotation;
  copy_v2_v2(gps_dst->uv_translation, gps_src->uv_translation);
  gps_dst->uv_scale = gps_src->uv_scale;
  gps_dst->select_index = gps_src->select_index;
  copy_v4_v4(gps_dst->vert_color_fill, gps_src->vert_color_fill);
}

Analyzer warning: V570 The 'gps_dst->fill_opacity_fac' variable is assigned to itself. gpencil_legacy.cc 1029

Variable gps_dst->fill_opacity_fac assigned to itself. Maybe it was intended that way? No. Most likely, there is a typo, and there should be a different expression on the right side of the assignment. Something like gps_src->fill_opacity_fac. Copy/Paste problem?

Fragment N9

static int gizmo_cage2d_modal(....)
{
  ....
  if ((transform_flag & ED_GIZMO_CAGE_XFORM_FLAG_SCALE_UNIFORM) == 0) 
  {
    const bool use_temp_uniform = (event->modifier & KM_SHIFT) != 0;
    const bool changed = data->use_temp_uniform != use_temp_uniform;
    data->use_temp_uniform = data->use_temp_uniform;
    ....
  }
  ....
}

Analyzer warning: V570 The 'data->use_temp_uniform' variable is assigned to itself. cage2d_gizmo.cc 1094

And again the problem is Copy/Paste. Most likely, in the end everything should look like this:

data->use_temp_uniform = use_temp_uniform;

Fragment N10

static void space_text_update_drawcache(SpaceText *st,
                                        const ARegion *region)
{
  ....
  if (st->wordwrap) 
  {
    ....
    if (drawcache->update) 
    {
      drawcache->valid_tail = drawcache->valid_head = 0;
      ....
      memmove(new_tail, old_tail, drawcache->valid_tail);
      ....
    }
    ....
  }
  ....
}

V575 The 'memmove' function processes '0' elements. Inspect the third argument. text_draw.cc 673

It is strange that we are trying to copy 0 bytes of information from one memory area to another memory area.

Fragment N11

void ui_but_value_set(uiBut *but, double value)
{
  ....
    if (but->editval) {
      value = *but->editval = value;
    }
    else
    ....
  ui_but_update_select_flag(but, &value);
}

Analyzer warning: V570 The same value is assigned twice to the 'value' variable. interface.cc 2676

Do you think this was intended, or is this also a typo? I'll leave the question open, you know, like in bad horror films.

More vague behavior for the god of vague behavior!

“Cthulhu fhtagn, my dears! And really, who else, no matter how sleeping, could be suitable for the role of a god of uncertain behavior? However, be careful in your actions, you don’t want him to wake up before the appointed hour? There’s already so much chaos in the code! “

Fragment N12

static void rigidbody_update_ob_array(RigidBodyWorld *rbw)
{
  if (rbw->group == nullptr) 
  {
    rbw->numbodies = 0;
    rbw->objects = static_cast<Object **>(realloc(rbw->objects, 0)); // <=
    return;
  }
  ....
}

V575 The 'realloc' function processes '0' elements. Inspect the second argument. rigidbody.cc 1696

Starting with the C23 standard, if the memory size for a reallocated memory location is zero, then the behavior is considered undefined. By the way, with C17 this pattern is perceived as outdated.

Fragment N13

void BKE_vfont_build_char(....)
{
  ....
  BezTriple *bezt2 = (BezTriple *)MEM_malloc_arrayN(u, 
                                                    sizeof(BezTriple),
                                                    "duplichar_bezt2" );
  ....
  for (int i = nu2->pntsu; i > 0; i--) 
  {
    float *fp = bezt2->vec[0];    
    fp[0] = (fp[0] + ofsx) * fsize;
    fp[1] = (fp[1] + ofsy) * fsize;
    fp[3] = (fp[3] + ofsx) * fsize;
    fp[4] = (fp[4] + ofsy) * fsize;
    fp[6] = (fp[6] + ofsx) * fsize;
    fp[7] = (fp[7] + ofsy) * fsize;
    bezt2++;
  } 
  ....
}

Analyzer warning: V557 Array overrun is possible. The '7' index is pointing beyond array bound. vfont.cc 612

I also present to your attention the array itself:

typedef struct BezTriple 
{
  float vec[3][3];
  ....
}

In this example, the diagnostic rule generated quite a lot of hits for each assignment, starting with fp[3]. With a two-dimensional array bezt2->vec[3][3] operations are performed as if with a one-dimensional one. Until now, some programmers believe that they are doing everything correctly, and there are no problems with accessing elements of a two-dimensional array sequentially placed in memory.

However, the latest and most powerful compiler technologies for C and C++ languages, called optimizations, give the highest award in the category “Programmer of the Year in the field of ignorance of undefined behavior” to believers in the truth about the correctness of using two-dimensional arrays as one-dimensional ones.

Once my colleague Anton Tretyakov published an article “Kwa! How code was written in the days of Quake“, in which there was a similar example that explained why it is wrong to use multidimensional arrays as one-dimensional ones. I advise everyone to read it. Also, my colleague Philip Handelyantz – he is also one of my mentors and a guy who knows almost everything – reproduced the problem undefined behavior. I also recommend checking it out.

In general, everything has been proven, everything has been tested. Undefined behavior occurs due to various optimizations. I advise everyone not to do this. Go ahead.

Using uninitialized data

“The main problem with searching for uninitialized variables is often that there is a lot of code, the train of thought regarding where the variable is used and its data is lost against the background of parallel calculations and changes in the code, and if this data is also constantly transferred from function to function, without changing their internal state, and then somewhere out there, maybe something happens to them, and the variable is still initialized… or not.”

In general, if you get lost a few times while reading the top paragraph, that's normal. In fact, this is the same as looking for places in the code where uninitialized variables are used. Especially if there is a lot of code. Not exactly a pleasant experience. Therefore, the convenience of the analyzer has been proven in practice. It’s quite a common case: there’s a lot of code, it takes half a day to figure it out, but you need to figure out where the bug is yesterday.

Fragment N14

static void gpencil_convert_spline(....)
{
  ....
  float init_co[3];

  switch (nu->type) {
    case CU_POLY: 
    {
      ....
    }
    case CU_BEZIER: 
    {
      ....
    }
    case CU_NURBS: 
    {
      if (nu->pntsv == 1) 
      {
        ....
        gpencil_add_new_points (gps, coord_array, 1.0f, 1.0f, 0, 
                                gps->totpoints, init_co, false); // <=
        ....
    }
    default: 
    {
      break;
    }
}

Analyzer warning: V614 Uninitialized buffer 'init_co' used. Consider checking the seventh actual argument of the 'gpencil_add_new_points' function. gpencil_curve_legacy.cc 439

So, if it’s quick: found init_coshe's the same float init_co[3].

If you check the entire function in which this array variable is declared, up to the point where it is passed to gpencil_add_new_points(), we will not see any initialization of it with any value. Random data. Okay, maybe it's initialized inside this function? Let's look:

static void gpencil_add_new_points(....,
                                   const float init_co[3],
                                   const bool last)
{
  BLI_assert(totpoints > 0);
  ....
  for (int i = 0; i < totpoints; i++) 
  {
    ....
    if ((last) && (i > 0) && (i == totpoints - 1)) 
    {
      float dist = len_v3v3(init_co, &pt->x);
      ....
    }
    ....
  }
}

Our mini-array is now passed to the function* len_v3v3*, and it has not been filled with any values ​​before. Random again.

We jump inside *len_v3v3 *and look for init_cowhich is now a[3]:

static __forceinline float len_v3v3(const float a[3], const float b[3])
{
  float d[3];

  sub_v3_v3v3(d, b, a);
  return len_v3(d);
}

And even deeper in *sub_v3_v3v3 *for a[3]which is already b[3]:

Static __forceinline void sub_v3_v3v3(float r[3], const float a[3], 
                                      const float b[3] )
{
  r[0] = a[0] - b[0];
  r[1] = a[1] - b[1];
  r[2] = a[2] - b[2];
}

The search for initialization data was not successful, and an error occurs. The main thing is that we discovered an uninitialized variable, and now there is a chance that the problem will be fixed.

Additional triggers:

  • V614 Uninitialized variable 'efd.distance' used. boids.cc 133

  • V614 Potentially uninitialized pointer 'g_prev' used. Consider checking the third actual argument of the 'blf_font_width_to_strlen_glyph_process' function. blf_font.cc 784

  • V614 Uninitialized variable 'dummy_matrix[0][0]' used. Consider checking the first actual argument of the 'GPU_uniform' function. node_shader_tex_coord.cc 43

Stranger Things

Fragment N15

typedef struct Point2Struct
{
  double coordinates[2];

  Point2Struct() { .... }
  ....
} Point2;

typedef Point2 Vector2;

using BezierCurve = Vector2 *;

static BezierCurve GenerateBezier(Vector2 *d,
                                  int first, int last,
                                  double *uPrime,
                                  Vector2 tHat1, Vector2 tHat2)
{
  ....
  BezierCurve bezCurve; /* RETURN bezier curve control points. */
  ....
  bezCurve = (Vector2 *)malloc(4 * sizeof(Vector2)); // <=
  ....
  if (alpha_l < 1.0e-6 || alpha_r < 1.0e-6)
  {
    ....
    bezCurve[0] = d[first]; // <=
    bezCurve[3] = d[last];  // <=
    ....
  }
  ....
}

Analyzer warning: V630 The 'malloc' function is used to allocate memory for an array of objects which are classes containing constructors. FitCurve.cpp 129

Function malloc used to create an array of four objects of type Point2 (Vector2 have a pseudonym Point2). However, class Point2 contains a non-trivial default constructor that nullifies the array coordinates. Consequently, the code contains undefined behavior: at the time of assignment, the lifetime of these objects has not yet started.

Currently, compilers do not take advantage of this opportunity for clever optimizations. However, everything may change in the future :).

How can this be fixed? The most correct option would be to use the operator new[]. However, the code consists of a mixture of C and C++ languages, and its logic will happily break if we start using the operator new[].

In addition, use the operators new And delete It has long been out of fashion and even dangerous due to such a human trait as inattention. The obvious option is smart pointers. And it seems that the problem has been solved, and we can end this. But using smart pointers will also result in the code having to be rewritten.

What other options? There is a way to fix everything without rewriting anything:

static BezierCurve GenerateBezier(Vector2 *d,
                                  int first, int last,
                                  double *uPrime,
                                  Vector2 tHat1, Vector2 tHat2)
{
  ....
  BezierCurve bezCurve; /* RETURN bezier curve control points. */
  ....
  bezCurve = (Vector2 *)malloc(4 * sizeof(Vector2));
  std::uninitialized_default_construct_n(bezCurve, 4); // <=
  ....
  if (alpha_l < 1.0e-6 || alpha_r < 1.0e-6)
  {
    ....
    bezCurve[0] = d[first]; 
    bezCurve[3] = d[last]; 
    ....
  }
  ....
}

The problem of undefined behavior regarding the lifetime of objects has been resolved. You can read more about this functionality Here.

Regarding C++20. The attentive reader may know about the creeping P0593R6thanks to which the behavior of the program, starting with C++20, will be strictly defined.

Lost in the namespace

Fragment N16

But first, since the case is not simple and very confusing, here is a warning from the analyzer:

V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'preferred_domain' in derived class 'HandlePositionFieldInput' and base class 'CurvesFieldInput'. node_geo_input_curve_handles.cc 95

So, from the warning text it is clear that our problem is incorrect overriding of a virtual function in arbitrary classes. What's wrong? Let's figure it out.

Let's start with the base class:

typedef struct CurvesGeometry { .... };

namespace bke
{
  ....
  class CurvesGeometry : public ::CurvesGeometry { .... };

  class CurvesFieldInput : public fn::FieldInput 
  {
    ....
    virtual std::optional<AttrDomain> preferred_domain(
      const CurvesGeometry &curves) const;
  };
  ....
}

Virtual function preferred_domain accepts a type parameter bke::CurvesGeometry. Let's remember.

Now let's look at the heir:

namespace blender::nodes::node_geo_input_curve_handles_cc
{
  class HandlePositionFieldInput final : public bke::CurvesFieldInput 
  {
    ....
    std::optional<AttrDomain> preferred_domain(
      const CurvesGeometry & /*curves*/) const;
  };
}

Found a problem? If not, then don’t be upset, I didn’t understand at first either :). We'll figure out.

In the base class, the virtual function accepts a parameter with an unqualified name CurvesGeometry. When the compiler looks for this type, it will start with the scope of the class CurvesFieldInput and will look into all surrounding areas of visibility until it encounters this type. As a result, the type will be found bke::CurvesGeometry.

Now let's look at derived classes. They are defined in a namespace different from the one in which the base class resides. The compiler will also start searching for the required name CurvesGeometry, will not meet it in the framing areas of visibility and will reach the global one. And in the global namespace there is also CurvesGeometryjust not the one we need to override the function :).

To fix it, you just need to specify a qualified type name. Well, let's take advantage of the capabilities of C++11 (override), protecting future generations from mistakes:

namespace blender::nodes::node_geo_input_curve_handles_cc
{
  class HandlePositionFieldInput final : public bke::CurvesFieldInput 
  {
    ....
    std::optional<AttrDomain> preferred_domain(
      const bke::CurvesGeometry & /*curves*/) const override;
  };
}

There are also two other classes nearby that contain the same error:

  • V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'preferred_domain' in derived class 'IndexOnSplineFieldInput' and base class 'CurvesFieldInput'. node_geo_curve_spline_parameter.cc 277

  • V762 It is possible a virtual function was overridden incorrectly. See first argument of function 'preferred_domain' in derived class 'EndpointFieldInput' and base class 'CurvesFieldInput'. node_geo_curve_endpoint_selection.cc 105

Conclusion

The feeling from checking the project was twofold. On the one hand, there were many additional protective measures: often smart, cunning and generally beautiful. On the other hand, even the PVS-Studio analyzer from time to time cannot understand the cause-and-effect relationships of this labyrinth of code, where the floor is lava.

I hope that among the analysis of code fragments, warnings and errors, you managed to gain an interesting or even useful experience for yourself.

Traditionally I offer try our analyzer PVS-Studio. For Open Source projects we have provided* free license.*

Take care of yourself and all the best!

Bonus for those who read to the end =)

See pictures:

“We expel the god of uncertain behavior (Cthulhu)”

If you want to share this article with an English-speaking audience, please use the translation link: Alexey Gorshkov. Let's check Blender.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *