Skip to content

Commit 3d9e1d1

Browse files
committed
nTip guard
1 parent 1f5dfdf commit 3d9e1d1

11 files changed

Lines changed: 102 additions & 20 deletions

R/RcppExports.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,10 @@ cpp_mci_impl_score <- function(x, y, n_tips) {
275275
.Call(`_TreeDist_cpp_mci_impl_score`, x, y, n_tips)
276276
}
277277

278+
cpp_sl_max_tips <- function() {
279+
.Call(`_TreeDist_cpp_sl_max_tips`)
280+
}
281+
278282
cpp_robinson_foulds_distance <- function(x, y, nTip) {
279283
.Call(`_TreeDist_cpp_robinson_foulds_distance`, x, y, nTip)
280284
}

R/transfer_consensus.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ TransferConsensus <- function(trees,
6565
if (nTip < 4L) {
6666
return(StarTree(tipLabels))
6767
}
68-
if (nTip > 32767L) stop("This many tips are not (yet) supported.")
68+
.CheckMaxTips(nTip)
6969

7070
# Convert each tree to a raw split matrix (TreeTools C++ internally).
7171
# as.Splits() will error if a tree's tips don't match tipLabels.
@@ -114,7 +114,7 @@ tc_profile <- function(trees, scale = TRUE, greedy = "best",
114114
tipLabels <- TipLabels(trees[[1]])
115115
nTip <- length(tipLabels)
116116
if (nTip < 4L) stop("Need at least 4 tips for profiling.")
117-
if (nTip > 32767L) stop("This many tips are not (yet) supported.")
117+
.CheckMaxTips(nTip)
118118

119119
splitsList <- lapply(trees, function(tr) unclass(as.Splits(tr, tipLabels)))
120120

R/tree_distance.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ GeneralizedRF <- function(splits1, splits2, nTip, PairScorer,
149149
}
150150
nTip <- length(tipLabels)
151151
if (nTip < 4) return(NULL) # nocov
152-
if (nTip > 32767L) stop("This many tips are not (yet) supported.")
152+
.CheckMaxTips(nTip)
153153

154154
splits_list <- as.Splits(tree1, tipLabels = tipLabels)
155155
n_threads <- as.integer(getOption("mc.cores", 1L))
@@ -203,7 +203,7 @@ GeneralizedRF <- function(splits1, splits2, nTip, PairScorer,
203203

204204
nTip <- length(tipLabels1)
205205
if (nTip < 4) return(NULL)
206-
if (nTip > 32767L) stop("This many tips are not (yet) supported.")
206+
.CheckMaxTips(nTip)
207207

208208
splits1 <- as.Splits(tree1, tipLabels = tipLabels1)
209209
splits2 <- as.Splits(tree2, tipLabels = tipLabels1) # Use tipLabels1 to ensure order consistency

R/tree_distance_transfer.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ TransferDistSplits <- function(splits1, splits2,
168168
if (is.null(tipLabels)) return(NULL)
169169
nTip <- length(tipLabels)
170170
if (nTip < 4L) return(NULL)
171-
if (nTip > 32767L) stop("This many tips are not (yet) supported.")
171+
.CheckMaxTips(nTip)
172172

173173
# Check all trees share same tip set
174174
allLabels <- TipLabels(tree1)
@@ -211,7 +211,7 @@ TransferDistSplits <- function(splits1, splits2,
211211
if (is.null(tipLabels)) return(NULL)
212212
nTip <- length(tipLabels)
213213
if (nTip < 4L) return(NULL)
214-
if (nTip > 32767L) stop("This many tips are not (yet) supported.")
214+
.CheckMaxTips(nTip)
215215

216216
# Check all trees share same tip set
217217
allLabels1 <- TipLabels(trees1)

R/tree_distance_utilities.R

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
# Validate that nTip does not exceed the compiled SL_MAX_TIPS limit.
2+
# Called from every distance entry point before any C++ work.
3+
.CheckMaxTips <- function(nTip) {
4+
if (!is.na(nTip) && nTip > .SL_MAX_TIPS) {
5+
stop(
6+
"Trees with ", nTip, " tips exceed the compiled limit of ",
7+
.SL_MAX_TIPS, " tips.",
8+
if (.SL_MAX_TIPS < 32768L)
9+
"\nUpdate TreeTools and reinstall TreeDist to support more tips."
10+
else "",
11+
call. = FALSE
12+
)
13+
}
14+
}
15+
116
#' Wrapper for tree distance calculations
217
#'
318
#' Calls tree distance functions from trees or lists of trees
@@ -132,9 +147,7 @@ CalculateTreeDistance <- function(Func, tree1, tree2 = NULL,
132147
# Fast paths: use OpenMP batch functions when all trees share the same tip
133148
# set and no R-level cluster has been configured. Each branch mirrors the
134149
# generic path exactly but avoids per-pair R overhead.
135-
if (!is.na(nTip) && nTip > 32767L) {
136-
stop("This many tips are not (yet) supported.")
137-
}
150+
.CheckMaxTips(nTip)
138151
if (!is.na(nTip) && is.null(cluster)) {
139152
.n_threads <- as.integer(getOption("mc.cores", 1L))
140153
.batch_result <- if (identical(Func, MutualClusteringInfoSplits)) {
@@ -235,9 +248,7 @@ CalculateTreeDistance <- function(Func, tree1, tree2 = NULL,
235248
#' @importFrom stats setNames
236249
.SplitDistanceManyMany <- function(Func, splits1, splits2,
237250
tipLabels, nTip = length(tipLabels), ...) {
238-
if (!is.na(nTip) && nTip > 32767L) {
239-
stop("This many tips are not (yet) supported.")
240-
}
251+
.CheckMaxTips(nTip)
241252
if (is.na(nTip)) {
242253
tipLabels <- union(unlist(tipLabels, use.names = FALSE),
243254
unlist(TipLabels(splits2), use.names = FALSE))
@@ -408,9 +419,7 @@ CalculateTreeDistance <- function(Func, tree1, tree2 = NULL,
408419
if (ncol(x) != ncol(y)) {
409420
stop("Input splits must address same number of tips.")
410421
}
411-
if (nTip > 32767L) {
412-
stop("This many tips are not (yet) supported.")
413-
}
422+
.CheckMaxTips(nTip)
414423
}
415424

416425
.CheckLabelsSame <- function(labelList) {

R/zzz.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
.SL_MAX_TIPS <- NULL # populated in .onLoad
2+
3+
.onLoad <- function(libname, pkgname) {
4+
.SL_MAX_TIPS <<- cpp_sl_max_tips()
5+
}
6+
17
.onUnload <- function(libpath) {
28
StopParallel(quietly = TRUE)
39
library.dynam.unload("TreeDist", libpath)

src/RcppExports.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,16 @@ BEGIN_RCPP
636636
return rcpp_result_gen;
637637
END_RCPP
638638
}
639+
// cpp_sl_max_tips
640+
int cpp_sl_max_tips();
641+
RcppExport SEXP _TreeDist_cpp_sl_max_tips() {
642+
BEGIN_RCPP
643+
Rcpp::RObject rcpp_result_gen;
644+
Rcpp::RNGScope rcpp_rngScope_gen;
645+
rcpp_result_gen = Rcpp::wrap(cpp_sl_max_tips());
646+
return rcpp_result_gen;
647+
END_RCPP
648+
}
639649
// cpp_robinson_foulds_distance
640650
List cpp_robinson_foulds_distance(const RawMatrix& x, const RawMatrix& y, const IntegerVector& nTip);
641651
RcppExport SEXP _TreeDist_cpp_robinson_foulds_distance(SEXP xSEXP, SEXP ySEXP, SEXP nTipSEXP) {
@@ -780,6 +790,7 @@ static const R_CallMethodDef CallEntries[] = {
780790
{"_TreeDist_cpp_transfer_dist_all_pairs", (DL_FUNC) &_TreeDist_cpp_transfer_dist_all_pairs, 4},
781791
{"_TreeDist_cpp_transfer_dist_cross_pairs", (DL_FUNC) &_TreeDist_cpp_transfer_dist_cross_pairs, 5},
782792
{"_TreeDist_cpp_mci_impl_score", (DL_FUNC) &_TreeDist_cpp_mci_impl_score, 3},
793+
{"_TreeDist_cpp_sl_max_tips", (DL_FUNC) &_TreeDist_cpp_sl_max_tips, 0},
783794
{"_TreeDist_cpp_robinson_foulds_distance", (DL_FUNC) &_TreeDist_cpp_robinson_foulds_distance, 3},
784795
{"_TreeDist_cpp_robinson_foulds_info", (DL_FUNC) &_TreeDist_cpp_robinson_foulds_info, 3},
785796
{"_TreeDist_cpp_matching_split_distance", (DL_FUNC) &_TreeDist_cpp_matching_split_distance, 3},

src/pairwise_distances.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ NumericVector cpp_mutual_clustering_all_pairs(
305305
const int n_tip,
306306
const int n_threads = 1
307307
) {
308+
TreeDist::check_ntip(n_tip);
308309
const int N = splits_list.size();
309310
if (N < 2) return NumericVector(0);
310311

@@ -397,6 +398,7 @@ NumericVector cpp_rf_info_all_pairs(
397398
const int n_tip,
398399
const int n_threads = 1
399400
) {
401+
TreeDist::check_ntip(n_tip);
400402
const int N = splits_list.size();
401403
if (N < 2) return NumericVector(0);
402404
const int n_pairs = N * (N - 1) / 2;
@@ -516,6 +518,7 @@ NumericVector cpp_msd_all_pairs(
516518
const int n_tip,
517519
const int n_threads = 1
518520
) {
521+
TreeDist::check_ntip(n_tip);
519522
const int N = splits_list.size();
520523
if (N < 2) return NumericVector(0);
521524
const int n_pairs = N * (N - 1) / 2;
@@ -618,6 +621,7 @@ NumericVector cpp_msi_all_pairs(
618621
const int n_tip,
619622
const int n_threads = 1
620623
) {
624+
TreeDist::check_ntip(n_tip);
621625
const int N = splits_list.size();
622626
if (N < 2) return NumericVector(0);
623627
const int n_pairs = N * (N - 1) / 2;
@@ -710,6 +714,7 @@ NumericVector cpp_shared_phylo_all_pairs(
710714
const int n_tip,
711715
const int n_threads = 1
712716
) {
717+
TreeDist::check_ntip(n_tip);
713718
const int N = splits_list.size();
714719
if (N < 2) return NumericVector(0);
715720
const int n_pairs = N * (N - 1) / 2;
@@ -875,6 +880,7 @@ NumericVector cpp_jaccard_all_pairs(
875880
const bool allow_conflict = true,
876881
const int n_threads = 1
877882
) {
883+
TreeDist::check_ntip(n_tip);
878884
const int N = splits_list.size();
879885
if (N < 2) return NumericVector(0);
880886
const int n_pairs = N * (N - 1) / 2;
@@ -944,6 +950,7 @@ NumericMatrix cpp_mutual_clustering_cross_pairs(
944950
const List& splits_a, const List& splits_b,
945951
const int n_tip, const int n_threads = 1
946952
) {
953+
TreeDist::check_ntip(n_tip);
947954
const int nA = splits_a.size();
948955
const int nB = splits_b.size();
949956
if (nA == 0 || nB == 0) return NumericMatrix(nA, nB);
@@ -987,6 +994,7 @@ NumericMatrix cpp_rf_info_cross_pairs(
987994
const List& splits_a, const List& splits_b,
988995
const int n_tip, const int n_threads = 1
989996
) {
997+
TreeDist::check_ntip(n_tip);
990998
const int nA = splits_a.size();
991999
const int nB = splits_b.size();
9921000
if (nA == 0 || nB == 0) return NumericMatrix(nA, nB);
@@ -1027,6 +1035,7 @@ NumericMatrix cpp_msd_cross_pairs(
10271035
const List& splits_a, const List& splits_b,
10281036
const int n_tip, const int n_threads = 1
10291037
) {
1038+
TreeDist::check_ntip(n_tip);
10301039
const int nA = splits_a.size();
10311040
const int nB = splits_b.size();
10321041
if (nA == 0 || nB == 0) return NumericMatrix(nA, nB);
@@ -1070,6 +1079,7 @@ NumericMatrix cpp_msi_cross_pairs(
10701079
const List& splits_a, const List& splits_b,
10711080
const int n_tip, const int n_threads = 1
10721081
) {
1082+
TreeDist::check_ntip(n_tip);
10731083
const int nA = splits_a.size();
10741084
const int nB = splits_b.size();
10751085
if (nA == 0 || nB == 0) return NumericMatrix(nA, nB);
@@ -1110,6 +1120,7 @@ NumericMatrix cpp_shared_phylo_cross_pairs(
11101120
const List& splits_a, const List& splits_b,
11111121
const int n_tip, const int n_threads = 1
11121122
) {
1123+
TreeDist::check_ntip(n_tip);
11131124
const int nA = splits_a.size();
11141125
const int nB = splits_b.size();
11151126
if (nA == 0 || nB == 0) return NumericMatrix(nA, nB);
@@ -1153,6 +1164,7 @@ NumericMatrix cpp_jaccard_cross_pairs(
11531164
const bool allow_conflict = true,
11541165
const int n_threads = 1
11551166
) {
1167+
TreeDist::check_ntip(n_tip);
11561168
const int nA = splits_a.size();
11571169
const int nB = splits_b.size();
11581170
if (nA == 0 || nB == 0) return NumericMatrix(nA, nB);
@@ -1206,6 +1218,7 @@ NumericVector cpp_clustering_entropy_batch(
12061218
const List& splits_list,
12071219
const int n_tip
12081220
) {
1221+
TreeDist::check_ntip(n_tip);
12091222
const int N = splits_list.size();
12101223
NumericVector result(N);
12111224
if (N == 0 || n_tip <= 0) return result;
@@ -1239,6 +1252,7 @@ NumericVector cpp_splitwise_info_batch(
12391252
const List& splits_list,
12401253
const int n_tip
12411254
) {
1255+
TreeDist::check_ntip(n_tip);
12421256
const int N = splits_list.size();
12431257
NumericVector result(N);
12441258
if (N == 0 || n_tip < 4) return result;

src/tree_distances.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,23 @@ namespace TreeDist {
2828
}
2929
}
3030

31-
void check_ntip(const double n) {
32-
// Validated by R caller (nTip > 32767 guard in CalculateTreeDistance et al.)
33-
ASSERT(n <= static_cast<double>(std::numeric_limits<int16>::max())
34-
&& "This many tips are not (yet) supported.");
31+
void check_ntip(const int32 n) {
32+
if (n > SL_MAX_TIPS) {
33+
Rcpp::stop("Trees with %d tips exceed the compiled limit of %d. "
34+
"Update TreeTools to support more tips, then reinstall "
35+
"TreeDist.", static_cast<int>(n),
36+
static_cast<int>(SL_MAX_TIPS));
37+
}
3538
}
3639

3740

3841
}
3942

43+
// [[Rcpp::export]]
44+
int cpp_sl_max_tips() {
45+
return static_cast<int>(SL_MAX_TIPS);
46+
}
47+
4048
using TreeDist::resize_uninitialized;
4149

4250
inline List robinson_foulds_distance(const RawMatrix &x, const RawMatrix &y,
@@ -619,7 +627,9 @@ inline List shared_phylo (const RawMatrix &x, const RawMatrix &y,
619627
List cpp_robinson_foulds_distance(const RawMatrix &x, const RawMatrix &y,
620628
const IntegerVector &nTip) {
621629
ASSERT(x.cols() == y.cols() && "Input splits must address same number of tips.");
622-
return robinson_foulds_distance(x, y, static_cast<int32>(nTip[0]));
630+
const int32 n_tip = static_cast<int32>(nTip[0]);
631+
TreeDist::check_ntip(n_tip);
632+
return robinson_foulds_distance(x, y, n_tip);
623633
}
624634

625635
// [[Rcpp::export]]

src/tree_distances.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ constexpr splitbit ALL_ONES = (std::numeric_limits<splitbit>::max)();
1818

1919
namespace TreeDist {
2020

21+
// Validate that n_tips does not exceed the compiled SL_MAX_TIPS limit.
22+
// Defined in tree_distances.cpp; calls Rcpp::stop() on failure.
23+
void check_ntip(int32 n);
24+
2125
// Re-exported from mutual_clustering.h:
2226
// ic_matching(int16 a, int16 b, int16 n)
2327

0 commit comments

Comments
 (0)