Skip to content

Commit

Permalink
Fix #3119. Don't expose ListValue<T> lists to users (#3129)
Browse files Browse the repository at this point in the history
* Fix #3119. Don't expose ListValue<T> lists to users

Use Collections.Generic.List<T> instead of ListValue<T>. Return a ListValue copy of these lists to user code.

Changes:
FileContent:binary
Lexicon:keys
Lexicon:values
Part:children
Stage:resources
Stage:resourceslex
String:split
Structure:suffixnames
Timewarp:ratelist
Timewarp:railsratelist
Timewarp:physicsratelist
Vessel:parts
Vessel:dockingports
Vessel:decouplers (separators)
Vessel:resources
allnodes bound variable
allwaypoints function

Stage:resources, Stage:resourceslex, Vessel:parts, Vessel:dockingports, Vessel:decouplers now return copies instead of references to internal lists. All lists returned by these suffixes don't get modified over time so the only way to tell that they are copies now is to use the equality operator. Stage:resources and Stage:resourceslex returning a reference was a bug.

* Optimize by not creating list copies where possible. Revert Vessel parts, dockingports, decouplers suffixes to return readonly references to internal lists
  • Loading branch information
sug44 authored Feb 1, 2025
1 parent 2609a7a commit b9aad87
Show file tree
Hide file tree
Showing 15 changed files with 62 additions and 82 deletions.
14 changes: 7 additions & 7 deletions src/kOS.Safe/Encapsulation/Lexicon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ private void FillWithEnumerableValues(IEnumerable<Structure> values)
private void InitalizeSuffixes()
{
AddSuffix("CLEAR", new NoArgsVoidSuffix(Clear, "Removes all items from Lexicon"));
AddSuffix("KEYS", new Suffix<ListValue<Structure>>(GetKeys, "Returns the lexicon keys"));
AddSuffix("KEYS", new Suffix<ListValue>(GetKeys, "Returns the lexicon keys"));
AddSuffix("HASKEY", new OneArgsSuffix<BooleanValue, Structure>(HasKey, "Returns true if a key is in the Lexicon"));
AddSuffix("HASVALUE", new OneArgsSuffix<BooleanValue, Structure>(HasValue, "Returns true if value is in the Lexicon"));
AddSuffix("VALUES", new Suffix<ListValue<Structure>>(GetValues, "Returns the lexicon values"));
AddSuffix("VALUES", new Suffix<ListValue>(GetValues, "Returns the lexicon values"));
AddSuffix("COPY", new NoArgsSuffix<Lexicon>(() => new Lexicon(this), "Returns a copy of Lexicon"));
AddSuffix("LENGTH", new NoArgsSuffix<ScalarValue>(() => internalDictionary.Count, "Returns the number of elements in the collection"));
AddSuffix("REMOVE", new OneArgsSuffix<BooleanValue, Structure>(one => Remove(one), "Removes the value at the given key"));
Expand Down Expand Up @@ -158,12 +158,12 @@ private BooleanValue HasKey(Structure key)
return internalDictionary.ContainsKey(key);
}

public ListValue<Structure> GetValues()
public ListValue GetValues()
{
return ListValue.CreateList(Values);
}

public ListValue<Structure> GetKeys()
public ListValue GetKeys()
{
return ListValue.CreateList(Keys);
}
Expand Down Expand Up @@ -380,9 +380,9 @@ public override BooleanValue HasSuffix(StringValue suffixName)
/// to the list.
/// </summary>
/// <returns></returns>
public override ListValue<StringValue> GetSuffixNames()
public override ListValue GetSuffixNames()
{
ListValue<StringValue> theList = base.GetSuffixNames();
ListValue theList = base.GetSuffixNames();

foreach (Structure key in internalDictionary.Keys)
{
Expand All @@ -392,7 +392,7 @@ public override ListValue<StringValue> GetSuffixNames()
theList.Add(keyStr);
}
}
return new ListValue<StringValue>(theList.OrderBy(item => item.ToString()));
return new ListValue(theList.OrderBy(item => item.ToString()));
}
public override Dump Dump()
{
Expand Down
6 changes: 3 additions & 3 deletions src/kOS.Safe/Encapsulation/StringValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,10 @@ System.Collections.IEnumerator IEnumerable.GetEnumerator()
}

// As the regular Split, except returning a ListValue rather than an array.
public ListValue<StringValue> SplitToList(string separator)
public ListValue SplitToList(string separator)
{
string[] split = Regex.Split(internalString, Regex.Escape(separator), RegexOptions.IgnoreCase);
ListValue<StringValue> returnList = new ListValue<StringValue>();
ListValue returnList = new ListValue();
foreach (string s in split)
returnList.Add(new StringValue(s));
return returnList;
Expand Down Expand Up @@ -273,7 +273,7 @@ private void StringInitializeSuffixes()
AddSuffix("PADRIGHT", new OneArgsSuffix<StringValue, ScalarValue>( one => PadRight(one)));
AddSuffix("REMOVE", new TwoArgsSuffix<StringValue, ScalarValue, ScalarValue>( (one, two) => Remove(one, two)));
AddSuffix("REPLACE", new TwoArgsSuffix<StringValue, StringValue, StringValue>( (one, two) => Replace(one, two)));
AddSuffix("SPLIT", new OneArgsSuffix<ListValue<StringValue>, StringValue>( one => SplitToList(one)));
AddSuffix("SPLIT", new OneArgsSuffix<ListValue, StringValue>( one => SplitToList(one)));
AddSuffix("STARTSWITH", new OneArgsSuffix<BooleanValue, StringValue>( one => StartsWith(one)));
AddSuffix("TOLOWER", new NoArgsSuffix<StringValue>(() => ToLower()));
AddSuffix("TOUPPER", new NoArgsSuffix<StringValue>(() => ToUpper()));
Expand Down
14 changes: 7 additions & 7 deletions src/kOS.Safe/Encapsulation/Structure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private void InitializeInstanceSuffixes()

AddSuffix("TOSTRING", new NoArgsSuffix<StringValue>(() => ToString()));
AddSuffix("HASSUFFIX", new OneArgsSuffix<BooleanValue, StringValue>(HasSuffix));
AddSuffix("SUFFIXNAMES", new NoArgsSuffix<ListValue<StringValue>>(GetSuffixNames));
AddSuffix("SUFFIXNAMES", new NoArgsSuffix<ListValue>(GetSuffixNames));
AddSuffix("ISSERIALIZABLE", new NoArgsSuffix<BooleanValue>(() => this is SerializableStructure));
AddSuffix("TYPENAME", new NoArgsSuffix<StringValue>(() => new StringValue(KOSName)));
AddSuffix("ISTYPE", new OneArgsSuffix<BooleanValue,StringValue>(GetKOSIsType));
Expand Down Expand Up @@ -208,17 +208,17 @@ public virtual BooleanValue HasSuffix(StringValue suffixName)
return false;
}

public virtual ListValue<StringValue> GetSuffixNames()
public virtual ListValue GetSuffixNames()
{
callInitializeSuffixes();
List<StringValue> names = new List<StringValue>();
names.AddRange(instanceSuffixes.Keys.Select(item => (StringValue)item));
names.AddRange(GetStaticSuffixesForType(GetType()).Keys.Select(item => (StringValue)item));
List<StringValue> names = new List<StringValue>();

foreach (var suffix in instanceSuffixes.Keys) names.Add(suffix);
foreach (var staticSuffix in GetStaticSuffixesForType(GetType()).Keys) names.Add(staticSuffix);

// Return the list alphabetized by suffix name. The key lookups above, since they're coming
// from a hashed dictionary, won't be in any predictable ordering:
return new ListValue<StringValue>(names.OrderBy(item => item.ToString()));
return new ListValue(names.OrderBy(item => item.ToString()));
}

public virtual BooleanValue GetKOSIsType(StringValue queryTypeName)
Expand Down
7 changes: 1 addition & 6 deletions src/kOS.Safe/Persistence/FileContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,10 @@ private void InitializeSuffixes()
AddSuffix("EMPTY", new Suffix<BooleanValue>(() => Size == 0));
AddSuffix("TYPE", new Suffix<StringValue>(() => Category.ToString()));
AddSuffix("STRING", new Suffix<StringValue>(() => String));
AddSuffix("BINARY", new Suffix<ListValue<ScalarIntValue>>(() => ContentAsIntList()));
AddSuffix("BINARY", new Suffix<ListValue>(() => new ListValue(Bytes.Select(x => new ScalarIntValue(x)))));
AddSuffix("ITERATOR", new Suffix<Enumerator>(() => new Enumerator(GetEnumerator())));
}

private ListValue<ScalarIntValue> ContentAsIntList()
{
return new ListValue<ScalarIntValue>(Bytes.Select(x => new ScalarIntValue(x)));
}

public override Dump Dump()
{
return new Dump { { DumpContent, PersistenceUtilities.EncodeBase64(Bytes) } };
Expand Down
7 changes: 4 additions & 3 deletions src/kOS/Binding/FlightStats.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using kOS.Module;
using kOS.Control;
using kOS.Safe.Binding;
Expand Down Expand Up @@ -59,12 +60,12 @@ public override void AddTo(SharedObjects shared)
shared.BindingMgr.AddGetter("ALLNODES", () => GetAllNodes(shared));
}

public ListValue<Node> GetAllNodes(SharedObjects shared)
public ListValue GetAllNodes(SharedObjects shared)
{
var vessel = shared.Vessel;
if (vessel.patchedConicSolver == null || vessel.patchedConicSolver.maneuverNodes.Count == 0)
return new ListValue<Node>();
var ret = new ListValue<Node>(vessel.patchedConicSolver.maneuverNodes.Select(e => Node.FromExisting(vessel, e, shared)));
return new ListValue();
var ret = new ListValue(vessel.patchedConicSolver.maneuverNodes.Select(e => Node.FromExisting(vessel, e, shared)));
return ret;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/kOS/Function/Suffixed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ public override void Execute(SharedObjects shared)
AssertArgBottomAndConsume(shared); // no args

// ReSharper disable SuggestUseVarKeywordEvident
ListValue<WaypointValue> returnList = new ListValue<WaypointValue>();
ListValue returnList = new ListValue();
// ReSharper enable SuggestUseVarKeywordEvident

WaypointManager wpm = WaypointManager.Instance();
Expand Down
4 changes: 2 additions & 2 deletions src/kOS/Suffixed/AggregateResourceValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ public static ListValue PartsToList(IEnumerable<global::Part> parts, SharedObjec
return list;
}

public static ListValue<AggregateResourceValue> FromVessel(Vessel vessel, SharedObjects shared)
public static ListValue FromVessel(Vessel vessel, SharedObjects shared)
{
var resources = ProspectResources(vessel.parts, shared);
return ListValue<AggregateResourceValue>.CreateList(resources.Values);
return new ListValue(resources.Values);
}
}
}
2 changes: 1 addition & 1 deletion src/kOS/Suffixed/Part/DockingPortValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public PartValue GetPartner()
}

var otherVessel = VesselTarget.CreateOrGetExisting(otherNode.vessel, Shared);
foreach (var part in otherVessel.Parts)
foreach (PartValue part in otherVessel.Parts)
{
if (part.Part == otherNode.part)
{
Expand Down
6 changes: 3 additions & 3 deletions src/kOS/Suffixed/Part/PartValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class PartValue : Structure, IKOSTargetable
public global::Part Part { get; private set; }
public PartValue Parent { get; private set; }
public DecouplerValue Decoupler { get; private set; }
public ListValue<PartValue> Children { get; private set; }
public ListValue Children { get; private set; }
public Structure ParentValue { get { return (Structure)Parent ?? StringValue.None; } }
public Structure DecouplerValue { get { return (Structure)Decoupler ?? StringValue.None; } }
public int DecoupledIn { get { return (Decoupler != null) ? Decoupler.Part.inverseStage : -1; } }
Expand All @@ -37,7 +37,7 @@ internal PartValue(SharedObjects shared, global::Part part, PartValue parent, De
Parent = parent;
Decoupler = decoupler;
RegisterInitializer(PartInitializeSuffixes);
Children = new ListValue<PartValue>();
Children = new ListValue();
}

private void PartInitializeSuffixes()
Expand Down Expand Up @@ -66,7 +66,7 @@ private void PartInitializeSuffixes()
AddSuffix(new[] { "DECOUPLER", "SEPARATOR" }, new Suffix<Structure>(() => DecouplerValue, "The part that will decouple/separate this part when activated"));
AddSuffix(new[] { "DECOUPLEDIN", "SEPARATEDIN" }, new Suffix<ScalarValue>(() => DecoupledIn));
AddSuffix("HASPARENT", new Suffix<BooleanValue>(() => Part.parent != null, "Tells you if this part has a parent, is used to avoid null exception from PARENT"));
AddSuffix("CHILDREN", new Suffix<ListValue<PartValue>>(() => PartValueFactory.ConstructGeneric(Part.children, Shared), "A LIST() of the children parts of this part"));
AddSuffix("CHILDREN", new Suffix<ListValue>(() => PartValueFactory.ConstructGeneric(Part.children, Shared), "A LIST() of the children parts of this part"));
AddSuffix("DRYMASS", new Suffix<ScalarValue>(() => Part.GetDryMass(), "The Part's mass when empty"));
AddSuffix("MASS", new Suffix<ScalarValue>(() => Part.CalculateCurrentMass(), "The Part's current mass"));
AddSuffix("WETMASS", new Suffix<ScalarValue>(() => Part.GetWetMass(), "The Part's mass when full"));
Expand Down
4 changes: 2 additions & 2 deletions src/kOS/Suffixed/Part/PartValueFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public static ListValue Construct(IEnumerable<global::Part> parts, SharedObjects
return ListValue.CreateList(parts.Select(part => Construct(part, shared)).Where(p => p != null));
}

public static ListValue<PartValue> ConstructGeneric(IEnumerable<global::Part> parts, SharedObjects shared) {
return ListValue<PartValue>.CreateList(parts.Select(part => Construct(part, shared)).Where(p => p != null));
public static ListValue ConstructGeneric(IEnumerable<global::Part> parts, SharedObjects shared) {
return new ListValue(parts.Select(part => Construct(part, shared)).Where(p => p != null));
}

public static PartValue Construct(global::Part part, SharedObjects shared) {
Expand Down
12 changes: 7 additions & 5 deletions src/kOS/Suffixed/ResourceTransferValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,7 @@ private TransferPartType DetermineType(object toTest)
{
return TransferPartType.Part;
}
if (toTest is ListValue)
{
return TransferPartType.Parts;
}
if (toTest is ListValue<PartValue>)
if (toTest is ListValue || toTest is ListValue<PartValue> || toTest is List<PartValue>)
{
return TransferPartType.Parts;
}
Expand Down Expand Up @@ -350,6 +346,12 @@ private double CalculateAvailableResource(IEnumerable<global::Part> fromParts)
parts.AddRange(partListValue.Select(pv => pv.Part));
break;
}
var partList= obj as List<PartValue>;
if (partList!= null)
{
parts.AddRange(partList.Select(pv => pv.Part));
break;
}
break;
case TransferPartType.Element:
var element = obj as ElementValue;
Expand Down
10 changes: 5 additions & 5 deletions src/kOS/Suffixed/StageValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class StageValues : Structure
private readonly SharedObjects shared;
private HashSet<global::Part> partHash = new HashSet<global::Part>();
private PartSet partSet;
private ListValue<ActiveResourceValue> resList;
private ListValue resList;
private Lexicon resLex;

// Set by VesselTarget (from InvalidateParts)
Expand All @@ -44,16 +44,16 @@ private void InitializeSuffixes()

AddSuffix("NUMBER", new Suffix<ScalarValue>(() => shared.Vessel.currentStage));
AddSuffix("READY", new Suffix<BooleanValue>(() => shared.Vessel.isActiveVessel && StageManager.CanSeparate));
AddSuffix("RESOURCES", new Suffix<ListValue<ActiveResourceValue>>(GetResourceManifest));
AddSuffix("RESOURCESLEX", new Suffix<Lexicon>(GetResourceDictionary));
AddSuffix("RESOURCES", new Suffix<ListValue>(() => new ListValue(GetResourceManifest())));
AddSuffix("RESOURCESLEX", new Suffix<Lexicon>(() => new Lexicon(GetResourceDictionary())));
AddSuffix(new string[] { "NEXTDECOUPLER", "NEXTSEPARATOR" }, new Suffix<Structure>(() => shared.VesselTarget.NextDecoupler ?? (Structure)StringValue.None));
AddSuffix("DELTAV", new Suffix<DeltaVCalc>(() => new DeltaVCalc(shared, shared.Vessel.VesselDeltaV.GetStage(shared.Vessel.currentStage))));
}

private ListValue<ActiveResourceValue> GetResourceManifest()
private ListValue GetResourceManifest()
{
if (resList != null) return resList;
resList = new ListValue<ActiveResourceValue>();
resList = new ListValue();
CreatePartSet();
var defs = PartResourceLibrary.Instance.resourceDefinitions;
foreach (var def in defs)
Expand Down
24 changes: 3 additions & 21 deletions src/kOS/Suffixed/TimeWarpValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ public static TimeWarpValue Instance
private void InitializeSuffixes()
{
AddSuffix("RATE", new SetSuffix<ScalarValue>(GetRate, SetRate));
AddSuffix("RATELIST", new Suffix<ListValue<ScalarValue>>(GetRatesList));
AddSuffix("RAILSRATELIST", new Suffix<ListValue<ScalarValue>>(() => GetRatesList(TimeWarp.Modes.HIGH)));
AddSuffix("PHYSICSRATELIST", new Suffix<ListValue<ScalarValue>>(() => GetRatesList(TimeWarp.Modes.LOW)));
AddSuffix("RATELIST", new Suffix<ListValue>(() => ListValue.CreateList(GetRateArrayForMode(TimeWarp.WarpMode))));
AddSuffix("RAILSRATELIST", new Suffix<ListValue>(() => ListValue.CreateList(GetRateArrayForMode(TimeWarp.Modes.HIGH))));
AddSuffix("PHYSICSRATELIST", new Suffix<ListValue>(() => ListValue.CreateList(GetRateArrayForMode(TimeWarp.Modes.LOW))));
AddSuffix("MODE", new SetSuffix<StringValue>(GetModeAsString, SetModeAsString));
AddSuffix("WARP", new SetSuffix<ScalarIntValue>(GetWarp, SetWarp));
AddSuffix("WARPTO", new OneArgsSuffix<ScalarValue>(WarpTo));
Expand Down Expand Up @@ -134,24 +134,6 @@ public ScalarValue GetDeltaT()
return TimeWarp.fixedDeltaTime;
}

public ListValue<ScalarValue> GetRatesList()
{
return GetRatesList(TimeWarp.WarpMode);
}

public ListValue<ScalarValue> GetRatesList(TimeWarp.Modes warpMode)
{
float[] ratesArray = GetRateArrayForMode(warpMode);

ListValue<ScalarValue> ratesKOSList = new ListValue<ScalarValue>();

// Have to convert the elements one at a time from (float) to (ScalarDoubleValue):
foreach (float val in ratesArray)
ratesKOSList.Add(val);

return ratesKOSList;
}

public BooleanValue IsWarpSettled()
{
float expectedRate = GetRateArrayForMode(TimeWarp.WarpMode)[TimeWarp.CurrentRateIndex];
Expand Down
18 changes: 9 additions & 9 deletions src/kOS/Suffixed/VesselTarget.Parts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ partial class VesselTarget
//..... [root, child1, child2, ..., part1-1, part1-2, ..., part2-1, ... heap ;)
private PartValue rootPart;
private DecouplerValue nextDecoupler;
private ListValue<PartValue> allParts;
private ListValue<DockingPortValue> dockingPorts;
private ListValue<DecouplerValue> decouplers;
private ListValue allParts;
private ListValue dockingPorts;
private ListValue decouplers;
private Dictionary<global::Part, PartValue> partCache;

private void InvalidateParts()
Expand Down Expand Up @@ -53,7 +53,7 @@ public DecouplerValue NextDecoupler
return nextDecoupler;
}
}
public ListValue<PartValue> Parts
public ListValue Parts
{
get
{
Expand All @@ -62,7 +62,7 @@ public ListValue<PartValue> Parts
return allParts;
}
}
public ListValue<DockingPortValue> DockingPorts
public ListValue DockingPorts
{
get
{
Expand All @@ -71,7 +71,7 @@ public ListValue<DockingPortValue> DockingPorts
return dockingPorts;
}
}
public ListValue<DecouplerValue> Decouplers
public ListValue Decouplers
{
get
{
Expand All @@ -98,9 +98,9 @@ private void ConstructParts()
{
rootPart = null;
nextDecoupler = null;
allParts = new ListValue<PartValue>();
dockingPorts = new ListValue<DockingPortValue>();
decouplers = new ListValue<DecouplerValue>();
allParts = new ListValue();
dockingPorts = new ListValue();
decouplers = new ListValue();
partCache = new Dictionary<global::Part, PartValue>();

ConstructPart(Vessel.rootPart, null, null);
Expand Down
Loading

0 comments on commit b9aad87

Please sign in to comment.