Els mods de Minecraft, l'idioma català i les interaccions no obvies amb l'operador ternari - quantes coses ha trobat el nostre analitzador! Introducció Aquest any, hem revisat molts projectes de codi obert i hem escrit Sobre ells Articles Ara, hem seleccionat deu dels errors més curiosos i fragments de codi bizarres. La selecció i el rànquing són, per descomptat, subjectius i basats en el nostre propi sentit de la frescor. Ha arribat l’hora... Anem! 10è lloc: Dret a esquerra, esquerra a dreta Comencem amb un cas de En certa manera, el projecte ressona amb nosaltres: igual que PVS-Studio, analitza llengües –només naturals, no artificials. Llenguatge La cosa divertida és que aquest error es refereix al nostre analitzador. No obstant això, el trobem tan divertit que simplement no el podem saltar. Mireu el fragment del codi: public String getEnclitic(AnalyzedToken token) { .... if (word.endsWith("ه")) { suffix = "ه"; .... else if ((word.equals("عني") || word.equals("مني")) && word.endsWith("ني") // <= ) { suffix = "ني"; } .... } Alerta de l'estudi del PSC: L'expressió 'word.endsWith("ni")' és sempre falsa. El 6007 Targeta àrab.java 428 Si mirem les constants de cordes de la manera que els parlants nadius de llengües de l'esquerra a la dreta (LTR), com l'anglès o el francès, solen fer, l'advertència de l'analista sembla justa. - Els parlants àrabs esperen el final d'un literal, en realitat veuran el seu començament. Parla en àrab. non can't Per cert, si sabem que hi ha una escriptura RTL, ens adonem que la condició és en realitat sempre No és fals. true Això ens va fer curiositat: com gestiona Java els idiomes de dreta a esquerra? tot és fàcil com a pastís: Java gestiona Unicode en comptes de la visualització de text. , és a dir, en l'ordre en què els parlants nadius els llegeixen i els escriuen. are stored in logical order La cadena segueix sent una seqüència regular de punts de codi Unicode, i operacions com obtenir la seva longitud, accedir a caràcters, o extreure una substring treballen el mateix per a tots els idiomes. Per tant, per a una tal comprovació: System.out.println("مرحباً بالجميع".endsWith("بالجميع")); El resultat serà , encara que si llegim de l'esquerra a la dreta, la cadena comença, en comptes de acabar, amb "بالجميع". true No obstant això, ja que l'analitzador està tècnicament equivocat aquí, el col·loquem en el 10è lloc en el nostre rànquing. Si estàs curiós per saber-ne més, pots llegir la història completa de com vam analitzar LanguageTool en aquest enllaç. . Article de 9è lloc: Llengua catalana Torna a aparèixer al nostre rànquing! Llenguatge El mòdul de llengua catalana té la mètode, que corregeix acuradament les formes d'ortografia obsoletes i elimina les diacrítiques innecessàries. Per exemple, converteix "adéu" a "adéu", "dóna" a "dona", "vénen" a "venen" i així successivament: removeOldDiacritics() private String removeOldDiacritics(String s) { return s .replace("contrapèl", "contrapel") .replace("Contrapèl", "Contrapel") .replace("vés", "ves") .replace("féu", "feu") .replace("desféu", "desfeu") .replace("adéu", "adeu") .replace("dóna", "dona") .replace("dónes", "dones") .replace("sóc", "soc") .replace("vénen", "venen") .replace("véns", "véns") // <= .replace("fóra", "fora") .replace("Vés", "Ves") .replace("Féu", "Feu") .replace("Desféu", "Desfeu") .replace("Adéu", "Adeu") .replace("Dóna", "Dona") .replace("Dónes", "Dones") .replace("Sóc", "Soc") .replace("Vénen", "Venen") .replace("Véns", "Vens") .replace("Fóra", "Fora"); } A primera vista, sembla bé: funciona, corregeix i no molesta a ningú. No obstant això, l’analista té un altre punt: La funció "reemplaçar" rep un argument estrany. L'argument "véns" s'ha aprovat diverses vegades. El 6009 Català.java 453 El problema és que el mètode reemplaça "véns" per "véns", és a dir, res no es reemplaça en absolut.El més probable és que quan els desenvolupadors van treballar amb aquest bloc, simplement van copiar la paraula original i van substituir la lletra, però en aquest cas, van oblidar canviar la lletra en el segon argument: .... .replace("véns", "vens") .... Atès que el nostre analitzador de llengües artificials va aconseguir detectar un error en el seu analitzador de llengües naturals, aquesta advertència guanya el seu lloc a la nostra llista principal. 8è lloc: últim efecte de línia Let's go further, now we talk about the error from . Elàstic Mireu el fragment del codi: @Override public boolean equals(Object obj) { .... KeyedFilter other = (KeyedFilter) obj; return Objects.equals(keys, other.keys) && Objects.equals(timestamp, other.timestamp) && Objects.equals(tiebreaker, other.tiebreaker) && Objects.equals(child(), other.child()) && isMissingEventFilter == isMissingEventFilter; // <= } L'avís PVS-Studio: V6001 Hi ha subexpressions idèntiques 'isMissingEventFilter' a l'esquerra i a la dreta de l'operador '=='. Títol original: Java 116 Com en l'exemple anterior, estem tractant d'un tipus, però amb una lleugera matèria: l'error es produeix en l'última línia de bloc de codi idèntic. Aquesta situació ocorre bastant sovint durant el desenvolupament, tan sovint que els desenvolupadors li han donat un nom, Fins i tot dins d'aquest projecte, aquest no és un cas únic: apareix un error similar en l'última línia d'un altre bloc en el codi idèntic: the last line effect @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; IndexError that = (IndexError) o; return indexName.equals(that.indexName) && Arrays.equals(shardIds, that.shardIds) && errorType == that.errorType && message.equals(that.message) && stallTimeSeconds == stallTimeSeconds; // <= } L'avís PVS-Studio: V6001 Hi ha subexpressions idèntiques 'stallTimeSeconds' a l'esquerra i a la dreta de l'operador '=='. ÍndexError.java 147 Si voleu saber més sobre aquest efecte, us convidem a llegir els següents articles: La nostra primera trobada amb ella: L’efecte de l’última línia. una discussió més detallada sobre el fenomen: L'efecte de l'última línia explicat. Com que aquest error demostra un fenomen tan curiós, li atorguem un lloc en la nostra llista principal. 7è lloc, perdut en la localització Passem a la setena posició. Ja hem mirat tres errors de Java, però encara no hem trobat Minecraft. Mireu el fragment de codi de : Coneixements + private String getTimePast() { .... if(selected.timePast > 3600000){ int hours = (int) (selected.timePast / 3600000); if(hours == 1) return hours + " " + StatCollector.translateToLocal("mailbox.hour"); else return hours + " " + StatCollector.translateToLocal("mailbox.hours"); } int minutes = (int) (selected.timePast / 60000); if(minutes == 1) return minutes + " " + StatCollector.translateToLocal("mailbox.minutes"); else return minutes + " " + StatCollector.translateToLocal("mailbox.minutes"); } Alerta de l'estudi del PSC: La declaració "llavors" és equivalent a la declaració "else". El 6004 GuiMailbox.java 67 Aquí podem veure un tipus que condueix a un error de localització. Depenent del nombre de minuts, s'hauria d'haver mostrat "minuts" o "minuts". and Les branques són idèntiques, que sempre produeixen "minuts". then else Com a autor d’aquest diu: “Des que el text de Ja està en les fonts del joc, l'enviament del compromís va trigar més temps que fixar el codi." Per corregir-ho, els desenvolupadors han d'esborrar una lletra. article mailbox.minute Per descomptat, l’error no és tan greu, però perquè es pot reproduir sense cap problema, guanya el setè lloc en el nostre top: El 6è lloc.Aquesta va ser l'última palla Passem a l’error de : Autònoms public static final int MAX_TYPE_NUMBER = 20; private static final LongAdder[] USAGE_STATS = new LongAdder[MAX_TYPE_NUMBER]; .... public static ByteBuf byteBuffer(int initCapacity, int type) { try { if (MEMORY_USAGE_DETECT) { .... if (type > MAX_TYPE_NUMBER) { counter = UNKNOWN_USAGE_STATS; } else { counter = USAGE_STATS[type]; // <= .... } .... } .... } } Alerta de l'estudi del PSC: Possiblement l'índex 'tipus' està fora de límits. El 6025 Càritas.java 151 Aquest és un error bastant comú (honestament, tan comú que l'autor d'aquesta llista superior ho ha fet molt sovint ell mateix). A causa d'un error menor en la condició, un pot ocórrer en la línia de codi indicada per l'analitzador. MAX_TYPE_NUMBER OutOfBoundsException Això pot succeir a causa de Es tracta d’una situació en la qual, en el La branca, el El paràmetre tindrà En aquest cas, es llançarà una excepció fora de límits, ja que la primera línia accedeix a l'array utilitzant el El seu índice. if (type > MAX_TYPE_NUMBER) else type MAX_TYPE_NUMBER type To fix it, we just need to use En lloc de . >= > Com que aquest error està a prop del cor de l'autor, guanya el sisè lloc en la llista superior. 5è lloc: Dades externes en HTML Ara és Si us plau, mireu el codi: Jetió public class HelloSessionServlet extends HttpServlet { .... @Override protected void doGet( HttpServletRequest request, HttpServletResponse response ) { .... String greeting = request.getParameter("greeting"); // <= if (greeting != null) { .... message = "New greeting '" + greeting + "' set in session."; // <= .... } .... PrintWriter out = response.getWriter(); out.println("<h1>" + greeting + " from HelloSessionServlet</h1>"); // <= out.println("<p>" + message + "</p>"); // <= .... } } Alerta de l'estudi del PSC: Possible injecció de XSS. Les dades potencialment contaminades de la variable 'missatge' podrien ser utilitzades per executar un script maliciós. P33030 Càtedra.java 70 Els analistes assenyalen que pot haver-hi una . Injecció XSS En el codi, obtenim una cadena de la sol·licitud, l'utilitzem per generar missatges, i després utilitzem aquests missatges per generar una pàgina HTML. En qualsevol cas, la injecció de XSS és realment possible. sanitària Si l’exterior passa És , the JavaScript S'executarà quan s'obri la pàgina, igual que qualsevol altre codi js. message <script>alert("XSS Injection")</script> alert("XSS Injection") Si aquesta és una veritable vulnerabilitat, per què no es classifica més alt en la llista? Perquè forma part d'un exemple de demostració. En altres paraules, mostrem el codi només per veure què pot passar en Jetty. No obstant això, val la pena recordar que l'exemple de demostració no es podia copiar en la producció. Després de tot, no és la millor pràctica mostrar codi no segur. Com el primer -Alerta d'anàlisi en un projecte real, encara que sigui en un exemple de demostració, li atorguem el 5è lloc en la part superior. Taïna Quarta posició, només doble Veure l'error de . Elàstic public static Number truncate(Number n, Number precision) { .... Double result = (((n.doubleValue() < 0) ? Math.ceil(g) : Math.floor(g)) / tenAtScale); return n instanceof Float ? result.floatValue() : result; // <= } Alerta de l'estudi del PSC: El resultat d'aquesta expressió serà implícitament llançat a 'doble'. comprovar si la lògica del programa el tracta correctament. El 6088 Matèries.java 122 Aquest mètode retalla el nombre passat d'acord amb la precisió especificada. Per als valors enters, redueix el nombre de dígits, mentre que per als valors fraccionals, elimina els dígits excedents després del punt decimal. A jutjar per l'operador ternari, en el cas de fraccions, els desenvolupadors volen que el mètode retorni un nombre idèntic amb el qual va arribar al mètode (ja sigui o Però un detall subtil però important ha estat ignorat. Float Double L'operador ternari és una expressió, el que significa que el seu resultat ha de tenir un tipus únic i predeterminat. En breu, s’aplica el següent: Context numèric If objects of different types appear in an expression, they're converted to a single common type. En aquest cas, el tipus comú seria Doble, ja que representa tant Doble com Float. És a dir, malgrat les expectatives, el mètode sempre retorna una Processament de nombres fraccionals. Double El format d'enregistrament compacte realment ens deixa enrere. Una correcció pot semblar això: public static Number truncate(Number n, Number precision) { .... if (n instanceof Float) { return result.floatValue(); } else { return result; } } A causa d'aquest matís lingüístic no obvi, l'error guanya el quart lloc en el top deu. 3r lloc, si us plau, espera el teu torn! Ara anem al podi del premi.La medalla de bronze ha estat trobada en Mireu el fragment del codi: Idees intel·ligents public final class UsageType { .... public static final UsageType DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED = new UsageType( UsageViewBundle.messagePointer( "usage.type.delegate.to.another.instance.method.parameters.changed" ) ); private static final Logger LOG = Logger.getInstance(UsageType.class); public UsageType(@NotNull Supplier<....> nameComputable) { myNameComputable = nameComputable; if (ApplicationManager.getApplication().isUnitTestMode()) { String usageTypeString = myNameComputable.get(); if (usageTypeString.indexOf('{') != -1) { LOG.error(....); } } } .... } Alerta de l'estudi del PSC: La inicialització de 'DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED' apareix abans de la inicialització de 'LOG'. V6050 UsageType.java 46 L'analitzador adverteix sobre una dependència cíclica durant la inicialització de camp estàtic; un d'aquells matisos de Java que és fàcil de perdre. El constructor s'utilitza per inicialitzar el El camp i l’accés logger in this constructor. DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED LOG Camps . As we can see, Està situat a dalt , és a dir, s'inicia abans. Com a resultat, quan el constructor s'executa, , and when we try to access it, we'll encounter an NPE. són inicialitzats en l'ordre en què es declaren DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED LOG LOG Serà null Com en la majoria dels casos anteriors, la correcció serà extremadament senzilla. Declaració de dalt . LOG DELEGATE_TO_ANOTHER_INSTANCE_PARAMETERS_CHANGED 2n lloc: On va anar el bloc? Hem passat a la medalla de plata. Tornem a conèixer el mod de Minecraft, . Coneixements + Mireu el fragment del codi: public int range = 5; public int speed = 5; .... public boolean aiShouldExecute() { healTicks++; if (healTicks < speed * 10) return false; for (Object plObj : npc.worldObj.getEntitiesWithinAABB( EntityLivingBase.class, npc.boundingBox.expand( range, range / 2, // <= range)) ) { .... } healTicks = 0; return !toHeal.isEmpty(); } Alerta de l'estudi del PSC: L'expressió es va llançar implícitament del tipus 'int' al tipus 'doble'.Considereu utilitzar un tipus explícit per evitar la pèrdua d'una part fraccional. V6094 Càritas.java 41 En aquest fragment de codi, el joc periòdicament (basat en el camp) escanes per a entitats dins d'una àrea rectangular amb un radi definit per Es va centrar al voltant d'un metge NPC. speed range Since the El mètode té la Amb aquesta signatura, l’analista té tota la raó d’advertir sobre aquest fragment: S’ha introduït el mètode en què “Llavors què?”, potser us preguntareu, i pensem que això és realment estrany, de manera que hem decidit investigar aquest cas. expand expand(double x, double y, double z) int double Aquest defecte menor condueix a la següent conseqüència: si el radi és un nombre estrany, quan avaluem l'eix Y, perdem la meitat d'un bloc a la part superior i a la part inferior (és a dir, un bloc sencer en total). La col·lisió del metge NPC es destaca en blanc. The collision currently implemented (5 / 2) is highlighted in red. La col·lisió sense divisió de nombres sencers (5 / 2.0) es destaca en verd. Com es pot veure, si utilitzem el literal de la type, the division is no longer an integer, and as a result, the collision will cover exactly the radius we want. 2.0 double And to confirm that it worked correctly, the author of the Hem de “preguntar a l’univers”: Article de Notablement, els desenvolupadors han corregit aquest error en el mod original per a Minecraft 1.12.2. Malauradament, no puc proporcionar cap evidència directa, així que digui que vaig preguntar a l'univers -i aquest fragment de codi és el que va enviar de tornada: Notably, developers have fixed this error in the original mod for Minecraft 1.12.2. Unfortunately, I can't provide any direct evidence, so let's say I asked the universe—and this code snippet is what it sent back: .... this.npc.world.getEntitiesWithinAABB( EntityLivingBase.class, npc.getEntityBoundingBox().expand(range, range / 2.0, range) ); .... Since devs have fixed the bug in the original modification, the same should apply here. Since devs have fixed the bug in the original modification, the same should apply here. 1r lloc: El misteriós cas del bo perdut La medalla d’or va a... Podria l'abundància de bugs de Minecraft ser perquè la meitat del nostre equip va passar innombrables hores treballant en mods i plugins per a això? Coneixements + Let's move on to the code: .... int startIndex = -1; boolean number = false; try { startIndex = Integer.parseInt(bonusID); number = true; } catch (Exception var34) { number = false; } for (startIndex = 0; startIndex < bonuses.length; ++startIndex) { .... if (number && startIndex == startIndex || // <= !number && bonusValues[startIndex][0].equals(bonusID) ) { noNBTText = bonusValues[startIndex][0] + ";" + bonusValueString; bonuses[startIndex] = ""; bonuses[startIndex] = noNBTText; .... break; } } .... Alerta de l'estudi del PSC: V6001 Hi ha subexpressions idèntiques 'startIndex' a l'esquerra i a la dreta de l'operador '=='. ScriptDBCPlayer.java 289 V6007 L'expressió '!número' sempre és veraç. ScriptDBCPlayer.java 289 V6033 Un element amb la mateixa clau 'startIndex' ja ha estat canviat. ScriptDBCPlayer.java 293 Do warnings really indicate errors? And if so, what does it lead to? Here, a whole detective investigation with an interesting ending awaits us. En primer lloc, anem a veure el primer avís. L'autor intrèpid de l'article, amb el seu barret detectiu i el seu mantell llarg, va a buscar pistes a la classe. startIndex File: : ScriptDBCPlayer.java(224) .... int num = -1; boolean number; try { num = Integer.parseInt(bonusID); number = true; } catch (Exception var33) { number = false; } for (int i = 0; i < bonuses.length; ++i) { .... if (number && i == num || !number && bonusValues[i][0].equals(bonusID) ) { bonuses[i] = ""; break; } } .... El codi sembla molt similar, però no és casualitat que en el nostre equip, anomenem l'autor de l'article "The Keen Eye" - va trobar la diferència! in the Bloc i en el Lloc: Estan and En el primer fragment, inicialitzem En la bloc, i després iterar a través d'ella en un loop. different try for num i startIndex try for Therefore, in the second fragment, two different variables are compared in an identical check: i En el primer, tenim el que va assenyalar l'analista - . num i startIndex == startIndex Així que ara sabem com havia de ser el primer fragment. ? But what does the error impact At this point, the detective noticed an important clue in the name of the class itself: the prefix significa que la classe s'utilitza en el mecanisme de guió del joc. El mètode amb error és responsable d'assignar bonificacions als atributs del joc com "Força", "Agilitat", etc. Lògicament, el mètode hauria de llegir un índex de bonificació específic del guió del joc i després aplicar el modificador corresponent. Script En primer lloc, hem d'analitzar l'índex de bonificació en el El bloc: try try { num = Integer.parseInt(bonusID); number = true; } .... I després, en el bucle, estableix el valor passat al bonus: for (int i = 0; i < bonuses.length; ++i) { .... if (number && i == num || ....) { noNBTText = bonusValues[i][0] + ";" + bonusValueString; bonuses[i] = ""; bonuses[i] = noNBTText; .... break; } } But in the fragment with the error, the attribute index obtained from the script is overwritten with zero and then compared with itself: try { startIndex = Integer.parseInt(bonusID); number = true; } catch (Exception var34) { number = false; } for (startIndex = 0; startIndex < bonuses.length; ++startIndex) { .... if (number && startIndex == startIndex || // <= !number && bonusValues[startIndex][0].equals(bonusID) ) { noNBTText = bonusValues[startIndex][0] + ";" + bonusValueString; .... } } That is, the bonus value passed in the script is always assigned to the Bonificació perquè is overwritten in the condition and becomes zero, and then is compared with itself. Now the problem is crystal clear. Let's look at the consequences of the error. first startIndex for En primer lloc, hem d'escriure un guió en el joc que crearà bonificacions per a "Força". Primer, creem dos bons amb valors de 1, i després fixem els seus valors a 5 i 15, respectivament: El Hem creat bons per a "Força" amb els índexs 0 i 1, i després els hem assignat els valors. Ara, en teoria, si mostrem aquests dos bons de "Força", obtindrem 5 i 15? Si no hi hagués hagut cap error en el codi, això hauria passat. Per confirmar aquest comportament, anem a escriure un altre guió en el joc que simplement imprimeix els valors de bonificació: El Com a resultat, obtenim: El De fet, el segon bo a "Power" també té un valor inicial d'1, mentre que el primer bo ara és igual a 15. Tenint en compte la quantitat de treball detectiu involucrat i el fet que aquest bug es pugui reproduir de manera fiable en la pràctica, li atorguem amb confiança el títol de En el nostre top 10! honorable first place Wrap-up Yay! Hem fet a través de tots els deu dels errors més estranys, calents i educatius, i esperem que gaudiu del viatge tant com ho vam fer! Si teniu els vostres propis pensaments sobre aquesta part superior, o potser un error favorit del vostre propi, estem encantats de veure que els compartiu en els comentaris. Si voleu llegir més sobre comprovar altres projectes, us convidem al nostre O és . El blog Aquesta pàgina Si t'inspires a comprovar els teus projectes individuals i vols provar tu mateix l'analitzador PVS-Studio, segueix I si ets estudiant, professor o mantenidor de projectes de codi obert en C, C++, C# o Java, pots utilitzar PVS-Studio de forma gratuïta. . Enllaç Aquí All that's left is to say goodbye for now. Happy Holidays! See ya soon!