保护变形:分析 Kafka 项目
编程并不是一个机械性的工作,而是需要有思考,有创新的工作,语法是固定的,但解决问题的思路则是依靠人的思维,这就需要我们坚持学习和更新自己的知识。今天golang学习网就整理分享《保护变形:分析 Kafka 项目》,文章讲解的知识点主要包括,如果你对文章方面的知识点感兴趣,就不要错过golang学习网,在这可以对大家的知识积累有所帮助,助力开发能力的提升。
您有没有想过跨国公司的项目源代码中可能潜藏着哪些错误?不要错过在开源 apache kafka 项目中发现 pvs-studio 静态分析器检测到的有趣错误的机会。
介绍
apache kafka 是一个著名的开源项目,主要用 java 编写。 linkedin 于 2011 年将其开发为消息代理,即各种系统组件的数据管道。如今,它已成为同类产品中最受欢迎的解决方案之一。
准备好看看引擎盖下的内容了吗?
附注
只是想简单说明一下标题。它参考了弗朗茨·卡夫卡的《变形记》,其中主角变成了可怕的害虫。我们的静态分析器致力于防止您的项目变身为可怕的害虫转变为一个巨大的错误,所以对“变形记”说不。
哦不,虫子
所有的幽默都源于痛苦
这不是我的话;这句话出自理查德·普赖尔之口。但这有什么关系呢?我想告诉你的第一件事是一个愚蠢的错误。然而,在多次尝试理解程序无法正常运行的原因后,遇到如下示例的情况令人沮丧:
@override public keyvalueiterator, v> backwardfetch( k keyfrom, k keyto, instant timefrom, instant timeto) { .... if (keyfrom == null && keyfrom == null) { // <= kvsubmap = kvmap; } else if (keyfrom == null) { kvsubmap = kvmap.headmap(keyto, true); } else if (keyto == null) { kvsubmap = kvmap.tailmap(keyfrom, true); } else { // keyfrom != null and keyto != null kvsubmap = kvmap.submap(keyfrom, true, keyto, true); } .... }
如您所见,这是任何开发人员都无法避免的事情——一个微不足道的拼写错误。在第一个条件下,开发人员希望使用以下逻辑表达式:
keyfrom == null && keyto == null
分析器发出两个警告:
v6001 在“&&”运算符的左侧和右侧有相同的子表达式“keyfrom == null”。 readonlywindowstorestub.java 327、readonlywindowstorestub.java 327
v6007 表达式“keyfrom == null”始终为 false。 readonlywindowstorestub.java 329
我们可以明白为什么。对于每个开发人员来说,这种可笑的打字错误都是永恒的。虽然我们可以花很多时间寻找它们,但要回忆起它们潜伏的地方可不是小菜一碟。
在同一个类中,另一个方法中存在完全相同的错误。我认为称其为复制面食是公平的。
@override public keyvalueiterator, v> fetch( k keyfrom, k keyto, instant timefrom, instant timeto) { .... navigablemap kvmap = data.get(now); if (kvmap != null) { navigablemap kvsubmap; if (keyfrom == null && keyfrom == null) { // <= kvsubmap = kvmap; } else if (keyfrom == null) { kvsubmap = kvmap.headmap(keyto, true); } else if (keyto == null) { kvsubmap = kvmap.tailmap(keyfrom, true); } else { // keyfrom != null and keyto != null kvsubmap = kvmap.submap(keyfrom, true, keyto, true); } } .... }
以下是相同的警告:
v6007 表达式“keyfrom == null”始终为 false。 readonlywindowstorestub.java 273
v6001 在“&&”运算符的左侧和右侧有相同的子表达式“keyfrom == null”。 readonlywindowstorestub.java 271, readonlywindowstorestub.java 271
不用担心——我们不必一次查看数百行代码。 pvs-studio 非常擅长处理此类简单的事情。解决一些更具挑战性的事情怎么样?
可变同步
java 中 synchronized 关键字的用途是什么?在这里,我将只关注同步方法,而不是块。根据 oracle 文档,synchronized 关键字将方法声明为同步,以确保与实例的线程安全交互。如果一个线程调用该实例的同步方法,则尝试调用同一实例的同步方法的其他线程将被阻塞(即它们的执行将被挂起)。它们将被阻塞,直到第一个线程调用的方法处理其执行。当实例对多个线程可见时,需要执行此操作。此类实例的读/写操作只能通过同步方法执行。
开发人员违反了 sensor 类中的规则,如下面的简化代码片段所示。对实例字段的读/写操作可以通过同步和非同步两种方式执行。它可能会导致竞争条件并使输出不可预测。
private final mapmetrics; public void checkquotas(long timems) { // <= for (kafkametric metric : this.metrics.values()) { metricconfig config = metric.config(); if (config != null) { .... } } .... } public synchronized boolean add(compoundstat stat, // <= metricconfig config) { .... if (!metrics.containskey(metric.metricname())) { metrics.put(metric.metricname(), metric); } .... } public synchronized boolean add(metricname metricname, // <= measurablestat stat, metricconfig config) { if (hasexpired()) { return false; } else if (metrics.containskey(metricname)) { return true; } else { .... metrics.put(metric.metricname(), metric); return true; } }
分析器警告如下所示:
v6102 “metrics”字段同步不一致。考虑在所有用途上同步该字段。传感器.java 49,传感器.java 254
如果不同的线程可以同时更改实例状态,则允许此操作的方法应该同步。如果程序没有预料到多个线程可以与实例交互,那么使其方法同步是没有意义的。最坏的情况下,甚至会损害程序性能。
程序中有很多这样的错误。这是分析器发出警告的类似代码片段:
private final prefixkeyformatter prefixkeyformatter; @override public synchronized void destroy() { // <= .... bytes keyprefix = prefixkeyformatter.getprefix(); .... } @override public void addtobatch(....) { // <= physicalstore.addtobatch( new keyvalue<>( prefixkeyformatter.addprefix(record.key), record.value ), batch ); } @override public synchronized void deleterange(....) { // <= physicalstore.deleterange( prefixkeyformatter.addprefix(keyfrom), prefixkeyformatter.addprefix(keyto) ); } @override public synchronized void put(....) { // <= physicalstore.put( prefixkeyformatter.addprefix(key), value ); }
分析器警告:
v6102 “prefixkeyformatter”字段同步不一致。考虑在所有用途上同步该字段。 logicalkeyvaluesegment.java 60、logicalkeyvaluesegment.java 247
迭代器、迭代器、迭代器……
在示例中,一行中同时出现两个相当令人不快的错误。我将在文章的一部分中解释它们的性质。这是一个代码片段:
private final maptopicids = new hashmap(); private map handledeletetopicsusingnames(....) { .... collection topicnames = new arraylist<>(topicnamecollection); for (final string topicname : topicnames) { kafkafutureimpl future = new kafkafutureimpl<>(); if (alltopics.remove(topicname) == null) { .... } else { topicnames.remove(topicids.remove(topicname)); // <= future.complete(null); } .... } }
这就是分析仪向我们展示的内容:
v6066 作为参数传递的对象类型与集合类型不兼容:string、uuid。 mockadminclient.java 569
v6053 'arraylist' 类型的 'topicnames' 集合在迭代过程中被修改。可能会发生concurrentmodificationexception。 mockadminclient.java 569
现在这是一个很大的困境!这是怎么回事,我们该如何解决?!
首先,我们来谈谈集合和泛型。使用集合的泛型类型可以帮助我们避免 classcastexceptions 以及转换类型时的繁琐构造。
如果我们在初始化集合时指定了某种数据类型并添加了不兼容的类型,编译器将不会编译代码。
这是一个例子:
public class test { public static void main(string[] args) { setset = new hashset<>(); set.add("str"); set.add(uuid.randomuuid()); // java.util.uuid cannot be converted to // java.lang.string } }
但是,如果我们从集合中删除不兼容的类型,则不会抛出异常。该方法返回false。
这是一个例子:
public class test { public static void main(string[] args) { setset = new hashset<>(); set.add("abc"); set.add("def"); system.out.println(set.remove(new integer(13))); // false } }
这是浪费时间。如果我们在代码中遇到类似的情况,很可能这是一个错误。我建议你回到本章开头的代码并尝试找出类似的情况。
其次,我们来谈谈迭代器。关于集合的迭代我们可以讨论很长时间。我不想让您感到厌烦或偏离主题,因此我将只介绍要点,以确保我们理解为什么会收到警告。
那么,我们如何迭代这里的集合呢?代码片段中的 for 循环如下所示:
for (type collectionelem : collection) { .... }
for 循环条目只是语法糖。其结构与此相同:
for (iteratoriter = collection.iterator(); iter.hasnext();) { type collectionelem = iter.next(); .... }
我们基本上使用集合迭代器。好了,就这样安排好了!现在,我们来讨论concurrentmodificationexception。
concurrentmodificationexception 是一个涵盖单线程和多线程程序中的一系列情况的异常。在这里,我们重点关注单线程。我们很容易找到解释。让我们看一下 oracle 文档:当方法检测到不支持它的对象的并行修改时,它可以抛出异常。在我们的例子中,当迭代器运行时,我们从集合中删除对象。这可能会导致迭代器抛出 concurrentmodificationexception。
迭代器如何知道何时抛出异常?如果我们查看 arraylist 集合,我们会看到它的父级 abstactlist 具有 modcount 字段,用于存储对集合的修改次数:
protected transient int modcount = 0;
以下是 arraylist 类中 modcount 计数器的一些用法:
public boolean add(e e) { modcount++; add(e, elementdata, size); return true; } private void fastremove(object[] es, int i) { modcount++; final int newsize; if ((newsize = size - 1) > i) system.arraycopy(es, i + 1, es, i, newsize - i); es[size = newsize] = null; }
因此,每次修改集合时,计数器都会递增。
顺便说一句,fastremove 方法用于 remove 方法,我们在循环中使用该方法。
这是 arraylist 迭代器内部工作的小代码片段:
private class itr implements iterator{ .... int expectedmodcount = modcount; final void checkforcomodification() { if (modcount != expectedmodcount) // <= throw new concurrentmodificationexception(); } public e next() { checkforcomodification(); .... } public void remove() { .... checkforcomodification(); try { arraylist.this.remove(lastret); .... expectedmodcount = modcount; } catch (indexoutofboundsexception ex) { throw new concurrentmodificationexception(); } } .... public void add(e e) { checkforcomodification(); try { .... arraylist.this.add(i, e); .... expectedmodcount = modcount; } catch (indexoutofboundsexception ex) { throw new concurrentmodificationexception(); } } }
让我解释一下最后一个片段。如果集合修改与预期的修改数量(即创建迭代器之前的初始修改与迭代器操作的数量之和)不匹配,则会抛出 concurrentmodificationexception 。只有当我们在迭代集合的同时使用其方法修改集合时(即 与迭代器并行 ),这才有可能。这就是第二个警告的内容。
所以,我已经向您解释了分析器消息。现在让我们把它们放在一起:
当 iterator 仍在运行时,我们尝试从集合中删除一个元素:
topicnames.remove(topicids.remove(topicname)); // topicsnames – collection// topicsids – map
但是,由于不兼容的元素被传递到arraylist进行删除(remove方法从topicids返回一个uuid对象),因此修改计数不会增加,但是对象不会被删除。简而言之,该代码部分是初级的。
我斗胆猜测开发者的意图是明确的。如果是这种情况,解决这两个警告的一种方法可能如下:
collectiontopicnames = new arraylist<>(topicnamecollection); list removableitems = new arraylist<>(); for (final string topicname : topicnames) { kafkafutureimpl future = new kafkafutureimpl<>(); if (alltopics.remove(topicname) == null) { .... } else { topicids.remove(topicname); removableitems.add(topicname); future.complete(null); } .... } topicnames.removeall(removableitems);
空虚,甜蜜的空虚
如果没有我们一直以来最喜欢的null及其潜在问题,我们会去哪里,对吧?让我向您展示分析器发出以下警告的代码片段:
v6008 函数“removestaticmember”中可能存在对“oldmember”的空取消引用。 consumergroup.java 311、consumergroup.java 323
@override public void removemember(string memberid) { consumergroupmember oldmember = members.remove(memberid); .... removestaticmember(oldmember); .... } private void removestaticmember(consumergroupmember oldmember) { if (oldmember.instanceid() != null) { staticmembers.remove(oldmember.instanceid()); } }
如果 members 不包含带有 memberid 键的对象,oldmember 将为 null。它可能会导致 removestaticmember 方法中出现 nullpointerexception。
繁荣!检查参数是否为 null:
if (oldmember != null && oldmember.instanceid() != null) {
下一个错误将是本文中的最后一个错误 - 我想以积极的态度来总结。下面的代码以及本文开头的代码有一个常见且愚蠢的拼写错误。然而,它肯定会导致不愉快的后果。
让我们看一下这个代码片段:
protected SchemaAndValue roundTrip(...., SchemaAndValue input) { String serialized = Values.convertToString(input.schema(), input.value()); if (input != null && input.value() != null) { .... } .... }
是的,没错。该方法实际上首先访问 input 对象,然后检查它是否引用 null.
v6060 “输入”引用在验证为空之前已被使用。 valuestest.java 1212、valuestest.java 1213
再次,我会指出这样的拼写错误是可以的。然而,它们可能会导致一些非常糟糕的结果。手动在代码中搜索这些内容既困难又低效。
结论
总之,我想回到上一点。手动搜索代码以查找所有这些错误是一项非常耗时且乏味的任务。对于像我所展示的那些长期潜伏在代码中的问题来说,这并不罕见。最后一个错误可以追溯到 2018 年。这就是为什么使用静态分析工具是个好主意。如果您想了解有关 pvs-studio(我们用来检测所有这些错误的工具)的更多信息,您可以在此处了解更多信息。
仅此而已。让我们把事情总结到这里吧。 “哦,如果我看不到你,下午好,晚上好,晚安。”
我差点忘了!抓住链接以了解有关开源项目免费许可证的更多信息。
今天关于《保护变形:分析 Kafka 项目》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!

- 上一篇
- 软件培训如何帮助缩小 IT 技能差距

- 下一篇
- 唯捷创芯拟出资1.5亿元设立投资基金 投资半导体/新材料等领域
-
- 文章 · java教程 | 2小时前 |
- 学Java必备知识点全解析,Java体系详解
- 133浏览 收藏
-
- 文章 · java教程 | 21小时前 |
- 反序输出字符串:填码验证算法小练习
- 278浏览 收藏
-
- 文章 · java教程 | 1天前 |
- Java非C语言开发,揭秘Java技术实现
- 236浏览 收藏
-
- 文章 · java教程 | 1天前 |
- Java学习必备知识体系结构详解
- 237浏览 收藏
-
- 文章 · java教程 | 1天前 |
- 若依框架MyBatis依赖配置及查找方法
- 194浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 谱乐AI
- 谱乐AI是由青岛艾夫斯科技有限公司开发的AI音乐生成工具,采用Suno和Udio模型,支持多种音乐风格的创作。访问https://yourmusic.fun/,体验智能作曲与编曲,个性化定制音乐,提升创作效率。
- 3次使用
-
- Vozo AI
- 探索Vozo AI,一款功能强大的在线AI视频换脸工具,支持跨性别、年龄和肤色换脸,适用于广告本地化、电影制作和创意内容创作,提升您的视频制作效率和效果。
- 3次使用
-
- AIGAZOU-AI图像生成
- AIGAZOU是一款先进的免费AI图像生成工具,无需登录即可使用,支持中文提示词,生成高清图像。适用于设计、内容创作、商业和艺术领域,提供自动提示词、专家模式等多种功能。
- 3次使用
-
- Raphael AI
- 探索Raphael AI,一款由Flux.1 Dev支持的免费AI图像生成器,无需登录即可无限生成高质量图像。支持多种风格,快速生成,保护隐私,适用于艺术创作、商业设计等多种场景。
- 3次使用
-
- Canva可画AI生图
- Canva可画AI生图利用先进AI技术,根据用户输入的文字描述生成高质量图片和插画。适用于设计师、创业者、自由职业者和市场营销人员,提供便捷、高效、多样化的视觉素材生成服务,满足不同需求。
- 3次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览