It has already become a tradition that programmers replenishing the PVS-Studio team begin their work by writing an article about analyzing an open source project. This time, such a proven project will be Telerik UI for UWP.
PVS-Studio code analyzer
PVS-Studio is a tool for detecting errors and potential vulnerabilities in the source code of programs written in C, C ++, C # and Java. Runs on Windows, Linux, and macOS.
The analyzer provides various usage scenarios:
- can be used locally on development machines, integrating as a plug-in with Visual Studio or IntelliJ IDEA;
- can integrate with the SonarQube continuous quality assurance platform;
- can be used independently, integrating into the assembly system;
- it is possible to use compilation monitoring utility;
- integration with Azure DevOps, Jenkins, TeamCity, Travis CI and similar systems is possible;
- and so on.
Audited Project
Telerik UI for UWP is a set of user interface components for the Universal Windows Platform (UWP). The source code for the project can be
found on Github . The set includes more than 20 components that allow you to visualize data in the form of graphs, create lists and tables, use a map to demonstrate data that is associated with a location.
Fragments that attracted attention when studying the analyzer report
PVS-Studio Warning :
V3013 It is odd that the body of 'OnMinValuePropertyChanged' function is fully equivalent to the body of 'OnMaxValuePropertyChanged' function. RadGauge.cs 446
private static void OnMinValuePropertyChanged( DependencyObject sender, DependencyPropertyChangedEventArgs args) { double newVal = (double)args.NewValue; ValidateValue(newVal); RadGauge gauge = sender as RadGauge; if (gauge.panel != null) { gauge.panel.UpdateOnMinMaxValueChange(); } if(AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged)) { var peer = FrameworkElementAutomationPeer.FromElement(gauge) as RadGaugeAutomationPeer; if (peer != null) { peer.RaiseMinimumPropertyChangedEvent((double)args.OldValue, (double)args.NewValue); } } } private static void OnMaxValuePropertyChanged( DependencyObject sender, DependencyPropertyChangedEventArgs args) { double newVal = (double)args.NewValue; ValidateValue(newVal); RadGauge gauge = sender as RadGauge; if (gauge.panel != null) { gauge.panel.UpdateOnMinMaxValueChange(); } if (AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged)) { var peer = FrameworkElementAutomationPeer.FromElement(gauge) as RadGaugeAutomationPeer; if (peer != null) { peer.RaiseMinimumPropertyChangedEvent((double)args.OldValue, (double)args.NewValue); } } }
The analyzer detected two methods,
OnMinValuePropertyChanged and
OnMaxValuePropertyChanged , which perform the same actions. I have strong suspicions that an error has crept into this code. Note that both the
OnMinValuePropertyChanged method and the
OnMaxValuePropertyChanged method use
RaiseMinimumPropertyChangedEvent . At the same time, in the
RadGaugeAutomationPeer class
you can find both a method for “Minimum” and for “Maximum”:
internal void RaiseMaximumPropertyChangedEvent(double oldValue, double newValue) { this.RaisePropertyChangedEvent( RangeValuePatternIdentifiers.MaximumProperty, oldValue, newValue); } internal void RaiseMinimumPropertyChangedEvent(double oldValue, double newValue) { this.RaisePropertyChangedEvent( RangeValuePatternIdentifiers.MinimumProperty, oldValue, newValue); }
The
RaiseMaximumPropertyChangedEvent method is never used in the code, but
RaiseMinimumPropertyChangedEvent is used twice. And, you know, the performance of the
OnMaxValuePropertyChanged method raises questions ... I think it was supposed to write the following:
private static void OnMaxValuePropertyChanged( DependencyObject sender, DependencyPropertyChangedEventArgs args) { .... peer.RaiseMaximumPropertyChangedEvent((double)args.OldValue, (double)args.NewValue); .... }
But even so, the code does not look very neat due to the large number of repeating elements. It is difficult to understand, and repeated lines dull the programmer’s attention, and it becomes more difficult to conduct code review in such conditions. But the static analysis tools do an excellent job of checking such code (but this does not mean that refactoring can be dispensed with, and especially without reducing the number of repeated lines).
Based on the above and the following code fragment, we can assume that the authors are not averse to copy-paste. However, like all of us ... :)
PVS-Studio Warning :
V3001 There are identical sub-expressions 'element.RenderSize == emptySize' to the left and to the right of the '||' operator. TiltInteractionEffect.cs 181
private static bool IsPointInElementBounds(FrameworkElement element, Point position) { Size emptySize = new Size(0, 0); if (element.RenderSize == emptySize || element.RenderSize == emptySize) { return false; } return new Rect(....).Contains(position); }
The analyzer detected an erroneous code fragment in which to the right and to the left of the '||' operator the if
statement uses the same subexpressions. The second subexpression clearly should have looked different. Perhaps in place of the second
RenderSize should be
DesiredSize . Or there should not be a second subexpression at all. In any case, this piece of code needs to be fixed.
PVS-Studio Warning :
V3001 There are identical sub-expressions 'text [0] ==' - '' to the left and to the right of the '||' operator. RadNumericBox.cs 1057
private void ValidateText() { string text = this.textBox.Text; .... if (text.Length == 1 && (text[0] == '-' || text[0] == '-')) { if (this.isNegative) { this.isNegative = false; } else { this.SetText(string.Empty); } return; } .... }
Here, the developer writes the text entered in the text box field to a variable. Then the first character of the string is compared twice with the same '-' character, which is a suspicious decision. Obviously, the validation of the text in this function does not work as originally intended.
PVS-Studio Warning :
V3001 There are identical sub-expressions 'newValue.HasValue' to the left and to the right of the '&&' operator. DateTimePicker.cs 576
private static void OnValueChanged(object sender, DependencyPropertyChangedEventArgs args) { DateTimePicker picker = sender as DateTimePicker; var newValue = (DateTime?)args.NewValue; if (newValue.HasValue && newValue != null)
The expression
newValue.HasValue returns
true if
newValue contains any value, and the expression
newValue! = Null does the same. The analyzer pays attention to this, and what to do is to remove one of the subexpressions or replace it with another (if something else had to be checked), it is up to the developers to decide.
PVS-Studio Warning :
V3125 The 'CurrentAttachedMenu' object was used after it was verified against null. Check lines: 98, 96. PopupService.cs 98
internal static class PopupService { .... private static void Overlay_PointerPressed(....) { if (CurrentAttachedMenu == null || !CurrentAttachedMenu.hitTestService. HitTest(e.GetCurrentPoint(CurrentAttachedMenu).Position).Any()) { CurrentAttachedMenu.IsOpen = false; HideOverlay(); } } }
If the variable
CurrentAttachedMenu turns out to be
null , then evaluating the expression
CurrentAttachedMenu.IsOpen will
raise an exception. At first glance, it seems that this is a simple typo and did not mean a comparison with
null , but the inverse operation - '! ='. But then an exception will occur in the condition of the
if statement if the
CurrentAttachedMenu variable is
null .
Further in the code there were another
37 of the same warnings, some of which appear to indicate errors. But to describe them within the framework of one article is still too much, so I will leave them unattended.
PVS-Studio Warning :
V3019 Possibly an incorrect variable is compared to null after type conversion using 'as' keyword. Check variables 'dragDropElement', 'uiDragDropElement'. DragDrop.cs 91
internal static void StartDrag(....) { var dragDropElement = sender as IDragDropElement; .... UIElement uiDragDropElement = dragDropElement as UIElement; .... if (dragDropElement == null || !dragDropElement.CanStartDrag(trigger, initializeContext)) { return; } .... }
It is very likely that the author mixed up the variables. The
null inequality is checked not by the link obtained as a result of the cast, but by the original (
dragDropElement ). Most likely, the
uiDragDropElement link was supposed to be
checked . The conjecture is also confirmed by the fact that the programmer further used
uiDragDropElement without checking for
null .
PVS-Studio Warning :
V3030 Recurring check. The '! ShowIndicatorWhenNoData' condition was already verified in line 139. RadDataBoundListBox.PullToRefresh.cs 141
internal void HandlePullToRefreshItemStateChanged(object item, ItemState state) { .... bool showIndicatorWhenNoData = this.ShowPullToRefreshWhenNoData; if (this.realizedItems.Count == 0 && !showIndicatorWhenNoData) { if (state == ItemState.Recycled && !showIndicatorWhenNoData) { this.StopPullToRefreshLoading(false); this.HidePullToRefreshIndicator(); } return; } .... }
The analyzer found a piece of code in which, under two conditions, the same
showIndicatorWhenNoData variable is re-checked. Perhaps the check is simply redundant, but there is also the possibility that one of the duplicate subexpressions should be different at all.
PVS-Studio Warning :
V3031 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions. SelectedItemCollection.cs 77
internal class SelectedItemCollection : ObservableCollection<object> { .... private bool CanInsertItem(object item) { return this.suspendLevel == 0 && this.AllowSelect && ((!this.AllowMultipleSelect && this.Count == 0) || this.AllowMultipleSelect); } }
This piece of code is not formally erroneous. The analyzer hints at some code redundancy in the condition. However, it is worth remembering that extra code is sometimes the result of an error, for example, when instead of one variable, another is checked several times.
You can reduce this condition a bit and remove the extra code. For example, like this:
internal class SelectedItemCollection : ObservableCollection<object> { .... private bool CanInsertItem(object item) { return this.suspendLevel == 0 && this.AllowSelect && (this.AllowMultipleSelect || this.Count == 0); } }
Other similar messages:
- V3031 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions. SelectedItemCollection.cs 93
- V3031 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions. StackVirtualizationStrategy.cs 49
- V3031 An excessive check can be simplified. The '||' operator is surrounded by opposite expressions 'state == null' and 'state! = null'. LocalFieldDescriptionsProviderBase.cs 24
Consider another piece of code, to which the analyzer issued the following:
PVS-Studio warnings :
- V3137 The 'leftMargin' variable is assigned but is not used by the end of the function. DragDrop.cs 87
- V3137 The 'topMargin' variable is assigned but is not used by the end of the function. DragDrop.cs 88
internal static class DragDrop { .... double leftMargin = 0d; double topMargin = 0d; if (frameworkElementSource != null) { leftMargin = frameworkElementSource.Margin.Left;
The analyzer reports that the
leftMargin and
topMargin variables have been assigned values, but after that these variables are not used until the end of the method. There is probably no mistake here, but such code looks suspicious. This may be due to a typo or unsuccessful refactoring.
The same problem was found elsewhere: V3137 The 'currentColumnLength' variable is assigned but is not used by the end of the function. WrapLayout.cs 824
PVS-Studio Warning :
V3061 Parameter 'index' is always rewritten in method body before being used. DataEngine.cs 1443
private static Tuple<Group, int> FindGroupAndItemIndex(.... int index, ....) { if (exhaustiveSearch) { .... } else { var aggregateRowGroup = rowRootGroup; var rowGroupNames = valueProvider.GetRowGroupNames(item); foreach (var groupName in rowGroupNames) { Group group; if (aggregateRowGroup.TryGetGroup(groupName, out group)) { aggregateRowGroup = group; } } index = aggregateRowGroup.IndexOf(item,
The
index parameter of the
FindGroupAndItemIndex method
is overwritten before being used. Most likely, this indicates a programmer error.
PVS-Studio Warning :
V3083 Unsafe invocation of event 'Completed', NullReferenceException is possible. Consider assigning event to a local variable before invoking it. ActionBase.cs 32
internal abstract class ActionBase { .... protected virtual void OnCompleted() { this.IsCompleted = true; if (this.Completed != null) { this.Completed(this, EventArgs.Empty); } } }
The programmer allowed a potentially unsafe call to the event handler in this method, which can lead to an exception of type
NullReferenceException . An exception will be thrown provided that between the
null check and the call of the event handlers, this event does not remain.
There are another
49 similar problems in the code. It will not be interesting to watch all of them in this article, and the authors can easily find them on their own using PVS-Studio, so we will move on to other errors.
PVS-Studio Warning :
V3145 Unsafe dereference of a WeakReference target, consider inspecting info.Target. The object could have been garbage collected between checking 'IsAlive' and accessing the 'Target' property. FadeAnimation.cs 84
public class RadFadeAnimation : RadAnimation { .... protected internal override void ApplyAnimationValues(PlayAnimationInfo info) { .... if (info.Target.Opacity != opacity)
The analyzer warns of the danger of
throwing an exception of type
NullReferenceException when accessing the
info.Target.Opacity property. In order to better understand the essence of the problem, you need to look at fragments of the
PlayAnimationInfo class, in particular, the
Target property.
public class PlayAnimationInfo { .... private WeakReference target; .... public PlayAnimationInfo(Storyboard storyboard, RadAnimation animation, UIElement target) { .... this.target = new WeakReference(target); .... } .... public UIElement Target { get { if (this.target.IsAlive) { return this.target.Target as UIElement; } return null; } } .... }
In general, the further you dig this code, the more potential problems you can find in it. Let's look at perhaps the most interesting one - the one to which the analyzer issued a warning. The fact is that even if execution follows the then-branch of the
if statement , this does not guarantee that a non-zero reference will be returned. Regardless of conversations about the cast, here we consider everything permissible due to the initialization of the object in the constructor.
How is this possible? The fact is that if between the
IsAlive check and the
Target call a garbage collection is performed, under which the object referenced by
WeakReference falls ,
this.target.Target will return
null . That is, the
IsAlive check
does not guarantee that the next time the
Target is accessed, the object is still available.
By the way, the situation is
return null; catches another diagnostic rule: V3080 Possible null dereference. Consider inspecting 'info.Target'. FadeAnimation.cs 84
Similar problems in the code occurred several more times:
- V3145 Unsafe dereference of a WeakReference target, consider inspecting target. The object could have been garbage collected before the 'Target' property was accessed. MoveXAnimation.cs 80
- V3145 Unsafe dereference of a WeakReference target, consider inspecting target. The object could have been garbage collected before the 'Target' property was accessed. MoveYAnimation.cs 80
- V3145 Unsafe dereference of a WeakReference target, consider inspecting info.Target. The object could have been garbage collected before the 'Target' property was accessed. PlaneProjectionAnimation.cs 244
- V3145 Unsafe dereference of a WeakReference target. The object could have been garbage collected between checking 'IsAlive' and accessing the 'Target' property. WeakEventHandler.cs 109
Let's move on to the next example.
Warning PVS-Studio :
V3066 Possible incorrect order of arguments passed to 'NotifyCollectionChangedEventArgs' constructor: 'oldItem' and 'newItem'. CheckedItemsCollection.cs 470
public class CheckedItemsCollection<T> : IList<T>, INotifyCollectionChanged { .... private NotifyCollectionChangedEventArgs GenerateArgs(....) { switch (action) { case NotifyCollectionChangedAction.Add: .... case NotifyCollectionChangedAction.Remove: .... case NotifyCollectionChangedAction.Replace: return new NotifyCollectionChangedEventArgs( action, oldItem, newItem, changeIndex);
To understand what this warning from the analyzer means, it is worth looking at the
NotifyCollectionChangedEventArgs constructor
parameters :
public NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction action, object newItem, object oldItem, int index);
The analyzer warns that in the expression
return new NotifyCollectionChangedEventArgs( action, oldItem, newItem, changeIndex);
swapped the variables
oldItem and
newItem . In the constructor definition, they are listed in a different order. Whether this was done consciously or not, one can only guess.
PVS-Studio Warning :
V3102 Suspicious access to element of 'x' object by a constant index inside a loop. DataEngine.cs 1718
private class ObjectArrayComparer : IEqualityComparer<object[]> { public bool Equals(object[] x, object[] y) { .... for (int i = 0; i < x.Length; i++) { if (!object.Equals(x[0], y[0]))
At each iteration in the loop, the programmer compares
x [0] and
y [0]. However, the loop does not make sense in this code, since only the first elements are compared. Most likely, this meant a comparison of the corresponding elements of arrays. Then the correct code will be like this:
for (int i = 0; i < x.Length; i++) { if (!object.Equals(x[i], y[i])) { return false; } }
Warning PVS-Studio :
V3123 Perhaps the '?:' Operator works in a different way than it was expected. Its priority is lower than priority of other operators in its condition. EditRowHostPanel.cs 35
protected override Size MeasureOverride(Size availableSize) { .... bool shouldUpdateRowHeight = editorLine == 0 || displayedElement == null ? false : displayedElement.ContainerType != typeof(DataGridGroupHeader); .... }
A warning is associated with the use of the '?:' Operator. It has a lower priority than
! =, ||, ==. Therefore, the expression may not be evaluated as planned by the programmer. Apparently, in this case this is a false positive and the code works correctly. But reading such code is very difficult and there is never any certainty that it is understood correctly. It feels like the developer wrote in such a way that no one understood anything :) The best way to do this is more readable - use brackets or the
if statement .
PVS-Studio Warning :
V3078 Original sorting order will be lost after repetitive call to 'OrderBy' method. Use 'ThenBy' method to preserve the original sorting. GridModel.Selection.cs 107
internal partial class GridModel { private void BuildCellSelectionRegions(....) { .... this.MergeCellSelectionRegions(selectedItemsInView .OrderBy(c => c.Column.ItemInfo.LayoutInfo.Line) .OrderBy(c => c.RowItemInfo.LayoutInfo.Line)); } }
The error is related to calling
OrderBy again for a collection of type
IOrderedEnumerable . Here the collection is sorted first by columns, then by rows. Moreover, at the time of sorting by rows, the previous sorting by columns is not taken into account anywhere. In order not to lose the sorting by columns and sort the collection by several criteria at once, then use
ThenBy :
this.MergeCellSelectionRegions(selectedItemsInView .OrderBy(c => c.Column.ItemInfo.LayoutInfo.Line) .ThenBy(c => c.RowItemInfo.LayoutInfo.Line));
PVS-Studio Warning :
V3008 The 'currentColumnLength' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 791, 785. WrapLayout.cs 791
private void OnAvailableLengthChanged(double oldValue, double newValue) { .... if (....) { if (currentColumnLength > 0) { var paddingValue = Math.Max(0, newValue - currentColumnLength); this.paddingRenderInfo.Add(paddingValue); currentColumnLength = 0;
It seemed suspicious to the analyzer that the
currentColumnLength variable was assigned a value twice. The variable is not used between assignments. Regardless of the condition, this variable will ultimately be zero. This code is either incorrect or redundant.
PVS-Studio Warning :
V3127 Two similar code fragments were found. Perhaps, this is a typo and 'emptyIconContainer' variable should be used instead of 'filledIconContainer' RadRatingItem.cs 240
public class RadRatingItem : RadContentControl { .... protected override void OnApplyTemplate() { .... this.filledIconContainer = this.GetTemplateChild( "FilledIconContainer") as Border; if (this.filledIconContainer == null)
Because of a typo, two identical conditions appeared in the code. Judging by the generated exception, the second condition should look like this:
if (this.emptyIconContainer == null) { throw new MissingTemplatePartException( "EmptyIconContainer", typeof(Border)); }
PVS-Studio Warning :
V3020 An unconditional 'break' within a loop. NodePool.cs 189
public IEnumerable<KeyValuePair<int, List<T>>> GetUnfrozenDisplayedElements() { foreach (var item in this.generatedContainers) { foreach (var pair in item.Value) { if (!pair.IsFrozen) { yield return item; } break; } } }
The analyzer found that here the
break statement does not belong to the
if statement .
Break will be executed regardless of the value of
pair.IsFrozen and because of this in
foreach , only one iteration will be performed.
That concludes my consideration of warnings. So that Telerik developers can conduct a more thorough code analysis and fix defects, we are ready to provide them with a temporary license. In addition, they can take advantage of the
free use of PVS-Studio option provided to the authors of open source projects.
Conclusion
Although the developers of Telerik UI for UWP did a great job, it was not without typos, as it usually happens :). All these errors could easily be found by a static analyzer and corrected. The most important thing is to use the analyzer
correctly and regularly .
If you want to share this article with an English-speaking audience, then please use the link to the translation: Ekaterina Nikiforova.
Checking Telerik UI for UWP as a Way to Get Started with PVS-Studio .